Commit Graph
11 Commits
Author SHA1 Message Date
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 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 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 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 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 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 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