Commit Graph
660 Commits
Author SHA1 Message Date
jklein fd7a653fa4 matrix-synapse: NOTES.txt and the exponential-quantity fix (7.1.0+upv1.159.0)
Two additive changes, neither of which alters a rendered object.

NOTES.txt (Teil von #43): prints what an operator would otherwise have to
read out of the templates - the rendered access URL or the port-forward
command, the field the 1Password item must carry (homeserver.yaml) plus the
four settings inside that file which this chart's mounts dictate, and the
chart's own traps: the split between matrix.delegation.serverUrl and
ingress.domain, the immutable server name, that a changed 1Password item does
not restart the StatefulSet while the delegation nginx does roll on a config
change, and that the PVC with signing key and media is deleted with the
release. Conditional on the values actually set: Ingress on/off, delegation
on/off, either workload scaled to zero, more than one Synapse replica, and a
matrix.env that is set. No real domain anywhere; the URLs render from the
values.

_helpers.tpl (Teil von #157): the canonical formatNumber helper, taken over
unchanged from template-chart. multiplyQuantity turned its input into a string
with printf "%v"; values that came back from fromYaml as float64 switched to
exponential notation from about 1e6 upwards, so a bare byte count like 1048576
became "1.048576e+06" and the render aborted. Both resource paths of this
chart were affected - matrix.resources in the StatefulSet and
matrix.delegation.resources in the delegation Deployment.

Minor bump, appVersion unchanged.
2026-08-29 00:31:40 +02:00
JK-Helper [BOT] 4ee94cdebd Merge pull request #182 from jk-cluster/feat/calibre-web-automated-notes-and-157
calibre-web-automated: NOTES.txt und die Korrektur für nackte Ganzzahl-Ressourcen (6.1.0+upv4.0.6)
2026-08-28 22:26:14 +00:00
JK-Helper [BOT] 4efad669c4 Merge pull request #181 from jk-cluster/feat/rallly-notes-and-157
rallly: NOTES.txt and the #157 number formatting (8.1.0+up4.13.0)
2026-08-28 22:26:07 +00:00
jklein bf02dab364 paperless-ngx: NOTES.txt and the #157 render fix (12.1.0+up3.0.5)
Two additive changes, no rendered Kubernetes object changes except the
resource limits that previously aborted the render.

#157 — a bare integer resource value from about 1e6 upwards aborted the
render. The null-safe fallbacks pass values through fromYaml, which parses
every number as float64, and Go's "%v" switches float64 to exponential
notation from that size on: 1048576 became "1.048576e+06", which is not a
valid Kubernetes quantity. The canonical correction from the issue is taken
over verbatim: multiplyQuantity formats its input and its non-integer product
through a new formatNumber helper instead of "%v". The toYaml/fromYaml round
trip is not touched — it was just unified across all charts and it is not the
cause.

This chart has two resource paths and both were affected: paperless.resources
(sfs) and smbConnection.cron.resources (consume CronJob). Measured on both
before and after, plus the 65536 fallback probe, which is unchanged.

#43 — NOTES.txt. Prints what an operator would otherwise read out of the
templates: the rendered URL and that ingress.domain also becomes PAPERLESS_URL,
the field names each 1Password item must carry (including that the database
field is "server" and that "username" is used as both admin login and admin
mail), and that secrets.oidcSecretRef is read by no template at all. The
conditional sections cover the SMB consume strecke, scaleDown, OIDC, Tika and
the barcode settings actually in effect. The traps it names are the ones this
chart really has: no checksum annotation anywhere, so rotating an item's
content restarts nothing; Redis failing silently while the UI stays green; the
600s startup budget against migrations and the 3.x index rebuild; the consume
PVC being hardcoded to 1Gi; the CronJob moving only top-level *.pdf and having
no probes (the documented exception); and a cron schedule starting with "*"
aborting the render.

README is deliberately left alone: the line listing the charts with a NOTES.txt
is pulled up once at the end of the programme, so that parallel NOTES PRs do
not collide on it.

Teil von #157
Teil von #43
2026-08-29 00:22:43 +02:00
JK-Helper [BOT] 1fc666f9df Merge pull request #180 from jk-cluster/feat/outline-notes-and-157
outline: NOTES.txt und die Exponential-Korrektur für Ressourcenwerte (7.1.0+up1.9.2)
2026-08-28 22:21:15 +00:00
jklein 65cb33e5f2 calibre-web-automated: add a NOTES.txt worth reading twice (6.1.0+upv4.0.6)
Prints what an operator would otherwise dig out of the templates: the rendered
access URL or the concrete port-forward, the fields the 1Password item has to
carry, and the traps of this chart in particular - the public default login,
the ingest folder that deletes what it imports, the fixed 1Gi ingest claim,
the one shared library claim, and why a change inside the 1Password item rolls
nothing (subPath mounts included).

Conditional on the values that actually decide something: ingress on/off,
scaled to zero, an unset secret ref, and extra env entries.

Part of #43.
2026-08-29 00:19:21 +02:00
jklein 5cc53b0772 calibre-web-automated: keep large integer quantities out of exponential notation
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 on the suffix check.

Route both the helper's input and its non-integral product through a new
formatNumber helper, which forces decimal notation and trims the padding
zeros again. Strings such as "512Mi" pass through untouched.

Part of #157.
2026-08-29 00:19:13 +02:00
jklein 8189b74f55 rallly: NOTES.txt and the #157 number formatting (8.1.0+up4.13.0)
Two additive changes in one release.

multiplyQuantity turned a bare integer resource value of about 1e6 or more
into exponential notation and aborted the render with "unsupported resource
quantity suffix \"e+06\"". The null-safe helpers from #99 pass values through
fromYaml, which parses every number as float64, and Go's "%v" switches those
to exponential form. The new rallly.formatNumber helper forces plain decimal
notation on both the input and the non-integer product; it is the same wording
already merged into template-chart, zipline and voucher-vault.

The NOTES.txt prints what an operator would otherwise have to read out of the
templates: the rendered access URL or the port-forward command, the fields each
1Password item has to carry, the OIDC redirect URI, and the traps of this chart
- OIDC is the only way in because email login and registration are hard-coded
off, a rotated 1Password item does not restart the pods, the probes only prove
that the web process answers, and the Next.js cache emptyDir counts against the
container's ephemeral-storage limit.

Part of #157
Part of #43
2026-08-29 00:19:11 +02:00
JK-Helper [BOT] 4e4b423b91 Merge pull request #178 from jk-cluster/feat/zipline-notes-and-157
zipline: NOTES.txt und die Korrektur der Exponentialschreibweise (7.1.0+up4.7.0)
2026-08-28 22:13:19 +00:00
jklein 17c2391895 outline: NOTES.txt and the exponential-quantity fix (7.1.0+up1.9.2)
Part of #157: multiplyQuantity turned a bare integer resource value into
exponential notation and aborted the render. The null-safe helpers pass
values through fromYaml, which makes every number a float64, and Go's "%v"
switches float64 to exponential form from about 1e6 upwards - so
"1048576" arrived as "1.048576e+06" and the suffix check failed. A new
outline.formatNumber helper renders such a value as a plain decimal
string; it is applied to the input and to the non-integer product. This is
the canonical fix agreed for the issue, taken over unchanged so the copies
of the helper in all charts stay identical.

Part of #43: the chart gets a NOTES.txt. It renders the access URL (or the
port-forward command when the Ingress is off), lists the fields each of the
three 1Password items has to carry plus the OIDC redirect URI, and states
the traps that are otherwise found the hard way: editing a value inside a
1Password item restarts nothing, Postgres and Redis are external and
unencrypted by default, migrations run inside the startup probe's budget,
uploads sit on a single RWO volume that helm uninstall takes with it, the
Ingress body-size limit is ten times smaller than the app's, and the chart
configures no mail at all.

Minor bump: the fix changes no rendered output for any value the schema
accepted before, and a NOTES.txt renders no object.
2026-08-29 00:10:42 +02:00
jklein db00198c6b zipline: NOTES.txt and the exponential-quantity fix (7.1.0+up4.7.0)
Part of #43 — templates/NOTES.txt.

The rule from the approved plan was that a NOTES.txt repeating the values is
worthless, so this one prints what an operator would otherwise look up in the
templates: the rendered URL (or the port-forward command when the Ingress is
off, plus the reason ingress.domain stays required — it is
CORE_DEFAULT_DOMAIN), the two op:// refs together with the exact field names
the 1Password items must carry ("connection-url" and "server-secret", because
the operator names the Secret keys after the item's fields), and the traps of
this chart: the setup page hands the super administrator to whoever reaches
the URL first, migrations run inside the startup probe's window (computed
from the effective probe values), a database outage leaves the pod running
and NotReady because liveness deliberately uses "/", the upload ceiling that
actually bites is the Ingress annotation and not the app's fixed 500mb, /tmp
is an emptyDir counted against the ephemeral-storage limit (computed) so an
oversized upload evicts the pod, and the uploads PVC is an ordinary release
resource that "helm uninstall" deletes. Conditional sections cover scaleDown
/ replicas: 0, more than one replica against the single ReadWriteOnce claim,
and a set zipline.env. No domain is baked into the chart; every host is
rendered from the values, examples use example.com.

Part of #157 — multiplyQuantity.

The null-safe fallbacks 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 "1048576" arrived as "1.048576e+06" and the
render aborted on the suffix check. This is the canonical correction agreed
for all charts, taken over verbatim with this chart's prefix — the input and
the non-integer product now go through a formatNumber helper, and the
toYaml/fromYaml round trip is not touched, since it is not the cause.

Measured against ci/zipline/test-values.yaml on the chart's only resources
path, zipline.resources: before, both requests.memory=1048576 and
requests.ephemeralStorage=1048576 aborted with
  unsupported resource quantity suffix "e+06" in "1.048576e+06"
after, they render with the limits "3145728", while the 65536 fallback probe
still yields 196608 unchanged. Rendered manifests are byte-identical to the
previous version for the default values.
2026-08-29 00:10:31 +02:00
JK-Helper [BOT] 52a02c5472 Merge pull request #179 from jk-cluster/feat/voucher-vault-notes-and-157
voucher-vault: exponential-notation fix and a NOTES.txt (6.1.0+up1.30.1)
2026-08-28 22:09:26 +00:00
JK-Helper [BOT] 0da0fef872 Merge pull request #177 from jk-cluster/feat/template-chart-notes-and-157
template-chart: NOTES.txt and the naked-integer render fix (4.1.0+upxxx)
2026-08-28 22:05:43 +00:00
jklein e0541a0352 voucher-vault: add a NOTES.txt worth reading twice (6.1.0+up1.30.1)
The notes print what an operator would otherwise have to dig out of the
templates or out of the image, not a restatement of the values.

The centrepiece is the secret inventory: the three OnePasswordItem CRs
carry item paths only, so the field names each item must contain are
decided in this chart and appear nowhere in 1Password. A missing field
does not fail the install - the operator writes a Secret without that key
and the pod stays in CreateContainerConfigError.

The warnings are the ones this chart earns, all read out of the running
image rather than assumed:

- The first start against an empty database creates the superuser "admin"
  with a random password and prints it to stdout exactly once. With OIDC
  on, the app drops its password-change pages and the login form redirects
  to the provider by itself, so /admin/ is the documented way back in.
- The probes send Host: 127.0.0.1, which is always in ALLOWED_HOSTS. A
  wrong ingress.domain is therefore invisible: probes pass, the rollout
  goes green, and every real request gets 400.
- The entrypoint waits for Postgres in an nc loop with no timeout before
  opening the HTTP port, so a wrong database host produces no error at all
  - just a log that stops, until the startup probe budget runs out.
- Celery worker and beat are started detached from the same entrypoint;
  uwsgi answers whether or not they survived, so a wrong redis-url costs
  the expiry notifications silently.
- Every start runs makemigrations and migrate, and helm rollback does not
  reverse them.
- Expiry notifications need a per-user Apprise URL, and the beat schedule
  is created only while an empty database is initialized.

Conditional blocks cover the ingress and no-ingress access paths, the
scaled-down state, the storage class, and the case where voucherVault.env
has replaced the chart's full env list instead of extending it. The
startup budget and the replica state are read through the chart's own
helpers. No real domain appears anywhere; the one place an example is
needed uses example.com.

Minor bump, purely additive: a NOTES.txt changes no rendered object.

Part of #43
2026-08-29 00:03:06 +02:00
jklein 800a198017 voucher-vault: keep large integer quantities out of exponential notation
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 reached
multiplyQuantity as "1.048576e+06" and the render aborted with

  unsupported resource quantity suffix "e+06" in "1.048576e+06"

Both the input and the non-integer product now go through a formatNumber
helper that forces decimal notation and trims the padding zeros; strings
such as 512Mi pass through untouched. This is the canonical fix agreed for
all charts - multiplyQuantity is the same copy everywhere, so deviating
here would create drift instead of a solution.

Measured against ci/voucher-vault/test-values.yaml on both request keys
that get a computed limit (memory, ephemeral-storage): 1048576 renders a
limit of "3145728" instead of aborting, and the fallback case 65536 is
unchanged at 196608.

Part of #157
2026-08-29 00:02:51 +02:00
jklein 0217422179 template-chart: NOTES.txt and the naked-integer render fix (4.1.0+upxxx)
Two changes to the reference implementation, both of which every chart copied from it inherits.

Render fix (part of #157): multiplyQuantity turned its input into a string with printf "%v". The null-safe fallback helpers pass values through fromYaml, which parses every number as float64, and Go's "%v" switches float64 to exponential notation from about 1e6 upwards - so a bare byte count like 1048576 arrived as "1.048576e+06" and the suffix check aborted the render. A new xxx.formatNumber helper forces plain decimal notation on both the input and the non-integer product; strings like "512Mi" pass through untouched. This is the canonical fix agreed for the issue, taken over verbatim, because multiplyQuantity is the same copy in every chart and a private variant would create drift instead of a solution. The toYaml/fromYaml round-trip is deliberately not touched: it is not the cause.

NOTES.txt (part of #43): the scaffold now carries the reference NOTES.txt - a placeholder-carrying template, not finished prose. It renders the access URL from ingress.domain (no real domain anywhere), names which fields the 1Password item must carry, and prints the chart's traps: the placeholder image, the fact that both a Deployment and a StatefulSet render, and that no checksum/secret-ref means a secret change restarts nothing here. Output is conditional on the values actually set.

appVersion unchanged; minor bump because both changes are additive.
2026-08-28 23:59:38 +02:00
jklein cf730affe4 Merge pull request #175 from jk-cluster/feat/bentopdf-chart
bentopdf: neuer Helm-Chart für den PDF-Werkzeugkasten (1.0.0+up2.8.7)
2026-08-28 16:40:26 +00:00
jklein 2b3ab7f8b7 Merge pull request #172 from jk-cluster/renovate/lukevella-rallly-4.x
Update lukevella/rallly Docker tag to v4.13.0
2026-08-28 16:38:00 +00:00
jklein 8df1913c5c bentopdf: new chart for the PDF toolbox (1.0.0+up2.8.7)
BentoPDF computes entirely in the visitor's browser; the container only serves
static files through nginx. The chart is therefore close to cyberchef - same
nginx-unprivileged base, uid 101, port 8080, probes on "/" - with four
differences that shaped the design.

Runtime config.json. The one setting reachable at runtime is the tool list.
It is a static file in the middle of the web root, so it is mounted by
subPath: a directory mount would hide the whole application. subPath mounts
are never refreshed by the kubelet, which makes the checksum/config annotation
(#82) load-bearing rather than decorative. bentopdf.config is passed through
verbatim, like searxng's settings, so a key upstream adds tomorrow is a values
change and not a chart change.

Three env switches that would kill the pod. PORT, DISABLE_IPV6 and
ROBOTS_NOINDEX are implemented as "sed -i" against files in the image, run
under "set -e" in /docker-entrypoint.d/, and the base image's entrypoint
aborts when one of them fails. With readOnlyRootFilesystem: true the pod
therefore never starts instead of ignoring the switch - measured in k3d as
CrashLoopBackOff, exit 1, "sed: can't create temp file
'/etc/nginx/nginx.confXXXXXX': Read-only file system". None of the three is
offered as a first-class value; values.yaml names the failure picture and the
escape hatch (env block plus readOnlyRootFilesystem: false).

Writable paths were measured, not copied from the upstream chart.
/etc/nginx/tmp holds the pid file and /var/cache/nginx is where nginx creates
its temp directories at startup - without it the master aborts with
'mkdir() "/var/cache/nginx/client_temp" failed'. /var/run is deliberately not
mounted: the running pod proves it is unnecessary.

Everything else about BentoPDF is a build-time argument and out of reach for
any chart; values.yaml says so explicitly rather than leaving it to be
discovered. Two accepted consequences are named there too: some tools fetch
WASM modules from a public CDN at runtime, and the served pages carry a
canonical link to the vendor's site.

Fixes #174
2026-08-28 18:22:30 +02:00
jlab-renovate[bot] 990f2eda3d Update lukevella/rallly Docker tag to v4.13.0 2026-08-27 19:36:01 +00:00
jklein ce5f654a8a Merge pull request #173 from jk-cluster/feat/searxng-chart
searxng: neues Chart (1.0.0+up2026.8.22.9fea41204)
2026-08-27 19:34:58 +00:00
jklein cf43d90beb searxng: state the limiter probe measurement instead of hedging it
The comment on the probe block said a probe on "/" would "risk" a 429 once
the limiter is on. It was measured against a reachable Valkey in k3d and it
does not risk it, it collects one: "/" answered 429 while /healthz answered
200. Comment only, no rendered change.
2026-08-27 21:13:08 +02:00
jklein 46942db53a searxng: new chart (1.0.0+up2026.8.22.9fea41204)
SearXNG as a stateless Deployment plus a settings ConfigMap, with the four CI
steps a new chart needs: publish workflow, change-app-version choice list,
ci/searxng/ fixtures and a renovate.json5 block.

Three points where the chart is not a cyberchef with another name:

* The image entrypoint writes settings.yml into /etc/searxng only when the
  file is missing. The chart always mounts one by subPath, so the copy and
  the random-secret sed never run and the container needs no write access
  there - which is what allows readOnlyRootFilesystem: true.
* The signing key is a required OnePasswordItem, not a ConfigMap key. It
  signs the settings cookie and the image-proxy URLs, and the entrypoint's
  self-generating fallback cannot fire here, so without it the instance would
  run on the literal "ultrasecretkey" - which webapp.py refuses with exit 1.
* All three probes use /healthz, the only path the bot detection lets through
  unconditionally. Measured: with the limiter active, "/" answers 429 while
  /healthz still answers 200.

ingress.domain is read a second time outside the Ingress template, for
SEARXNG_BASE_URL, and carries its own required() there - so it stays
mandatory with the Ingress switched off.

The Renovate versioning regex deviates from the plan on the issue and matches
the commit hash WITHOUT capturing it. Renovate parses a "build" group with
Number.parseInt, so a hash becomes the digits leading it; measured on
renovate 41.173.1 and 44.48.0, the planned form reports an image pushed
earlier the same day as an update to a newer one. Reasoning in the block and
in README.
2026-08-27 21:09:59 +02:00
jklein 71f5d2d450 Merge pull request #170 from jk-cluster/ci/freesailarr-install-test
ci: freesailarr wirklich installieren statt überspringen (Wireguard-Zugang aus 1Password)
2026-08-26 12:56:00 +00:00
jklein e5f98435ed ci: redact declared secret values from install-test diagnostics
The failure path prints container logs, and an app may print a value it was
given - gluetun logs its Wireguard address on startup. diagnose() now runs
through a filter that replaces the values of the declared REQUIRED_ENV
variables with the variable name. GitHub masks them as well, but that is the
platform's promise rather than this harness's, and the script runs locally too,
where nothing masks anything.
2026-08-26 14:37:46 +02:00
jklein 69a280399c ci: run the freesailarr install-test for real instead of skipping it
freesailarr was excluded from the install-test because gluetun never becomes
healthy without a real Wireguard key and no app container behind its kill
switch can become Ready. The green "Install-test freesailarr pass" was a
25-second SKIP on the most complex chart in the repo - a signal that proved
nothing.

With a CI-only Mullvad key in 1Password the reason is gone:

- ci/run-install-test.sh learns a general rule: ci/<chart>/fixtures.env.yaml is
  rendered with envsubst before it is applied, restricted to the variable names
  a chart declares in REQUIRED_ENV. A missing or empty declared variable fails
  the run loudly and names the variable - it never skips the chart.
- The install-test job loads the key from 1Password Connect for freesailarr
  only, following the pattern publish-helm-chart.yml documents: values via
  steps.<id>.outputs.*, mapped into the step's own env, quoted, with ":?"
  guards that name the missing op:// field before a cluster is created.
- Fixtures for freesailarr: three media claims, the gluetun secret template
  with variable names only, firewallOutboundSubnets so the kill switch does not
  block cluster DNS, and INSTALL_TIMEOUT=900 for nine containers.

freesailarr/Chart.yaml goes 3.0.0+up1.0.0 -> 3.0.1+up1.0.0. That bump is
deliberately empty of content: the install-test matrix follows changed chart
directories, and without it this PR could not run the test it adds.
2026-08-26 14:30:42 +02:00
jklein 5280554329 Merge pull request #168 from jk-cluster/feat/freesailarr-shared-domain-subpaths
freesailarr: gemeinsame Domain mit Unterpfaden und qBittorrent-Speicherpfad (3.0.0+up1.0.0)
2026-08-25 19:45:50 +00:00
jklein 65c35dff46 freesailarr: two measured details from the k3d end-to-end run
Both are documentation only - the rendering is byte-identical to the previous
commit.

The *arr probes: with a UrlBase set, radarr/sonarr/prowlarr answer the bare
/ping with 307 and Location: /<urlbase>/ping, not 200. The probes keep passing
because the kubelet counts 200-399 as success (9/9 Ready, 0 restarts), so the
probe paths stay at /ping and are valid in both ingress shapes. Recorded so
nobody reads the 307 as a fault.

Bazarr under a sub-path without its manual base URL: it does not answer 404.
It returns its index page for every unknown path, so /bazarr/ and
/bazarr/<asset> both come back as 200 text/html of identical length - the page
loads, the assets silently arrive as HTML, and nothing looks like an error.
The content type is the tell, not the status code.
2026-08-25 21:10:49 +02:00
jklein fc768efaf5 freesailarr: shared domain with sub-paths, qBittorrent save path (3.0.0+up1.0.0)
Adds a second, optional ingress shape and fixes the download target of
qBittorrent. The existing per-app-host shape stays the default and renders
unchanged.

Shared domain (#167)

Setting ingress.host moves every app without a host of its own into one
Ingress object with one path each (<app>.ingress.path, default /<app>), one
TLS secret and therefore one DNS name and one certificate for the whole
stack. A per-app host always wins and keeps its own object, so mixed
operation needs no extra values.

The apps differ in what they can do under a sub-path, and the chart follows
that rather than pretending they are alike:

  - radarr, sonarr, prowlarr get <APP>__SERVER__URLBASE from the chart, but
    only when their own env block does not already carry the variable.
  - bazarr has no environment variable for its base path and stays one
    documented manual step (Settings -> General -> Base URL).
  - qbittorrent has no base path at all and renders a second object on the
    same host with a prefix-stripping rewrite. It repeats the same TLS secret
    name and deliberately omits the cert-manager annotation, so both objects
    state their own TLS while only one certificate is requested.
  - seerr has no base path either and cannot be rewritten, so it is pinned to
    "/" and a fail refuses any other path. A second fail refuses two apps
    claiming the same path.

qBittorrent save path (#166)

Without Session\DefaultSavePath qBittorrent writes to its built-in default
/config/Downloads - the 1Gi config volume. It fills within minutes, every
torrent goes to "errored" and qBittorrent can no longer write its own
metadata to BT_backup, so a restart in that state costs the torrent list.

The init container (renamed qbittorrent-config-interface ->
qbittorrent-config-seed, since it no longer only sets the interface) now
seeds qbittorrent.savePath, default /downloads. Unlike the interface binding
it is inserted ONLY when the key is absent and never replaced: a path changed
in the WebUI is a legitimate operator decision and must survive a restart.

This is the reason for the major bump: an installation that never set the
path writes somewhere else after the upgrade.

Documentation

NOTES.txt and the media block in values.yaml now spell out why a shared claim
with three subPaths does not produce hardlinks: the kernel compares the
vfsmount in do_linkat, not the superblock, so two bind mounts of one
filesystem are EXDEV - while stat reports the same device number on both
sides and suggests the opposite. The layout that would work (one claim, one
mount, categories as sub-directories) is named as a possibility; the chart
cannot express it today.

README.md gains the shared domain as the second admissible ingress shape of
the style guideline.
2026-08-25 20:55:43 +02:00
jklein b2d74ca5fb Merge pull request #165 from jk-cluster/fix/freesailarr-shared-media-claim
freesailarr: one volume entry per media claim (2.0.2+up1.0.0)
2026-08-24 19:08:00 +00:00
jklein 39e9613ee3 freesailarr: one volume entry per media claim (2.0.2+up1.0.0)
media.downloads, media.movies and media.tv rendered three fixed volume
entries, one per category. Two categories on the same existingClaim
therefore produced two pod volumes with an identical claimName, and such a
pod never starts: it stays in Init:0/1 without a single kubelet event
(measured on RKE2/Longhorn, RWO and RWX alike).

The volume list is now deduplicated by claim name. A new mediaVolumeName
helper names the entry after the first ENABLED category in the fixed order
downloads, movies, tv that carries the claim; the other categories mount
that same entry and differ only in their subPath. The restriction to
enabled categories matters because the three entries render under
different conditions - a canonical owner whose entry is not rendered would
be a dangling reference.

With three distinct claims the chart renders byte-identically to 2.0.1,
hence a patch bump.

Also corrected in the values documentation, after measuring instead of
assuming: the kubelet creates missing subPath directories itself, but they
inherit the claim root's mode, and fsGroup cannot be relied on to fix that
- Longhorn's fsGroupPolicy (ReadWriteOnceWithFSType) skips fsGroup on RWX
claims entirely. What has to hold is that the claim root is writable for
PUID/PGID. No chown or init container was added.
2026-08-24 21:01:53 +02:00
jklein 6c3b6214ac Merge pull request #163 from jk-cluster/feat/minecraft-server-restore-java21
minecraft-server: Verzeichnis auf java21 zurückstellen (4.0.1+up2026.8.2.java21)
2026-08-24 15:30:00 +00:00
jklein 8a68df737a minecraft-server: restore the java21 line (4.0.1+up2026.8.2.java21)
Closes the three-step round that carried the #99 null-safety to every Java
line. Pure restoration of the directory to the newest line:

  version:            4.0.0+up2026.8.2.java17 -> 4.0.1+up2026.8.2.java21
  appVersion:         "2026.8.2-java17"       -> "2026.8.2-java21"
  minecraft.version:  "1.20.1"                -> "1.21.1"

Nothing changes in content, and that is not a claim but a measurement. Against
the java21 state published from #156, the ENTIRE chart tree differs in exactly
one line - the chart version - and .Chart.Version is not read by any template,
so the rendering is byte-identical:

  4.0.0+up2026.8.2.java21 (published) -> this chart:
    file tree: 1 differing line, Chart.yaml version
    rendering: byte-identical (CI values, serverpack init container enabled)

  main (java17) -> this chart:
    exactly TWO differences, both the line selection itself -
    image itzg/minecraft-server:2026.8.2-java17 -> :2026.8.2-java21 and
    env VERSION 1.20.1 -> 1.21.1.

Why 4.0.1 and not 4.0.0: 4.0.0+up2026.8.2.java21 is already in the registry
from #156, and helm push overwrites an existing OCI tag without complaint. The
version check in validate-charts.yml does not catch this - it compares only
against github.base_ref, where the version would read java17, so a republished
4.0.0 would pass the gate and silently overwrite the published artifact. Tracked
as #160.

What this round achieves, measured across the three published line states:
templates/ and values.schema.json are byte-identical on java8 (f7047b8), java17
(2b22ce5) and java21 (this chart); values.yaml differs only in minecraft.version
and the comment naming the appVersion; and all three now carry initImage
alpine:3.24, so the bump that had reached only java21 is no longer missing on
the other two. All three lines therefore run the same hardening, the same three
exec probes, the same resources handling and the same null-safe fallbacks.

Left untouched on purpose: the float64 quantity finding (#157) and seccompProfile
(#161) - the latter has to be pulled through all three lines and has its own
unknowns in gosu and the serverpack init container.

Refs #99
2026-08-24 17:26:41 +02:00
jklein 2b22ce5538 Merge pull request #162 from jk-cluster/feat/minecraft-server-java17-line
minecraft-server: java17-Linie nachziehen (4.0.0+up2026.8.2.java17)
2026-08-24 15:23:38 +00:00
jklein 73b304c1aa minecraft-server: publish the java17 line (4.0.0+up2026.8.2.java17)
Third of three line publications that carry the #99 null-safety to every Java
line. Templates and schema are the same for all three lines; only the
line-selecting fields change:

  version:            4.0.0+up2026.8.2.java8 -> 4.0.0+up2026.8.2.java17
  appVersion:         "2026.8.2-java8"       -> "2026.8.2-java17"
  minecraft.version:  "1.12.2"               -> "1.20.1"   (Java 17 runs
                                                            Minecraft 1.18-1.20.4)

What the diff does NOT show, same as for java8: the java17 line was last
published as 3.0.0+up2026.8.2.java17, and since then main also moved the
serverpack init image from alpine:3.22 to alpine:3.24 (commit 5a35ea4, which
landed on the java21 line only). That bump rides along here.

It is invisible in the diff against main, because relative to main the file
already says 3.24 - the java8 publication in #159 brought it there. Relative to
what a java17 release actually runs today it is a base-image change, so it is
named here rather than left to be discovered.

Measured against ci/minecraft-server/test-values.yaml with the serverpack init
container enabled, compared structurally (documents paired by kind/name,
recursive over keys) instead of line by line:

  3.0.0+up2026.8.2.java17 (published) -> this chart:
    exactly ONE difference, initContainers[0].image alpine:3.22 -> alpine:3.24.

  main (java8) -> this chart:
    exactly TWO differences, both the line selection itself -
    image itzg/minecraft-server:2026.8.2-java8 -> :2026.8.2-java17 and
    env VERSION 1.12.2 -> 1.20.1.

Hardening, the three exec probes, resources and the init.sh chown are unchanged,
which is what the null-safety was built to do: it changes what an ERASED block
renders, not what the defaults render.

At file level the line invariant holds exactly - against main's tree the only
differing files are Chart.yaml and values.yaml, in the three line fields plus
the comment naming the appVersion. _helpers.tpl, values.schema.json and both
templates are byte-identical across the lines.

The null-safety itself is not re-swept - #156 proved it on identical templates.
Confirmed once on this build that it carries: erasing both security contexts
renders byte-identical to the defaults.

Left untouched on purpose: the float64 quantity finding (#157), because
multiplyQuantity is the same copy in all 21 charts and a single-chart fix would
create drift, and seccompProfile (#84), which changes runtime behaviour and
belongs in its own change.

Refs #99
2026-08-24 17:19:50 +02:00
jklein f7047b80f0 Merge pull request #159 from jk-cluster/feat/minecraft-server-java8-line
minecraft-server: java8-Linie nachziehen (4.0.0+up2026.8.2.java8)
2026-08-24 15:16:38 +00:00
jklein b049101da5 minecraft-server: publish the java8 line (4.0.0+up2026.8.2.java8)
Second of three line publications that carry the #99 null-safety to every Java
line. The chart is published once per line, the templates and the schema are
the same for all three, and only the directory's line-selecting fields change:

  version:            4.0.0+up2026.8.2.java21 -> 4.0.0+up2026.8.2.java8
  appVersion:         "2026.8.2-java21"       -> "2026.8.2-java8"
  minecraft.version:  "1.21.1"                -> "1.12.2"   (Java 8 runs
                                                             Minecraft <= 1.16.5)

What the diff does NOT show, and what this commit therefore has to say out
loud: the java8 line was last published as 3.0.0+up2026.8.2.java8, and since
then main also moved the serverpack init image from alpine:3.22 to alpine:3.24
(commit 5a35ea4, which landed on the java21 line only). That bump rides along
here. It is invisible in this PR's diff, because relative to main the file
already says 3.24 - but relative to what a java8 release actually runs today it
is a base-image change, and the cluster side has to know.

Measured, rather than assumed. Rendered against ci/minecraft-server/test-values
with the serverpack init container enabled and compared structurally (documents
paired by kind/name, recursive over keys) instead of line by line:

  3.0.0+up2026.8.2.java8 (published) -> this chart:
    exactly ONE difference, initContainers[0].image alpine:3.22 -> alpine:3.24.

  main (java21) -> this chart:
    exactly TWO differences, both the line selection itself -
    image itzg/minecraft-server:2026.8.2-java21 -> :2026.8.2-java8 and
    env VERSION 1.21.1 -> 1.12.2.

Nothing else moves: the hardening, the three exec probes, the resources and the
init.sh chown are unchanged, which is what the null-safety was built to do (it
changes what an ERASED block renders, not what the defaults render).

The null-safety itself is not re-swept here - #156 proved it on identical
templates. Confirmed once on this build that it carries: erasing both security
contexts renders byte-identical to the defaults.

The float64 quantity finding (#157) is deliberately untouched, per its own
issue: multiplyQuantity is the same copy in all 21 charts and a single-chart
fix would create drift.

Refs #99
2026-08-24 17:11:12 +02:00
jklein 86a6504159 Merge pull request #156 from jk-cluster/fix/minecraft-server-null-safe-hardening
minecraft-server: Null-Absicherung für Härtung, Probes und Resources
2026-08-24 15:07:08 +00:00
jklein fc8f57e176 minecraft-server: null-safe hardening, probes and resources
The last open gap of #99. minecraft-server was the one chart of 21 where an
erased security context removed the hardening in silence: it never got the
defaultedDict helper, the StatefulSet rendered .Values.securityContext.pod
straight through toYaml, and the schema typed securityContext.pod as a strict
"object" without required. Measured against ci/minecraft-server/test-values.yaml
on 3.0.1:

  --set securityContext.pod=null --set securityContext.container=null
  -> helm exit 0, no schema error, "securityContext: null" for BOTH the pod and
     the container - no runAsNonRoot, no readOnlyRootFilesystem, no dropped
     capabilities, in a chart with a PVC.

Bitter place for it: the README lists this chart as the pilot for #68. It got
its schema early and was therefore never caught by the later null-safety wave.

Both halves are added, because they cover different cases. For a key WITH a
chart default Helm's coalescing deletes the null before validation ever sees
it, and only the defaultedDict fallback catches that; for a key WITHOUT one
(server.resources.limits) the null survives into validation, where the strict
"object" aborted the render. Now widened to ["object","null"].

The fallback table reproduces THIS chart's defaults, deviations included:
uid/gid pinned to 1000 (the image's minecraft user - starting as it directly
takes the entrypoint's non-root branch, which skips the recursive chown of
/data and the write to /etc/nsswitch.conf that a read-only root filesystem
would reject), fsGroupChangePolicy OnRootMismatch (a recursive chown of a 32Gi
world on every start costs minutes), the explicit empty capabilities.add list,
and all three probes as exec probes running the image's mc-health script - this
image has no HTTP endpoint, so a probe default rebuilt as httpGet would leave
the workload with no working probe at all. The startup budget stays 15s x 80.

server.resources goes through the same helper while keeping its memory request
deliberately unset, so it is still derived from the JVM heap.

Also, the same class one level down: init.sh chowned the migrated serverpack to
a hardcoded 1000:1000 while the uid it has to match was overridable. Both reads
now go through the same helper, so they cannot disagree. With the chart defaults
the rendered script is unchanged, so checksum/config does not move and no
existing pod rolls.

minecraft.resources.storage and .memory stay deliberately not null-safe: an
erased value yields an invalid PVC or an unparseable quantity, which is the loud
class #99 excludes.

Evidence, all against ci/minecraft-server/test-values.yaml unless noted:

  - the reported call now renders the defaults, byte-identical to the
    unmodified rendering. Fallback, not rejection.
  - rendering with securityContext, all three probes and server.resources set
    to null is byte-identical to rendering the chart defaults - which is what
    keeps the table and values.yaml in sync instead of leaving it to discipline.
  - no regression: rendering before and after the change is byte-identical, on
    the CI values, on the chart defaults, and with curseforgeServerpack enabled
    (the init-container path the CI values do not exercise). Confirmed
    structurally too, not only by raw diff.
  - partial nulling, 14 keys swept one at a time. On 3.0.1 twelve of them
    changed the rendering silently, including capabilities (the container kept
    every default capability instead of dropping ALL) and requests (cpu and
    ephemeral-storage disappeared entirely). On 4.0.0 all fourteen render
    identical to the defaults.
  - explicit false and 0 still win over the defaults; a scalar in a mapping slot
    is still rejected, now naming the values path.

Major bump: erasing one of these blocks used to change the rendered workload
and now does not, which is a runtime behaviour change. appVersion is untouched.

Refs #99
2026-08-24 16:53:25 +02:00
jklein 667cb55038 Merge pull request #151 from jk-cluster/renovate/docker.io-qmcgaw-gluetun-3.x
Update docker.io/qmcgaw/gluetun Docker tag to v3.41.3
2026-08-24 14:36:28 +00:00
jlab-renovate[bot] 38ea281c96 Update docker.io/qmcgaw/gluetun Docker tag to v3.41.3 2026-08-24 14:34:52 +00:00
jklein 14d22fcfe7 Merge pull request #155 from jk-cluster/feat/freesailarr-retrofit
freesailarr: Schema, Null-Absicherung, seccompProfile und NOTES.txt
2026-08-24 14:33:54 +00:00
jklein d9c7878afe README: bound the icon exception to its criterion
The previous wording named freesailarr as a documented exception but closed on
"the field is omitted, lint still exits 0", which reads as a general licence to
drop the icon. The rule is now stated with its criterion and its reach: the
exception exists because the chart has no single upstream whose logo the icon
could be, it is the only chart in the repo without one, and a single-upstream
chart missing an icon is a missing icon rather than an exception.
2026-08-24 16:27:53 +02:00
jklein a37588c2fe freesailarr: schema, null-safe fallbacks, seccompProfile, NOTES.txt
Retrofits the repo-wide groundwork that #59 deliberately left out to keep the
reviewed scope. Chart 2.0.0+up1.0.0 - a major, because seccompProfile changes
runtime behaviour; appVersion stays the freely chosen marker 1.0.0.

values.schema.json (#68). The largest schema in the repo: nine app blocks with
probes, resources, security contexts, env and image. additionalProperties is
false on every chart-owned level; the objects that go verbatim into the pod
spec stay open. It rejects a mistyped app name, a mistyped key at any depth and
ingress.domain - the key #59 removed, which until now would have been accepted
in silence.

Null safety (#99), both halves, because they cover different cases. The
defaultedDict helper catches a key WITH a chart default, where Helm's
coalescing deletes the null before the schema ever sees it; the widened
["object","null"] types catch a key WITHOUT one, where the null survives into
validation. Measured on the old chart: radarr.securityContext: null rendered
"securityContext: null" - the whole hardening gone - and
securityContext.capabilities: null dropped the capability block entirely, so
the container kept every default capability instead of dropping ALL. Both
render green.

The fallback table reproduces THIS chart's defaults including its deviations:
the five linuxserver.io containers keep runAsNonRoot unset and a writable root
filesystem so their s6 init can drop to PUID/PGID, flaresolverr keeps its
numerically pinned uid, gluetun keeps NET_ADMIN, and recyclarr keeps its exec
probes. Restoring the "clean" standard there would put the linuxserver
containers back on the image's uid 911 - the failure calibre-web-automated hit.
The sync between table and values.yaml is not left to discipline: rendering
with every block of the silent class explicitly set to null is byte-identical
to rendering the defaults, which is how a drift shows up.

seccompProfile RuntimeDefault (#84) in the pod context, so it covers the init
container too. Trivy's KSV-0104 drops from ten findings to none.

NOTES.txt (#43) covering what actually bites: the pod is not Ready until the
tunnel is up, the two fields the 1Password item needs, the warning when
firewallOutboundSubnets is unset, the immutable volumeClaimTemplates, and the
measured fact that imports do not hardlink across the media categories.

Also, from point 5 of the plan: checksum/config does not apply - the chart has
no ConfigMap - but checksum/secret-ref does, and was missing. Helm never sees
the secret's contents, yet it does see which op:// item the OnePasswordItem
points at, so repointing secrets.gluetunSecretRef now restarts the pod instead
of leaving the old credentials running.

README: the #43 status line was stale (it still claimed only homeassistant
ships NOTES.txt), and the mandatory-icon rule now records freesailarr as a
documented exception - #59 dropped the icon and did not amend the rule.
2026-08-24 16:06:20 +02:00
jklein 5300d7628c Merge pull request #154 from jk-cluster/fix/renovate-qbittorrent-legacy-tags
renovate: Schranke gegen die zwei Alt-Tags von qbittorrent
2026-08-24 13:51:23 +00:00
jklein f5da9ce126 renovate: exclude the two legacy qbittorrent tags
linuxserver/qbittorrent still carries 14.3.9 and 20.04.1 from earlier tagging
schemes. Docker versioning sorts both above every real release, so Renovate
offers them as major updates - PRs #152 and #153 are exactly those, and they
would have to be closed by hand again after every lookup.

They cost more than the noise. Renovate fills the major bucket once, so while a
stray occupies it the genuine major is never offered at all: measured from
currentValue 4.6.4, the unconstrained lookup returns 4.6.7 and 20.04.1, and
never mentions 5.2.3.

Both are excluded by name. A bound such as "<10" would reject a genuine major
sooner or later, whereas two finished artifacts cannot grow and so cannot mask
a release that does not exist yet. A shape-based filter was tried first and
measured out: requiring a non-padded minor drops 20.04.1 and simply promotes
14.3.9 into its place.

Verified with renovate 44.41.0 (dry-run=lookup) against the live registry:
  currentValue 5.2.1 -> [5.2.3 (patch)], both strays gone
  currentValue 4.6.4 -> [4.6.7 (patch), 5.2.3 (major)]
The second fixture is the guard the nginx rule taught us to run - it proves the
filter does not silently empty the update list. A full run over the real
repository with the real config confirms it in place: qbittorrent offers 5.2.3
only, nginx is unchanged, and no docker dependency carries a skipReason or a
warning.
2026-08-24 15:45:57 +02:00
jklein 9c81061f92 Merge pull request #59 from jk-cluster/feat/freesailarr-chart
Add freesailarr chart: gluetun-tunneled media automation stack
2026-08-24 13:38:58 +00:00
jklein 020e54d1ff Write the busybox image without the library/ namespace
docker.io/busybox and docker.io/library/busybox resolve to the same image on
Docker Hub, and the short form is the spelling the rest of the fleet will use
when the remaining unqualified official images (paperless-ngx, matrix-synapse,
minecraft-server) are qualified in the #84 follow-up.

Verified: the rendered output differs in exactly this one line, and a Renovate
lookup against the real chart files (44.41.0, official container, dry-run
lookup) still extracts all ten images through helm-values with no skipReason
and no warnings - docker.io/busybox included, whose candidate list stays empty
because 1.38.0 is current.
2026-08-24 15:37:10 +02:00
jklein 9cdbe097f6 Qualify the Docker Hub image names, drop the gluetun branding
main qualified five charts' image references in 6c73ef8 on the same day this
chart would otherwise arrive carrying the pattern that was just cleared out.
The seven Docker Hub names now say where they come from: qmcgaw/gluetun, the
five linuxserver.io images and the busybox init image, the last as
docker.io/library/busybox since it is an official image. The three ghcr.io
names were already qualified - checked rather than assumed.

Verified with Renovate 44.41.0 in the official container (RENOVATE_PLATFORM=
local, RENOVATE_DRY_RUN=lookup) that the prefixed form still resolves under the
built-in helm-values manager, since that manager is the only thing keeping
these images updated: it extracts all ten, datasource docker, no skipReason and
no warnings, and returns concrete candidates for eight of them. A control run
against the unqualified form produced byte-identical results, so the prefix
changes nothing about what Renovate offers.

Trivy's trusted-registry check (KSV-0125) drops from ten findings to none, which
is what the registry list in ci/trivy/data/ksv0125.yaml documents as the point
of writing images this way.

Chart metadata: the icon was gluetun's logo and the description opened with a
list of four of the nine projects, both suggesting a "main app" the stack does
not have. The description now names none of them, and the icon field is omitted
rather than filled with another project's logo - every other chart's icon is its
one upstream's, and this chart has no such upstream. helm lint --strict notes
"icon is recommended" as INFO and still exits 0.

Rendered output differs in exactly the seven image prefixes and nothing else.
2026-08-24 15:23:35 +02:00
jklein 09fe3c2be6 Decouple appVersion from gluetun in wording and value
The previous commit moved every image tag into values.yaml but left appVersion
described as a marker "for the gluetun line", to be updated together with
gluetun.image.tag. That still asserts a coupling the chart does not have, and
the value v3.41.1 confirmed it at a glance.

appVersion is now 1.0.0 (version 1.0.0+up1.0.0): the chart's own version
marker, freely chosen and tied to no upstream. The texts say that instead of
the old coupling in Chart.yaml, values.yaml and the two README passages. The
README's remaining true statement is kept but generalized - renovate-bump-chart
bumps the patch level on an unchanged appVersion for all nine images alike,
which is not a gluetun special case.

Also corrected on the way: the busybox init image comment claimed it was
"pinned independently of appVersion (which tracks gluetun)". Neither half holds
any more - appVersion tracks nothing, and every image in the chart is pinned
the same way.

No functional change: the rendered gluetun image is qmcgaw/gluetun:v3.41.1
before and after.
2026-08-24 15:10:14 +02:00