Commit Graph
3 Commits
Author SHA1 Message Date
jklein 78d7020dab freesailarr: MediathekArr as the tenth container, off by default (3.1.0+up1.0.0)
MediathekArr fakes a Usenet indexer towards Prowlarr and a SABnzbd downloader
towards Sonarr/Radarr and fetches from the ARD and ZDF media libraries. Both
consumers already share gluetun's network namespace, so they reach it over
127.0.0.1 - no service, no DNS, no ingress detour. Purely additive: the flag
defaults to false and a default render is byte-identical to 3.0.5 (verified
against the previous chart for both the default values and the CI values).

The four things that fail silently without them:

- A START GATE on gluetun. MediathekArr fetches its ruleset once at start and
  never retries; started before the tunnel it answers every Newznab search with
  0 results while the container is up, Ready and quiet. Kubernetes cannot order
  two regular containers and an initContainer would deadlock, so the wait sits
  in front of the image entrypoint: poll gluetun's health server on
  127.0.0.1:9999 and exec the entrypoint afterwards; on timeout the container
  exits rather than starting blind, because a crash loop is visible and an empty
  indexer is not. The startup probe budget (300s) deliberately outlasts the gate
  (180s).
- incomplete/ AND complete/ ON THE SHARED DOWNLOADS CLAIM, not in an emptyDir.
  An emptyDir counts against the container's ephemeral-storage limit, and one
  large download would have the kubelet evict the whole pod with all ten
  containers - the trap ytdlp-web-player documents.
- THE COMPLETE PATH SONARR SEES. The claim is mounted at /downloads exactly as
  sonarr/radarr mount it, and the chart sets DOWNLOAD_COMPLETE_PATH itself, so
  the SABnzbd complete_dir it reports is the same absolute path on both sides.
- TWO PORTS: 5007 (SABnzbd role and UI, primary) and 5008 (Newznab role for
  Prowlarr), both declared to gluetun's firewall and both on one Service. The
  SABnzbd API is at /download/api - /api on 5007 answers with a misleading
  Newznab validation error.

Further details:

- Image pinned by digest (tag + digest): "latest" is a 2024 build, "beta"
  moves. Single-arch linux/amd64. The built-in helm-values manager cannot
  follow that pair, so renovate.json5 gets the chart's first customManagers
  block; verified by dry-run with two fixtures, since a matching and a broken
  rule look identical while the pin is current.
- Hardened as measured: non-root, uid/gid from puid/pgid,
  readOnlyRootFilesystem: true, all capabilities dropped, no PUID/PGID.
- Probes on measured 200 endpoints (there is no health endpoint): readiness and
  startup on /download/, liveness as an exec covering BOTH ports, because one
  container runs two processes and a wedged Newznab half is exactly what makes
  searches come back empty.
- No ingress by default: the UI links its assets with root-absolute paths, so it
  can only be served at the root of a host; on the shared domain that root
  belongs to seerr. A sub-path fails the render instead of producing a broken
  UI.
- values.yaml and NOTES.txt document the geo MARKER PROCEDURE (not a table of
  series), the escape route of a German exit and its price for the whole stack,
  the fail_message: null trap, and the HLS question as an open question.
- CI: mediathekarr enabled in ci/freesailarr/test-values.yaml, install timeout
  raised to 1200s for the extra image and the gate.

Fixes #245
2026-09-08 23:20:16 +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 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