From ce73bc1111fd0a813391c952ad9008188e9aa776 Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:08:07 +0200 Subject: [PATCH 1/8] freesailarr: no exponential notation in computed limits (3.0.2+up1.0.0) The null-safe fallback helpers from #99 route values through fromYaml, which parses every number as float64. Go's "%v" switches float64 to exponential notation from about 1e6 upwards, so a bare byte count like 1048576 reached the suffix check inside multiplyQuantity as "1.048576e+06" and the render aborted with `unsupported resource quantity suffix "e+06"`. Both printf "%v" sites in multiplyQuantity now go through a new formatNumber helper that forces plain decimal notation and trims the padded zeros. The second site is the quiet half of the bug: a fractional limitFactor together with a bare number produced no error at all, just an invalid quantity such as "1.4999985e+06" that only the API server rejects on apply. limitFactor is typed "number" in this chart's values.schema.json, so a fractional factor is an ordinary, schema-valid input. All ten resource paths of the stack are affected and were verified one by one (gluetun, qbittorrent and its configInit init container, prowlarr, flaresolverr, radarr, recyclarr, sonarr, seerr, bazarr). The VPN, firewall and container structure is untouched: rendering the chart with its default values is byte-identical to main. Teil von #157 --- freesailarr/Chart.yaml | 2 +- freesailarr/templates/_helpers.tpl | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/freesailarr/Chart.yaml b/freesailarr/Chart.yaml index 7b261f3..db2b74b 100644 --- a/freesailarr/Chart.yaml +++ b/freesailarr/Chart.yaml @@ -8,7 +8,7 @@ description: A Helm chart to deploy a Wireguard-tunneled media automation stack type: application -version: 3.0.1+up1.0.0 +version: 3.0.2+up1.0.0 # The chart's own version marker, freely chosen and tied to no upstream. The # stack bundles nine independently versioned projects, so there is no single # "app version" to track here. diff --git a/freesailarr/templates/_helpers.tpl b/freesailarr/templates/_helpers.tpl index a49f2c4..93a2ef4 100644 --- a/freesailarr/templates/_helpers.tpl +++ b/freesailarr/templates/_helpers.tpl @@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary results are printed without a decimal point. */}} {{- define "freesailarr.multiplyQuantity" -}} -{{- $quantity := printf "%v" .quantity -}} +{{- $quantity := include "freesailarr.formatNumber" .quantity -}} {{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}} {{- if not $number -}} {{- fail (printf "cannot parse resource quantity %q" $quantity) -}} @@ -80,7 +80,24 @@ results are printed without a decimal point. {{- if eq $product (floor $product) -}} {{- printf "%d%s" (int64 $product) $suffix -}} {{- else -}} -{{- printf "%v%s" $product $suffix -}} +{{- printf "%s%s" (include "freesailarr.formatNumber" $product) $suffix -}} +{{- end -}} +{{- end -}} + +{{/* +Render a number as a plain decimal string, never in exponential notation. + +Why this exists (issue #157): the null-safe fallback helpers pass values +through fromYaml, which parses every number as float64. Go's "%v" switches +float64 to exponential notation from about 1e6 upwards, so a bare byte count +like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the +render aborted. Strings ("512Mi") are passed through untouched. +*/}} +{{- define "freesailarr.formatNumber" -}} +{{- if kindIs "float64" . -}} +{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}} +{{- else -}} +{{- printf "%v" . -}} {{- end -}} {{- end -}} From 35b4ce0a4217147c2aa298a5ede8ebe9edd7571f Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:09:12 +0200 Subject: [PATCH 2/8] patchmon: render byte counts without exponential notation (7.0.1+up2.1.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The null-safe fallback helpers from #99 pass values through fromYaml, which parses every number as float64. Go's "%v" switches float64 to exponential notation from about 1e6 upwards, so a bare byte count like 1048576 reached multiplyQuantity as "1.048576e+06" and the render aborted with unsupported resource quantity suffix "e+06" in "1.048576e+06" The same "%v" sits a second time on the output of a non-integral product, where it never passes the suffix check — a fractional limitFactor together with a bare number rendered a silently invalid quantity ("1.4999985e+06") that only the API server rejects on apply. Both call sites now go through a new patchmon.formatNumber helper, which forces decimal notation for float64 and passes strings ("512Mi") through untouched. This is the canonical fix agreed in #157, taken over verbatim from template-chart with this chart's define prefix, so multiplyQuantity stays the same copy in every chart. Teil von #157. --- patchmon/Chart.yaml | 2 +- patchmon/templates/_helpers.tpl | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/patchmon/Chart.yaml b/patchmon/Chart.yaml index 95b0002..f06dc76 100644 --- a/patchmon/Chart.yaml +++ b/patchmon/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/PatchMon/PatchMon/main/frontend/public/a type: application -version: 7.0.0+up2.1.3 +version: 7.0.1+up2.1.3 appVersion: "2.1.3" maintainers: diff --git a/patchmon/templates/_helpers.tpl b/patchmon/templates/_helpers.tpl index eb9d1d5..5baae8e 100644 --- a/patchmon/templates/_helpers.tpl +++ b/patchmon/templates/_helpers.tpl @@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary results are printed without a decimal point. */}} {{- define "patchmon.multiplyQuantity" -}} -{{- $quantity := printf "%v" .quantity -}} +{{- $quantity := include "patchmon.formatNumber" .quantity -}} {{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}} {{- if not $number -}} {{- fail (printf "cannot parse resource quantity %q" $quantity) -}} @@ -80,7 +80,24 @@ results are printed without a decimal point. {{- if eq $product (floor $product) -}} {{- printf "%d%s" (int64 $product) $suffix -}} {{- else -}} -{{- printf "%v%s" $product $suffix -}} +{{- printf "%s%s" (include "patchmon.formatNumber" $product) $suffix -}} +{{- end -}} +{{- end -}} + +{{/* +Render a number as a plain decimal string, never in exponential notation. + +Why this exists (issue #157): the null-safe fallback helpers pass values +through fromYaml, which parses every number as float64. Go's "%v" switches +float64 to exponential notation from about 1e6 upwards, so a bare byte count +like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the +render aborted. Strings ("512Mi") are passed through untouched. +*/}} +{{- define "patchmon.formatNumber" -}} +{{- if kindIs "float64" . -}} +{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}} +{{- else -}} +{{- printf "%v" . -}} {{- end -}} {{- end -}} From 5bffc3237a9b6e84cd19cd43fb6fb816ca814fec Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:13:12 +0200 Subject: [PATCH 3/8] searxng: render byte counts as plain decimals (1.0.1+up2026.8.22.9fea41204) multiplyQuantity turned its input into a string with printf "%v". The null-safe fallback helpers from #99 pass values through fromYaml, which parses every number as float64, and Go's "%v" switches float64 to exponential notation from about 1e6 upwards. A bare byte count like 1048576 therefore arrived as "1.048576e+06", the suffix check rejected "e+06" and the render aborted. The same printf sat on the second output path, for a non-integer product (a fractional limitFactor - typed "number", not "integer", in this chart's values.schema.json, so ordinary values reach it). That one never passes the suffix check, so it produced an invalid quantity silently and only the API server would have rejected the manifest on apply. Both are now routed through a formatNumber helper that forces decimal notation and trims the padding zeros. Strings ("512Mi") pass through untouched, so the default render is unchanged - including the checksum/config annotation over the settings.yml ConfigMap, which stays at 619e1ac4 for the CI values and 25bd1479 for the chart defaults, so no pod is rolled without reason. Part of #157. The helper is a verbatim copy of the fix already merged for template-chart, zipline, cyberchef and bentopdf - multiplyQuantity is the same duplicated code in every chart. --- searxng/Chart.yaml | 2 +- searxng/templates/_helpers.tpl | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/searxng/Chart.yaml b/searxng/Chart.yaml index 3f0cf7e..28e1c69 100644 --- a/searxng/Chart.yaml +++ b/searxng/Chart.yaml @@ -9,7 +9,7 @@ type: application # (2026.8.22-9fea41204) and "latest" moves daily. appVersion is therefore the # exact tag, and the +up suffix normalizes the dash to a dot as the versioning # scheme requires - the same shape rss-bridge and minecraft-server use. -version: 1.0.0+up2026.8.22.9fea41204 +version: 1.0.1+up2026.8.22.9fea41204 appVersion: "2026.8.22-9fea41204" maintainers: diff --git a/searxng/templates/_helpers.tpl b/searxng/templates/_helpers.tpl index a2bee5f..a648573 100644 --- a/searxng/templates/_helpers.tpl +++ b/searxng/templates/_helpers.tpl @@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary results are printed without a decimal point. */}} {{- define "searxng.multiplyQuantity" -}} -{{- $quantity := printf "%v" .quantity -}} +{{- $quantity := include "searxng.formatNumber" .quantity -}} {{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}} {{- if not $number -}} {{- fail (printf "cannot parse resource quantity %q" $quantity) -}} @@ -80,7 +80,24 @@ results are printed without a decimal point. {{- if eq $product (floor $product) -}} {{- printf "%d%s" (int64 $product) $suffix -}} {{- else -}} -{{- printf "%v%s" $product $suffix -}} +{{- printf "%s%s" (include "searxng.formatNumber" $product) $suffix -}} +{{- end -}} +{{- end -}} + +{{/* +Render a number as a plain decimal string, never in exponential notation. + +Why this exists (issue #157): the null-safe fallback helpers pass values +through fromYaml, which parses every number as float64. Go's "%v" switches +float64 to exponential notation from about 1e6 upwards, so a bare byte count +like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the +render aborted. Strings ("512Mi") are passed through untouched. +*/}} +{{- define "searxng.formatNumber" -}} +{{- if kindIs "float64" . -}} +{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}} +{{- else -}} +{{- printf "%v" . -}} {{- end -}} {{- end -}} From e666cfed6cfa85927f3bb885f7089923a229258c Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:13:57 +0200 Subject: [PATCH 4/8] rss-bridge: render byte counts as plain decimals (1.0.2+up2025.08.05) Teil von #157. The null-safe fallback helpers from #99 pass values through fromYaml, which goes over JSON and therefore parses every number as float64. Go's "%v" switches float64 to exponential notation from about 1e6 upwards, so a bare byte count like 1048576 turned into "1.048576e+06" - not a valid Kubernetes quantity. The suffix check in multiplyQuantity then aborted the render with unsupported resource quantity suffix "e+06" in "1.048576e+06" The second "%v" - the one printing a non-integer product - never reaches that check and failed silently instead: with a fractional limitFactor it rendered "1.4999985e+06" into the manifest, which only the API server rejects on apply. Both call sites now go through a new rss-bridge.formatNumber helper that forces decimal notation via printf "%.10f" and trims the padding zeros again; the leading dot in the regex protects "100.0000000000" from collapsing to "1". Strings such as "512Mi" pass through untouched. This is the canonical fix from #157, applied verbatim with this chart's prefix; the round-trip itself is not touched. Note on this chart specifically: rss-bridge sets explicit memory and ephemeral-storage limits as chart defaults, and an explicit limit always wins, so multiplyQuantity is not reachable through this chart's values at all - the bug was reproduced and the fix verified on a throwaway copy with those default limits removed. The rendered output for the chart defaults and for ci/rss-bridge/test-values.yaml is byte-identical to main. Patch bump: no behavior and no values-schema change. --- rss-bridge/Chart.yaml | 2 +- rss-bridge/templates/_helpers.tpl | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/rss-bridge/Chart.yaml b/rss-bridge/Chart.yaml index bde18f1..f4170d0 100644 --- a/rss-bridge/Chart.yaml +++ b/rss-bridge/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/RSS-Bridge/rss-bridge/master/static/logo type: application -version: 1.0.1+up2025.08.05 +version: 1.0.2+up2025.08.05 appVersion: "2025-08-05" maintainers: diff --git a/rss-bridge/templates/_helpers.tpl b/rss-bridge/templates/_helpers.tpl index bab5acb..f05cc2a 100644 --- a/rss-bridge/templates/_helpers.tpl +++ b/rss-bridge/templates/_helpers.tpl @@ -63,7 +63,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary results are printed without a decimal point. */}} {{- define "rss-bridge.multiplyQuantity" -}} -{{- $quantity := printf "%v" .quantity -}} +{{- $quantity := include "rss-bridge.formatNumber" .quantity -}} {{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}} {{- if not $number -}} {{- fail (printf "cannot parse resource quantity %q" $quantity) -}} @@ -76,7 +76,24 @@ results are printed without a decimal point. {{- if eq $product (floor $product) -}} {{- printf "%d%s" (int64 $product) $suffix -}} {{- else -}} -{{- printf "%v%s" $product $suffix -}} +{{- printf "%s%s" (include "rss-bridge.formatNumber" $product) $suffix -}} +{{- end -}} +{{- end -}} + +{{/* +Render a number as a plain decimal string, never in exponential notation. + +Why this exists (issue #157): the null-safe fallback helpers pass values +through fromYaml, which parses every number as float64. Go's "%v" switches +float64 to exponential notation from about 1e6 upwards, so a bare byte count +like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the +render aborted. Strings ("512Mi") are passed through untouched. +*/}} +{{- define "rss-bridge.formatNumber" -}} +{{- if kindIs "float64" . -}} +{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}} +{{- else -}} +{{- printf "%v" . -}} {{- end -}} {{- end -}} From d95e8569c892a1f5e97b5b004547fddcb99b7ed5 Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:18:19 +0200 Subject: [PATCH 5/8] homeassistant: render byte counts as plain decimals Part of #157. The null-safe fallback helpers from #99 pass values through fromYaml, which parses every number as float64; Go's "%v" switches float64 to exponential notation from about 1e6 upwards. A bare byte count like 1048576 therefore reached multiplyQuantity as "1.048576e+06" and the render aborted on the suffix check. This is the canonical correction already merged into the other charts, adopted verbatim with this chart's define prefix: multiplyQuantity formats its input and its non-integer product through a new formatNumber helper. The fromYaml round-trip itself is not touched - it is not the cause, and it has just been unified across all charts. Measured against ci/homeassistant/test-values.yaml: resources.requests.memory=1048576 aborted before and now yields request 1048576 / limit "3145728"; 65536 still yields 196608. The silent variant matters here too - values.schema.json types resources.limitFactor as "number" with exclusiveMinimum 0, so a fractional factor is allowed and rendered an invalid quantity without any error (limitFactor 1.5 with memory 999999 produced memory: "1.4999985e+06"); it now produces 1499998.5. --- homeassistant/templates/_helpers.tpl | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/homeassistant/templates/_helpers.tpl b/homeassistant/templates/_helpers.tpl index 78611e0..987eb67 100644 --- a/homeassistant/templates/_helpers.tpl +++ b/homeassistant/templates/_helpers.tpl @@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary results are printed without a decimal point. */}} {{- define "homeassistant.multiplyQuantity" -}} -{{- $quantity := printf "%v" .quantity -}} +{{- $quantity := include "homeassistant.formatNumber" .quantity -}} {{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}} {{- if not $number -}} {{- fail (printf "cannot parse resource quantity %q" $quantity) -}} @@ -80,7 +80,24 @@ results are printed without a decimal point. {{- if eq $product (floor $product) -}} {{- printf "%d%s" (int64 $product) $suffix -}} {{- else -}} -{{- printf "%v%s" $product $suffix -}} +{{- printf "%s%s" (include "homeassistant.formatNumber" $product) $suffix -}} +{{- end -}} +{{- end -}} + +{{/* +Render a number as a plain decimal string, never in exponential notation. + +Why this exists (issue #157): the null-safe fallback helpers pass values +through fromYaml, which parses every number as float64. Go's "%v" switches +float64 to exponential notation from about 1e6 upwards, so a bare byte count +like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the +render aborted. Strings ("512Mi") are passed through untouched. +*/}} +{{- define "homeassistant.formatNumber" -}} +{{- if kindIs "float64" . -}} +{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}} +{{- else -}} +{{- printf "%v" . -}} {{- end -}} {{- end -}} From c668165fb3aa35f602aa6d749773f9a4e19fe5f6 Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:18:33 +0200 Subject: [PATCH 6/8] homeassistant: a NOTES.txt worth reading (6.1.0+up2026.8.3) Part of #43. This chart was cited throughout that issue as the one that already has a NOTES.txt and therefore serves as the style reference for the whole programme. The file contained the single line "Hello :)". It is replaced here by the real thing, built on the pattern the later charts (zipline, voucher-vault, urlaubsverwaltung) established in the meantime. It prints what an operator would otherwise have to read out of the templates: the access URL assembled from the chart's upstream-style ingress block (hosts x tls, so the scheme is derived per host rather than assumed) or the port-forward command; the three fields the 1Password item must carry (config, http-config, recorder-config) and how configuration.yaml has to include the other two itself; and the traps of this particular chart - onboarding hands the instance to the first visitor, use_x_forwarded_for/trusted_proxies decides between 400 Bad Request and one client IP for everyone, the three config files are read-only subPath mounts that the kubelet never refreshes, safe mode lets a broken configuration pass the probes, and helm uninstall deletes the PVC that holds all of the state. Conditional sections cover scaleDown/replicas 0, replicas > 1 on a single ReadWriteOnce claim, a NET_RAW override that silently kills device discovery, a non-empty env block, the chart placeholder host chart-example.local, and the case where ingress.tls names a Secret while no cert-manager annotation is set - this chart adds none by itself, unlike the rest of the repo. No domain appears in the file: hosts come from the values, examples use example.com. Minor bump, appVersion unchanged; a NOTES.txt changes no rendered object, verified by diffing helm template before and after. --- homeassistant/Chart.yaml | 2 +- homeassistant/templates/NOTES.txt | 292 +++++++++++++++++++++++++++++- 2 files changed, 292 insertions(+), 2 deletions(-) diff --git a/homeassistant/Chart.yaml b/homeassistant/Chart.yaml index c70530c..e5ec7ff 100644 --- a/homeassistant/Chart.yaml +++ b/homeassistant/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart to deploy Home Assistant on Kubernetes icon: https://www.home-assistant.io/images/home-assistant-logo.svg type: application -version: 6.0.0+up2026.8.3 +version: 6.1.0+up2026.8.3 appVersion: "2026.8.3" diff --git a/homeassistant/templates/NOTES.txt b/homeassistant/templates/NOTES.txt index b8394df..31fb77d 100644 --- a/homeassistant/templates/NOTES.txt +++ b/homeassistant/templates/NOTES.txt @@ -1 +1,291 @@ -Hello :) \ No newline at end of file +{{- $replicas := include "homeassistant.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.replicas) -}} +{{- $res := fromYaml (include "homeassistant.effectiveResources" .Values.resources) -}} +{{/* +The ephemeral-storage limit goes through formatNumber for the reason issue #157 +documents: it comes back from fromYaml as float64, and a bare byte count would +print as "1e+06" here just as it did in the rendered manifest. +*/}} +{{- $ephRaw := index ($res.limits | default dict) "ephemeral-storage" -}} +{{- $eph := "the computed ephemeral-storage limit" -}} +{{- if not (kindIs "invalid" $ephRaw) -}} +{{- $eph = include "homeassistant.formatNumber" $ephRaw -}} +{{- end -}} +{{- $startup := fromYaml (include "homeassistant.startupProbe" .Values.startupProbe) -}} +{{- $budget := "" -}} +{{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}} +{{- $budget = printf "%.0f seconds" (mulf $startup.periodSeconds $startup.failureThreshold) -}} +{{- end -}} +{{- $ctr := fromYaml (include "homeassistant.containerSecurityContext" .Values.securityContext) -}} +{{- $caps := (index $ctr "capabilities") | default dict -}} +{{- $netRaw := has "NET_RAW" ((index $caps "add") | default list) -}} +{{- $ing := .Values.ingress -}} +{{- $hosts := $ing.hosts | default list -}} +{{- $tls := $ing.tls | default list -}} +{{- $ann := $ing.annotations | default dict -}} +{{- $issuer := or (hasKey $ann "cert-manager.io/cluster-issuer") (hasKey $ann "cert-manager.io/issuer") -}} +{{- $certName := "" -}} +{{- range $t := $tls }}{{ if not $certName }}{{ $certName = ($t.secretName | default "") }}{{ end }}{{ end }} +{{- $placeholder := false -}} +{{- range $h := $hosts }}{{ if eq ($h.host | default "") "chart-example.local" }}{{ $placeholder = true }}{{ end }}{{ end }} +{{- $pod := printf "%s-%s-sfs-0" .Release.Name .Chart.Name -}} +{{- $sfs := printf "%s-%s-sfs" .Release.Name .Chart.Name -}} +Home Assistant {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}. + +{{- if $ing.enabled }} + +ACCESS +{{- range $h := $hosts }} +{{- $scheme := "http" }} +{{- range $t := $tls }} +{{- if has ($h.host | default "") ($t.hosts | default list) }}{{ $scheme = "https" }}{{ end }} +{{- end }} +{{- range $p := ($h.paths | default list) }} + {{ $scheme }}://{{ $h.host }}{{ $p.path }} +{{- end }} +{{- end }} + + This chart keeps the upstream-style ingress block — hosts and tls lists + instead of the domain/clusterIssuer pair the other charts in this repo use. + That is a documented exception, and it has one consequence worth knowing + before you go looking for a certificate: +{{- if not $tls }} + + ingress.tls is empty, so the Ingress carries no TLS section at all and the + URLs above are plain http. Nothing here requests a certificate. +{{- else if not $issuer }} + + ingress.tls names a secretName, but NOTHING IN THIS RELEASE CREATES THAT + SECRET. The chart adds no cert-manager annotation of its own — unlike the + other charts here, which build one from ingress.clusterIssuer — and + ingress.annotations carries neither cert-manager.io/cluster-issuer nor + cert-manager.io/issuer. Until that Secret exists by some other means, + ingress-nginx answers with its own default certificate and every browser + shows a name mismatch. Add the annotation under ingress.annotations: + + ingress: + annotations: + cert-manager.io/cluster-issuer: letsencrypt +{{- else }} + + The certificate is requested by cert-manager through the annotation you set + under ingress.annotations; the chart adds none by itself, and it does not + name the TLS Secret either — you did, in ingress.tls. cert-manager's + ingress-shim names the Certificate after that Secret, so this is the object + to watch while the browser still shows a warning: + + kubectl -n {{ .Release.Namespace }} get certificate {{ $certName | default "" }} +{{- end }} +{{- else }} + +ACCESS — NO INGRESS + ingress.enabled is false, so this release has no Ingress. Nothing else in + this chart reads ingress.hosts, so no other setting silently depends on it. + Reach the instance inside the cluster with + + kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-{{ .Chart.Name }}-service 8123:{{ .Values.service.port }} + + and open http://localhost:8123. The Service listens on port {{ .Values.service.port }} and + forwards to the container's 8123; keeping 8123 as the LOCAL port matters, + because a browser that has already talked to another Home Assistant on + localhost:8123 will otherwise mix up the stored tokens. +{{- end }} +{{- if and $ing.enabled $placeholder }} + +THE INGRESS IS PUBLISHED UNDER THE CHART'S PLACEHOLDER HOST + ingress.enabled is true while ingress.hosts still contains the default entry + "chart-example.local". That name resolves nowhere, so the Ingress exists, + ingress-nginx accepts it, and no request ever arrives. Set ingress.hosts to + the name you actually serve — for example example.com — before assuming the + release is unreachable for some deeper reason. +{{- end }} +{{ if eq $replicas "0" }} +SCALED TO ZERO + {{ if .Values.scaleDown }}scaleDown: true{{ else }}replicas: 0{{ end }} — the StatefulSet runs with replicas: 0, so nothing answers + until you set it back and upgrade again. The PVC, the OnePasswordItem and + the Service{{ if $ing.enabled }} and Ingress stay in place, so no state is lost and no + certificate is re-issued.{{ else }} stay in place, so no state is lost.{{ end }} + + Bringing this release back up is not a no-op: the pod re-reads the config + item from the vault as it starts, and it starts on whatever app version the + chart carries now. A release parked at zero for a long time therefore comes + back changed in two ways at once. +{{ end }} +THE CONFIGURATION IS A 1PASSWORD ITEM, AND IT IS READ-ONLY IN THE CONTAINER + Home Assistant is configured from files, not from the environment, and this + chart delivers those files out of a single 1Password item. The operator + copies the item's FIELDS into the Kubernetes Secret, one key per field name, + and this chart mounts exactly three of them. The field names are decided + here and appear nowhere in 1Password, so they have to match to the letter: + + secrets.configSecretRef → {{ .Values.secrets.configSecretRef | default "(not set — the render fails without it)" }} + → Secret {{ .Release.Name }}-{{ .Chart.Name }}-config-secret + + config → /config/configuration.yaml + http-config → /config/http-config.yaml + recorder-config → /config/recorder-config.yaml + + The three are separate files on purpose, and configuration.yaml has to pull + the other two in itself — the chart only mounts them, it does not wire them + together. A working minimum for the "config" field is + + default_config: + http: !include http-config.yaml + recorder: !include recorder-config.yaml + + A field spelled differently yields a Secret without that key. The pod then + hangs in CreateContainerConfigError while the Secret itself exists, so it + looks as if the operator had done its job. What it actually delivered: + + kubectl -n {{ .Release.Namespace }} get secret {{ .Release.Name }}-{{ .Chart.Name }}-config-secret -o jsonpath='{.data}' + + All three are mounted with subPath from a Secret volume, which makes them + READ-ONLY inside the container. Home Assistant's own file editor and every + UI page that offers to write configuration.yaml will fail on these three + paths — and only on these three. Everything else under /config is on the + PVC and writable as usual. + + Pointing secrets.configSecretRef at a DIFFERENT item rolls the pod: the + StatefulSet carries a checksum over the rendered OnePasswordItem, so the + item path is part of the pod spec. Changing the CONTENT of the same item + does not, and this is the sharp edge — Helm never sees that content, and a + subPath mount is never refreshed by the kubelet even after the operator has + updated the Secret. The running pod keeps the old files until something + else restarts it, which may be months later, so a broken value surfaces far + away from the edit that caused it. After every content change: + + kubectl -n {{ .Release.Namespace }} rollout restart statefulset/{{ $sfs }} + +BEHIND AN INGRESS, THE http BLOCK DECIDES BETWEEN 400 AND WRONG CLIENT IPs + Every request reaching Home Assistant through ingress-nginx arrives from the + controller's pod IP, not from the browser. The http-config field is where + that is dealt with, and both ways of getting it wrong are quiet in their own + way: + + use_x_forwarded_for: true without the proxy's address in trusted_proxies + makes Home Assistant reject the request outright — the browser gets + 400 Bad Request and the log names the untrusted address it saw. + + Neither setting at all means every client looks like the ingress + controller. Nothing fails; you simply get one address in the logbook for + everyone, and Home Assistant's login-attempt ban then locks out all users + at once because they share that address. + + The value belongs to the ingress controller's POD network, not to the nodes + and not to the clients: + + use_x_forwarded_for: true + trusted_proxies: + - 10.42.0.0/16 # the cluster's pod CIDR — check yours + + Read the address Home Assistant actually sees straight out of the log + rather than guessing at the CIDR: + + kubectl -n {{ .Release.Namespace }} logs {{ $pod }} | grep -i forwarded + +THE FIRST VISITOR BECOMES THE OWNER + Against an empty /config Home Assistant has no users and sends every visitor + into onboarding, where the first account created is the owner. There is no + password to look up anywhere and no Secret holding one — whoever reaches the + URL first owns this instance. Do the onboarding right after the first + rollout, not the next morning. + + Onboarding also decides the instance's location, unit system and time zone + and stores them under /config/.storage. That is state on the PVC, not + configuration in the 1Password item: it survives a pod restart, and it is + gone with the volume. + +TZ AND HOME ASSISTANT'S OWN TIME ZONE ARE TWO SETTINGS, NOT ONE + core.timezone ({{ .Values.core.timezone | default "unset" }}) is passed to the container as TZ and is the only + environment variable this chart sets by itself. Home Assistant's own time + zone is the one from onboarding or from configuration.yaml, and it is NOT + derived from this value. When the two disagree, log timestamps and + time-based automations disagree with them, and nothing reports a problem. + +WHAT A READY POD PROVES — AND WHAT IT DOES NOT + All three probes fetch "/" on the container port. That path is served by the + HTTP integration as soon as it has come up, which happens early in the + bootstrap and says nothing about the rest: + + A configuration.yaml that cannot be loaded does not necessarily keep the + pod from becoming Ready. Home Assistant falls back to safe mode, serves + the frontend with none of your configuration in effect, and the rollout + goes green. An instance that comes back "empty" after a config change is + this, and the reason is only in the log. + + An integration that fails to set up, a recorder that cannot reach its + database, a full volume — none of them are in the probe's answer. + + So check the log after every config change, not the rollout status: + + kubectl -n {{ .Release.Namespace }} logs {{ $pod }} + + The startup probe gates liveness and readiness and gives the first boot + {{ if $budget }}{{ $budget }}{{ else }}its configured budget{{ end }}, which is deliberately generous: a cold start + installs and initializes integrations. If a start outruns that budget the + kubelet kills the container mid-setup and the next attempt begins again, + which looks like CrashLoopBackOff with no error in the log at all. + +/config IS THE WHOLE OF THE STATE, AND helm uninstall DELETES IT + The owner account, every long-lived token, all UI-created automations and + dashboards, every integration's stored credentials and the recorder + database live under /config — on {{ .Release.Name }}-{{ .Chart.Name }}-pvc ({{ .Values.core.storage.size }} + from storage class {{ .Values.core.storage.storageClass | default "the cluster default" }}), inside its subPath "hass-config". + Only the three files above come from the vault; nothing else here is + reproducible from the chart's values. + + That PVC is an ordinary claim in this release, not a StatefulSet + volumeClaimTemplate: "helm uninstall {{ .Release.Name }}" deletes it, and with a Delete + reclaim policy the instance is then gone for good. Back it up before + uninstalling — a resource-policy annotation added to the live PVC afterwards + does not help, because Helm reads that annotation from the release manifest. + + The recorder is what makes the volume grow, and its retention is a field of + the 1Password item (purge_keep_days in recorder-config), not a chart value. + Growing core.storage.size only takes effect if the storage class allows + volume expansion; shrinking it is refused by Kubernetes and makes the next + helm upgrade fail on the PVC instead of touching the data. + +/tmp AND /run ARE emptyDirs AND COUNT AGAINST ephemeral-storage + The root filesystem is read-only, so the two paths the image must write to — + /run for s6-overlay's state and /tmp for Home Assistant's scratch files — + are emptyDirs. An emptyDir counts against the container's ephemeral-storage + limit ({{ $eph }}). Going over it does not fail a request: the kubelet evicts the + whole pod, and the reason is on the pod, not in Home Assistant's log. Where + integrations pull large downloads through /tmp, raise + resources.requests.ephemeralStorage — the limit follows it through + resources.limitFactor. +{{- if gt (atoi $replicas) 1 }} + +MORE THAN ONE REPLICA WILL NOT WORK HERE + replicas is {{ $replicas }}, but every pod of this StatefulSet mounts the SAME + ReadWriteOnce claim {{ .Release.Name }}-{{ .Chart.Name }}-pvc at /config — there is no + volumeClaimTemplate and no per-pod volume. Unless the storage class hands + that claim to several nodes, the additional pods stay stuck before start; + where they do start, two Home Assistant processes write the same state + directory and the same recorder database at once. This chart is built for + one replica, and Home Assistant does not run active-active. +{{- end }} +{{- if not $netRaw }} + +NET_RAW HAS BEEN DROPPED FROM THE CONTAINER + The chart's default adds NET_RAW back after dropping ALL, and the effective + securityContext.container for this release no longer has it. That is a + values override, not a chart default. Home Assistant's device discovery — + the DHCP watcher, the ping and arp integrations — opens raw sockets, and + without the capability it does not fail loudly: those integrations log + "Operation not permitted" and simply find nothing. Devices that used to + appear on their own will stop appearing. +{{- end }} +{{- if .Values.env }} + +env IS APPENDED, AND HOME ASSISTANT WILL NOT READ IT ON ITS OWN + The {{ len .Values.env }} entries land after TZ and are rendered through tpl, so a value may + contain template expressions. Note what they do NOT do: apart from TZ, Home + Assistant takes no configuration from the environment. An entry here reaches + the application only if the YAML in the 1Password item picks it up with the + !env_var tag — otherwise it sits in the pod spec unused. + + Anything sensitive belongs in valueFrom.secretKeyRef rather than a plain + value: a value is stored in the pod spec and readable by anyone who can read + pods in {{ .Release.Namespace }}. +{{- end }} From 3c59d31830319765744335863afd7b3b27b444ab Mon Sep 17 00:00:00 2001 From: "jlab-renovate[bot]" <274046083+jlab-renovate[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:29:06 +0000 Subject: [PATCH 7/8] Update lukevella/rallly Docker tag to v4.13.1 --- rallly/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rallly/Chart.yaml b/rallly/Chart.yaml index 4fc6c3a..463c7fd 100644 --- a/rallly/Chart.yaml +++ b/rallly/Chart.yaml @@ -5,8 +5,8 @@ icon: https://raw.githubusercontent.com/lukevella/rallly/main/apps/landing/publi type: application -version: 8.1.0+up4.13.0 -appVersion: "4.13.0" +version: 8.1.1+up4.13.1 +appVersion: "4.13.1" maintainers: - name: jklein \ No newline at end of file From 17ae06b58ffa3d8d1bb51c4fe549120534a1506b Mon Sep 17 00:00:00 2001 From: jklein Date: Sat, 29 Aug 2026 01:33:15 +0200 Subject: [PATCH 8/8] README: every chart ships a NOTES.txt, plus two stale chart-table facts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NOTES.txt bullet still said the file was "planned (#43)" and named nine charts as the ones shipping one. Both halves are now wrong: 23 of 23 charts carry a templates/NOTES.txt, template-chart included. The count was also wrong when it was written — the very commit that raised it to "nine" added template-chart/templates/NOTES.txt, making it ten, and the enumeration was never maintained. Replaced with a statement and no list, so the next new chart cannot make it stale again; the quality bar the programme settled on (rendered URL, which fields the 1Password item must carry, the options actually active, the chart's traps — conditional on the values really set) is kept and now reads as a requirement for new charts. Two further statements corrected against origin/main: - The chart table listed 22 of 23 charts; `bentopdf` was missing entirely although it ships (bentopdf/Chart.yaml, 1.0.1+up2.8.7) and has CI fixtures. - The homeassistant row advertised "has NOTES.txt and Helm tests". The NOTES.txt half stopped being a distinguishing property once every chart got one. The Helm tests half holds and is now stated as what it is: homeassistant/templates/tests/test-connection.yaml is the only such file in the repo. Documentation only — no chart directory, no version bump, no publish. Part of #43. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8e5c2d0..411529d 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo | Chart | Deploys | Notes | |---|---|---| | `apache-tika` | Apache Tika | Deployment + Service only (no ingress) | +| `bentopdf` | BentoPDF | Stateless Deployment + `config.json` ConfigMap, `subPath`-mounted into the web root and always rendered (empty renders as `{}`) | | `calibre-web-automated` | Calibre-Web Automated | StatefulSet, extra consume-PVC | | `cyberchef` | CyberChef | Stateless Deployment | | `freesailarr` | gluetun VPN media stack (qbittorrent, prowlarr, flaresolverr, radarr, recyclarr, sonarr, seerr, bazarr) | Single-pod StatefulSet: all apps share gluetun's network namespace; config volumes via volumeClaimTemplates, media PVCs as existingClaims (optional per-category `subPath`, so one claim can serve all three categories). One ClusterIP Service per app; no global `ingress.domain`. Ingress in two shapes that mix: one host per app (default, each app's host required), or one shared `ingress.host` with the apps as sub-paths (documented ingress exception below) | | `gotenberg` | Gotenberg | Deployment + Service only (no ingress) | -| `homeassistant` | Home Assistant | StatefulSet, has `NOTES.txt` and Helm tests | +| `homeassistant` | Home Assistant | StatefulSet; the only chart carrying Helm tests (`templates/tests/`) | | `matrix-synapse` | Matrix Synapse | StatefulSet plus an nginx delegation Deployment/Service | | `minecraft-router` | itzg/mc-router | Deployment + routing ConfigMap + NodePort Service (TCP only, no ingress); `mappings` is a required value | | `minecraft-server` | itzg/minecraft-server | StatefulSet + PVC + Service + init-script ConfigMap (no ingress); service type is a value (`ClusterIP` by default, routed via `minecraft-router`). Published once per Java line — the image tag comes from `.Chart.AppVersion`, so the Java line is chosen by picking the chart version, and `minecraft.version` ships a default matching that line. Since chart 2.0.0 the tag is an **immutable dated upstream release** (`appVersion: -java8` / `-java17` / `-java21`, e.g. `2026.8.2-java8`) instead of the continuously rebuilt rolling line tag. Pilot for the strict `values.schema.json` (#68), which is now binding for every chart | @@ -256,6 +257,6 @@ Reference implementation: `template-chart/values.schema.json` and `template-char - `icon:` is mandatory in every `Chart.yaml`. **Documented exception — freesailarr, the only chart without one**: the rule everywhere else is "the icon is this chart's one upstream's logo", and a chart bundling nine independently versioned projects has no such upstream — picking one of the nine would imply a "main app" the stack deliberately does not have. That is the whole criterion, and it applies to no other chart in the repo: a single-upstream chart without an icon is a missing icon, not an exception. The omission costs an INFO line in `helm lint --strict` (`icon is recommended`), which still exits 0. - `helm lint --strict` must pass without findings. -- `NOTES.txt` for every chart is planned (#43); nine charts ship one so far (`apache-tika`, `bentopdf`, `cyberchef`, `freesailarr`, `gotenberg`, `homeassistant`, `patchmon`, `rss-bridge`, `searxng`). Reference implementation: `template-chart/templates/NOTES.txt`, which is a placeholder-carrying template rather than finished prose — a new chart adapts it instead of starting from an empty file. The bar it sets: print what an operator would otherwise have to look up in the templates (the rendered access URL, **which fields the 1Password item must carry**, the options actually active in this release, and the chart's known traps), conditional on the values really set. A `NOTES.txt` that only repeats the values is skimmed once and never read again. **No real domain anywhere** — the URL renders from `ingress.domain`, and where an example is unavoidable it is `example.com`. Adding one renders no Kubernetes object, so it is purely additive: a minor bump. +- **Every chart ships a `templates/NOTES.txt`** (#43), `template-chart` included — so this is a requirement for a new chart, not a nice-to-have it can defer. No list of names here on purpose: a count goes stale the moment the next chart lands, which is exactly how the previous wording came to claim a number it never matched. Reference implementation: `template-chart/templates/NOTES.txt`, a placeholder-carrying template rather than finished prose, so a new chart adapts it instead of starting from an empty file. The bar it sets: print what an operator would otherwise have to look up in the templates (the rendered access URL, **which fields the 1Password item must carry**, the options actually active in this release, and the chart's known traps), conditional on the values really set. A `NOTES.txt` that only repeats the values is skimmed once and never read again. **No real domain anywhere** — the URL renders from `ingress.domain`, and where an example is unavoidable it is `example.com`. A `NOTES.txt` renders no Kubernetes object, so writing or reworking one is purely additive: a minor bump. - CI: every installable chart has fixtures under `ci//` (`test-values.yaml`, optionally `fixtures.yaml`/`fixtures.env.yaml`/`settings.env`) or a justified entry in `ci/excluded-charts.txt` — the install-test fails if both are missing. - Charts have no dependencies (`charts/` directories are empty).