Commit Graph
425 Commits
Author SHA1 Message Date
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
jklein af5f9ed79b Pin gluetun in values too, so Renovate proposes it there
The chart bundles nine independently versioned upstreams, and eight of them
were already pinned in values.yaml while only gluetun took its tag from
appVersion. That split had no upside: it made gluetun the one image Renovate
could not reach through the built-in helm-values manager, and it implied a
"main app" the stack does not have.

gluetun now carries gluetun.image.repository/tag like every other image, and
the StatefulSet renders it from there. No tag comes from appVersion any more.
Renovate picks all nine up through helm-values - the manager the repo already
relies on for sidecar images - and ci/renovate-bump-chart.sh bumps the chart
version with each update.

Consequences, all documented where they bite:
- renovate.json5 deliberately gets no app-version block for this chart. One
  would either duplicate the helm-values updates or rewrite an appVersion that
  selects no image. The absence is commented so it does not read as an
  oversight.
- freesailarr leaves the change-app-version.yml choice list. That workflow
  rewrites appVersion and republishes, which for this chart would change no
  deployed image at all.
- appVersion becomes a hand-maintained marker for the gluetun line. Since
  renovate-bump-chart.sh bumps the patch level on an unchanged appVersion, a
  gluetun update has to be paired with a hand edit of appVersion, or the +up
  suffix stops describing what the chart deploys. Noted in Chart.yaml,
  values.yaml and README.
2026-08-24 15:01:34 +02:00
jklein e4f70bc856 Correct two firewall claims the runtime test disproved
FIREWALL_INPUT_PORTS does not open the app ports inside a pod network. gluetun
detects the pod's own subnet and inserts an INPUT accept for it ahead of the
per-port rules, and every packet arriving at the pod IP matches that rule by
destination. Measured with the packet counters: 50 fresh requests moved the
subnet rule from 306 to 356 packets while every --dport rule stayed at 0 since
container start. A port that listens but is not in the list (gluetun's control
server on 8000) is reachable from another pod all the same.

The variable is kept - it is the explicit statement of which ports are meant to
be reachable, and the subnet detection is gluetun's behaviour rather than a
guarantee of this chart - but the comments no longer claim the traffic would be
dropped without it.

Second correction, same run: the /tmp/gluetun emptyDir fixed the "open ...
permission denied" failure, but gluetun also chowns the file, which needs
CAP_CHOWN. With all capabilities but NET_ADMIN dropped that logs "chown
/tmp/gluetun/ip: operation not permitted" on every VPN restart. The file is
written and the public IP is persisted, so this is a log line rather than a
failure, and granting CHOWN to silence it would widen the container's
capabilities for nothing. Noted where the mount is declared.
2026-08-24 14:38:03 +02:00
jklein bc6577f321 Document what the media subPaths actually buy
The runtime test contradicted the assumption behind the change. Kubernetes
turns every volumeMount into its own bind mount, and link()/rename() across two
mount points fails with EXDEV even when both sides are the same claim on the
same device: in the radarr container /downloads and /movies both report device
65025 and resolve to the same PV directory, yet linking between them fails with
"Cross-device link", while the identical link succeeds when the claim root is
mounted once and the categories are sub-directories under it.

It is not a property of subPath - two plain mounts of the same claim without
any subPath fail the same way, so the chart never supported cross-category
hardlinks and this change does not regress anything. But the comment claimed a
benefit the chart does not deliver, so it now states the measurement instead:
subPath buys the layout (one claim, one sub-directory per category), not
hardlinked imports. Getting those needs a single mount containing all
categories, which is a change to the mount layout rather than a values setting.
2026-08-24 14:08:24 +02:00
jklein 047c5070a0 Merge pull request #150 from jk-cluster/fix/publish-registry-login-hardening
publish: Zugangsdaten aus Step-Outputs lesen und prüfen
2026-08-24 12:06:45 +00:00
jklein 0fb6c30466 publish: read registry credentials from step outputs and validate them
Two defects kept the publish broken after the load-secrets-action v5 bump
in #140.

v5 no longer exports the loaded values as environment variables for later
steps the way v4 did; only the step outputs carry them. Every
"${{ env.JK_CR_* }}" reference therefore resolved to an empty string from
that bump on, and nothing reported it - the loading step logs "Populating
variable" and stays green either way. #149 moved three of the four
references to steps.<id>.outputs but left "-u ${{ env.JK_CR_USERNAME }}"
on the old form, and piped the username instead of the token into
--password-stdin.

The second defect is why this surfaced as a terminal error: an empty value
collapsed into a missing argument instead of failing. With an empty
username, "-u" swallowed the following "--password-stdin", helm stopped
reading the password from stdin, prompted interactively and died with
"inappropriate ioctl for device".

All four values now come from steps.<id>.outputs, are mapped into each
step's own env block and used as quoted shell variables guarded with ":?".
An empty or unset value aborts the job naming the op:// path to fix,
quoting keeps it from shifting arguments, and the token no longer appears
on the command line.
2026-08-24 14:03:09 +02:00
jklein 70c7106fe8 Give the Trivy scan render values for freesailarr
ci/run-trivy-config.sh renders every changed chart before scanning it and exits
non-zero when the render fails - which it does for freesailarr, because the
chart is excluded from the install-test and therefore has no
ci/freesailarr/test-values.yaml to fall back on. The scan job would go red
without ever scanning anything.

The file supplies only the values the chart declares required (secret ref,
cluster issuer, the three media claims and one ingress host per app, since the
chart has no global ingress.domain) and nothing that would alter the security
posture the scan is meant to look at.
2026-08-24 13:51:29 +02:00
jklein 1bf381e4cb Merge pull request #149 from jk-cluster/fix/1Password-action
fix(publish-action): fixed 1Password secret access
2026-08-24 11:48:18 +00:00
jklein f6f04d27fe fix(publish-action): fixed 1Password secret access 2026-08-24 13:47:01 +02:00
jklein cdb66a32f4 Per-app ingress and service, media subPaths
Three review changes to the freesailarr chart.

Ingress: the chart no longer derives hosts as <app>.<ingress.domain> from one
Ingress object. The global ingress.domain is gone; every app carries its own
<app>.ingress.host and renders its own Ingress <release>-<chart>-<app>-ingress
with its own TLS secret tls-<release>-<chart>-<app>-ingress. The hosts are
therefore independent and may live in unrelated domains. The host is required,
but the required() call sits inside the loop over the *enabled* app ingresses,
so switching an app's ingress off must not make the chart demand a host for it.
clusterIssuer, className and annotations stay global defaults and are
overridable per app; an explicitly set per-app value wins (nil check instead of
"default", so an empty override is honoured).

Media: media.<category>.subPath mounts a sub-directory of the claim instead of
its root, default null (claim root = previous behaviour). The same
existingClaim may back all three categories with different subPaths. That is
the point rather than cosmetics: Radarr and Sonarr import by moving, and a move
within one filesystem can be a hardlink instead of a copy - across volume
boundaries every import duplicates the file.

Services: the collective Service with named ports is replaced by one ClusterIP
Service <release>-<chart>-<app>-service per enabled app that listens on a port,
flaresolverr included; recyclarr serves no port in daemon mode and gets none.
Each ingress points at its own app's service. All containers share gluetun's
network namespace, so the services differ only in their port - they provide
stable per-app DNS names, not network separation. The StatefulSet's governing
serviceName no longer names a removed object.
2026-08-24 13:45:38 +02:00
jklein d4f03cdfd9 Give gluetun a writable /tmp/gluetun
gluetun logs "clearing public IP data: open /tmp/gluetun/ip: permission
denied" every few seconds and never persists its public IP. It creates
/tmp/gluetun with mode 0644, i.e. without the execute bit needed to enter
the directory; it runs as root, but with all capabilities except
NET_ADMIN dropped there is no DAC_OVERRIDE left to paper over the missing
bit. An emptyDir provides the path with usable permissions.
2026-08-24 13:41:47 +02:00
jklein adc87c2cc1 Bind the qBittorrent session to the VPN interface
qBittorrent had no network interface configured, so libtorrent listened
and announced on every interface of the shared pod network namespace -
including eth0 (the pod IP) and loopback. gluetun's kill switch drops
exactly those packets, so trackers answered with "skipping tracker
announce (unreachable)" or timed out, no peers were found and torrents
stalled at 0 bytes in stalledDL. Beyond the stall this is a leak: without
the binding qBittorrent also listens on the pod IP, i.e. reachable past
the tunnel.

The setting only exists in the persisted config file, which qBittorrent
creates itself on first start, so an init container seeds it before the
app comes up. The edit is idempotent and preserves existing user
settings: it replaces the keys if present, inserts them into the
[BitTorrent] section if missing, and creates file or section if needed.
The interface is configurable via qbittorrent.networkInterface (default
tun0); emptying it drops the init container.
2026-08-24 13:41:47 +02:00
jklein 5e2d8e4083 Allow writable root filesystem for the linuxserver.io containers
With readOnlyRootFilesystem the s6-overlay init cannot rewrite
/etc/passwd, so the containers silently kept the image's built-in
uid/gid 911 instead of the configured PUID/PGID (verified at runtime) -
breaking ownership on the shared media volumes. Drop the now-unneeded
/run and /tmp emptyDirs; deviation documented in values.yaml.
2026-08-24 13:41:47 +02:00
jklein 265190d175 Calibrate gluetun liveness more tolerant than readiness
Short tunnel re-negotiations recover internally; a kubelet restart only
prolongs the outage while the kill switch already blocks all egress.
Liveness budget 60s, readiness stays at 30s.
2026-08-24 13:41:47 +02:00
jklein 0a5035ae83 Clean stale VPN state synchronously before gluetun starts
Replace the upstream-recommended postStart hook, which runs
concurrently with the entrypoint and loses the race (observed: it
deleted the ip rule the restarted gluetun had just created, leaving the
container in a permanent CrashLoop behind the kill switch). A command
wrapper now removes stale table-51820 rules and the leftover tun
device deterministically before exec'ing /gluetun-entrypoint, covering
container restarts after unclean exits.
2026-08-24 13:41:47 +02:00
jklein 818a71f7b6 Clean up stale ip rules on gluetun container start
An uncleanly exited gluetun leaves its ip rule in the shared pod
network namespace; the restarted container then fails forever with
'adding ip rule 101: file exists' while the kill switch keeps blocking
all egress. Apply the upstream-recommended Kubernetes fix: a postStart
hook dropping table-51820 rules.
2026-08-24 13:41:47 +02:00
jklein 2211226c8e Allow writable root filesystem for flaresolverr
The image copies and patches the chromedriver binary to the hardcoded
path /app/chromedriver at startup (no env override exists) and writes
chromedriver caches under /app, so readOnlyRootFilesystem cannot hold;
deviation documented in values.yaml. Reverts the HOME=/tmp workaround,
which only covered the cache path, not the binary patching.
2026-08-24 13:41:46 +02:00
jklein bce2b147a8 Set HOME=/tmp for flaresolverr
The image's home is /app on the read-only root filesystem, but
flaresolverr writes chromedriver caches under $HOME (/app/.local);
pointing HOME at the /tmp emptyDir keeps the root filesystem read-only.
2026-08-24 13:41:46 +02:00
jklein 1ed70e31dd Pin flaresolverr runAsUser/runAsGroup to 1000
The image declares its non-root user by name only, which the kubelet's
runAsNonRoot check cannot verify (CreateContainerConfigError); pinning
the uid/gid numerically satisfies the check.
2026-08-24 13:41:46 +02:00
jklein d1933e0b53 Fix gluetun image tag: Docker Hub tags carry a v prefix
The issue plan listed qmcgaw/gluetun:3.41.1, but the published tag is
v3.41.1. appVersion is the exact image tag per the style guide, so
appVersion becomes v3.41.1 and the chart version 1.0.0+upv3.41.1
(precedent: matrix-synapse, satisfactory, calibre-web-automated).
2026-08-24 13:41:46 +02:00
jklein 914c2d0205 Add freesailarr chart: gluetun-tunneled media automation stack
Single-pod StatefulSet running gluetun (appVersion 3.41.1) as Wireguard
VPN gateway with qbittorrent, prowlarr, flaresolverr, radarr, recyclarr,
sonarr, seerr and bazarr sharing its network namespace. Config volumes
via volumeClaimTemplates, media libraries as existing claims, per-app
enable/ingress toggles, computed FIREWALL_INPUT_PORTS, hardened
security contexts with documented per-image deviations. Wires up
publish workflow, change-app-version choice, install-test exclusion
and README overview.
2026-08-24 13:41:46 +02:00
jklein 178d4e9a6a Merge pull request #148 from jk-cluster/feat/seccomp-runtime-default-batch1
Acht Charts: seccompProfile RuntimeDefault im Pod-Context und qualifizierte Images
2026-08-24 11:31:33 +00:00
jklein 6c73ef874b Eight charts: seccompProfile RuntimeDefault and qualified images
Sets seccompProfile.type: RuntimeDefault in the pod security context of
minecraft-router, outline, urlaubsverwaltung, zipline, voucher-vault,
satisfactory, patchmon and calibre-web-automated, so the container
runtime's default syscall filter applies to every container in the pod.

The value lives in the pod context rather than the container context
because it covers init containers and any container added later without
being repeated; a container that needs a different filter can still
override it under securityContext.container, which wins over the pod
value.

Five of the eight also get the docker.io/ prefix on their previously
unqualified image references, so the pull no longer depends on the
runtime's default-registry setting: itzg/mc-router,
outlinewiki/outline, urlaubsverwaltung/urlaubsverwaltung,
l4rm4nd/vouchervault (twice, app and init container) and
guacamole/guacd:1.6.0. zipline, satisfactory and calibre-web-automated
were already qualified with ghcr.io.

Verified in a throwaway k3d cluster against real application work, not
just a Running pod, and with Seccomp: 2 (filter mode) read from
/proc/<pid>/status inside each container:

- minecraft-router: a Minecraft handshake for a mapped domain travels
  through the router to a backend and the backend's status payload comes
  back to the client.
- outline: all migrations apply (45 tables), OIDC discovery succeeds and
  the server-rendered page answers 200 with <title>Outline</title>.
- urlaubsverwaltung: Liquibase applies 79 changesets (35 tables), the app
  seeds its settings and vacation types, /login answers 200.
- zipline: 27 migrations applied, then a full round trip - first user
  created, login, token, upload, and the file downloaded back byte-exact
  from the PVC.
- voucher-vault: the init container's collectstatic output is served
  (200, 22 KB), Django has its 22 tables and uWSGI answers 200 with
  <title>VoucherVault</title>.
- satisfactory: application behavior is NOT proven - the image is
  multiple GB and pulls 10 GB+ of game files on first start, which is why
  the chart sits in ci/excluded-charts.txt. What is proven is that the
  rendered pod securityContext is accepted server-side and yields
  Seccomp: 2 with uid/gid 1000 on a substitute image.
- patchmon: both containers run under the pod profile and both report
  Seccomp: 2. The server applies 47 migrations (44 tables), connects to
  Postgres and Redis and serves its dashboard; guacd answers a real
  Guacamole "select rdp" instruction with its full args list, over the
  same 127.0.0.1:4822 path the chart wires up.
- calibre-web-automated: the readOnlyRootFilesystem: false deviation is
  unaffected - id abc is still uid 1000 (not the 911 the image falls back
  to in read-only mode), /config and the Calibre metadata.db are owned by
  1000, and both the root s6 init and the uid-1000 application report
  Seccomp: 2. calibredb 9.1 runs and adds a book. Its IPC worker prints a
  traceback while doing so, but an unconfined control pod on the same
  image prints the same class of traceback, so it is not the filter.

trivy config now reports "0 to act on" for all eight; the untouched
samba still shows KSV-0104 and KSV-0030, which confirms these charts are
what closed the findings.

No values.schema.json needed changing: in all eight schemas
securityContext.pod is type ["object", "null"] with no property
restrictions, so the new key validates as it stands. The #99 null paths
still hold - nulling securityContext or securityContext.pod falls back to
the defaults including the new one.

Renovate resolves the prefixed guacd reference: a local dry-run lookup
(renovate 44.40.1, --platform=local --dry-run=lookup) reports depName
docker.io/guacamole/guacd against registryUrl https://index.docker.io
with lookupName guacamole/guacd, currentVersion 1.6.0 and its real
release timestamp, with no warnings. The other four prefixed references
live in templates, which Renovate never reads - they are tracked by the
customManagers via appVersion in Chart.yaml with a depName hardcoded in
renovate.json5, so they are unaffected.

Major bump for all eight: runtime behavior changes.

Refs #84
2026-08-24 13:17:14 +02:00
jklein d85ff72dad Merge pull request #147 from jk-cluster/feat/renovate-nginx-84-wording
renovate: name the nginx alpine check in #84 accurately
2026-08-24 10:52:35 +00:00
jklein 640c6e5726 Merge pull request #138 from jk-cluster/renovate/alpine-3.x
Update alpine Docker tag to v3.24
2026-08-24 10:52:12 +00:00
jklein 0d62c0d6ce renovate: name the nginx alpine check in #84 accurately
The rationale called the alpine switch a proposal from the issue text.
A later comment on #84 commissions it as a check with a defined scope
(nginx.conf compatibility, port 8080, uid 101, /tmp redirection) to be
done alongside the seccompProfile PR for this chart. The outcome is open,
the check is not - the constraint has to survive either way.
2026-08-24 12:18:29 +02:00
jlab-renovate[bot] 5a35ea49b0 Update alpine Docker tag to v3.24 2026-08-24 10:16:01 +00:00
jklein 077030e61f Merge pull request #146 from jk-cluster/feat/renovate-nginx-stable-only
renovate: keep the delegation nginx on stable branches
2026-08-24 10:15:19 +00:00
jklein 2d03ba8a7c renovate: let the nginx constraint survive a variant tag
The anchored form rejected any tag with a suffix. Docker versioning only
offers candidates carrying the current tag's suffix, so the day the
delegation sidecar moves to a slimmer base image (#84, still open) every
candidate would fail the filter and nginx would stop receiving updates -
without an error, a warning, or a red dashboard.

Verified against currentValue 1.28.0-alpine: anchored form returns
"updates": [] and "warnings": [], the suffix-tolerant form proposes
1.30.4-alpine and still refuses the newer 1.31.x mainline.
2026-08-24 12:15:01 +02:00
jklein 0908785b10 renovate: keep the delegation nginx on stable branches
nginx numbers its release branches by minor: even is stable, odd is
mainline. PR #139 proposed 1.30.4 -> 1.31.4, which would have moved the
Synapse delegation sidecar onto mainline without that ever being a
decision. allowedVersions now permits even minors only; patch updates
inside the stable branch still come through and still automerge.
2026-08-24 12:09:26 +02:00
jklein 372b5f691c Merge pull request #145 from jk-cluster/feat/renovate-automerge-minor
renovate: automerge minor updates, make the install-test blocking
2026-08-24 10:03:47 +00:00
jklein 2e0e37d838 renovate: automerge minor updates, make the install-test blocking
The two open Renovate PRs (#138 alpine 3.22 -> 3.24, #139 nginx 1.30.4 ->
1.31.4) never automerged because both are minor updates while the rule
only covered patch and digest. Sidecar images barely move in patch steps,
so patch-only would practically never fire.

Automerging a chart update publishes it, so the install-test can no
longer report failures without blocking: continue-on-error is gone and
issue #22's stabilization phase ends here. Majors stay manual.
2026-08-24 12:01:08 +02:00
jklein 35db2affea Merge pull request #143 from jk-cluster/feat/renovate-config
renovate: chart-aware config with an automatic version bump
2026-08-24 09:47:54 +00:00
jklein 6b25d082a7 renovate: chart-aware config with an automatic version bump
Renovate only ever rewrites appVersion or an image tag, so every PR it
opened against a chart directory failed the build-chart version check
(#138, #139). ci/renovate-bump-chart.sh recomputes
version: <semver>+up<appVersion> as a postUpgradeTasks hook: chart major
when the app major moves, chart patch otherwise.

Each chart renders its main image as <repo>:{{ .Chart.AppVersion }}, so
repository and version live in different files and Renovate cannot join
them. The mapping is one customManager per chart in renovate.json5,
deliberately not a comment inside each Chart.yaml - that would touch all
20 chart directories and force 20 publishes for a comment change.

Patch and digest updates automerge after a 3-day minimumReleaseAge once
the pipeline is green; majors arrive one line at a time to match the
stepwise app-major procedure. ci/** stays excluded: the install-test
fixtures pin throwaway infrastructure to major lines on purpose.
2026-08-24 11:42:43 +02:00
jklein e730a81b17 Merge pull request #140 from jk-cluster/renovate/1password-load-secrets-action-5.x
Update 1password/load-secrets-action action to v5
2026-08-24 09:20:12 +00:00
jklein a176c7576c Merge pull request #142 from jk-cluster/renovate/azure-setup-helm-5.x
Update azure/setup-helm action to v5
2026-08-24 09:20:01 +00:00