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
This commit is contained in:
jklein committed 2026-09-08 23:20:16 +02:00
1 parent f49bfa4af8
commit 78d7020dab
11 files changed
+717 -49

No files matched your search

+91
View File
@@ -535,6 +535,76 @@
"$ref": "#/definitions/containerSecurityContext"
}
}
},
"mediathekarr": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"image": {
"$ref": "#/definitions/pinnedImage"
},
"ingress": {
"$ref": "#/definitions/appIngress"
},
"storage": {
"$ref": "#/definitions/appStorage"
},
"startGate": {
"description": "Delays the container until gluetun reports healthy. NOT null-safe on purpose and not merged through the fallback helper: this block decides whether the gate is rendered at all, and an erased block would silently produce a container that starts before the tunnel - the failure this chart writes three paragraphs about, and one that looks green.",
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"intervalSeconds": {
"type": "integer",
"minimum": 1
},
"timeoutSeconds": {
"description": "Total wait before the container exits instead of starting without a tunnel. Keep the startup probe's budget (periodSeconds x failureThreshold) above it, or the kubelet kills the container while the gate is still waiting.",
"type": "integer",
"minimum": 1
},
"settleSeconds": {
"type": "integer",
"minimum": 0
}
}
},
"downloads": {
"type": "object",
"additionalProperties": false,
"properties": {
"directory": {
"description": "Sub-directory on the shared downloads claim holding incomplete/ and complete/. The chart derives DOWNLOAD_INCOMPLETE_PATH and DOWNLOAD_COMPLETE_PATH from it; complete/ is what Sonarr/Radarr are told to import from, under the identical absolute path.",
"type": "string",
"minLength": 1
}
}
},
"env": {
"$ref": "#/definitions/env"
},
"livenessProbe": {
"$ref": "#/definitions/probe"
},
"readinessProbe": {
"$ref": "#/definitions/probe"
},
"startupProbe": {
"$ref": "#/definitions/probe"
},
"resources": {
"$ref": "#/definitions/resources"
},
"securityContext": {
"$ref": "#/definitions/containerSecurityContext"
}
}
}
},
"definitions": {
@@ -746,6 +816,27 @@
"object",
"null"
]
},
"pinnedImage": {
"description": "Like image, plus the digest mediathekarr is pinned by: its tags are unusable (\"latest\" is a 2024 build, \"beta\" moves), so the tag stays as the readable label and the digest is what is actually pulled. Deliberately a separate definition instead of adding an optional digest to image: on every other image in this chart a digest key would be a value the templates never read - exactly the silent no-op the schema exists to reject (#68).",
"type": "object",
"additionalProperties": false,
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": [
"string",
"number"
]
},
"digest": {
"description": "Image digest, sha256:<64 hex>. Required - the template refuses to render without it.",
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$"
}
}
}
}
}