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:
11 files changed
+717
-49
No files matched your search
@@ -11,7 +11,7 @@
|
||||
{{- $sharedNames := list -}}
|
||||
{{- range $shared }}{{ $sharedNames = append $sharedNames .name }}{{ end -}}
|
||||
freesailarr — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
Nine containers in ONE pod, sharing gluetun's network namespace.
|
||||
{{ if .Values.mediathekarr.enabled }}Ten{{ else }}Nine{{ end }} containers in ONE pod, sharing gluetun's network namespace.
|
||||
|
||||
NOTHING IS READY UNTIL THE TUNNEL IS UP
|
||||
gluetun's readiness probe is its health server, which answers 200 only while
|
||||
@@ -134,6 +134,101 @@ QBITTORRENT HAS NO SAVE PATH SET (qbittorrent.savePath is empty)
|
||||
WebUI before starting any download.
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.mediathekarr.enabled }}
|
||||
{{- $dir := .Values.mediathekarr.downloads.directory }}
|
||||
{{- $gate := .Values.mediathekarr.startGate }}
|
||||
{{- $mediaStartup := fromYaml (include "freesailarr.probeFor" (dict "root" $ "app" "mediathekarr" "kind" "startupProbe" "given" .Values.mediathekarr.startupProbe)) }}
|
||||
|
||||
MEDIATHEKARR: TWO PORTS, AND THE SABNZBD API IS NOT AT /api
|
||||
Every container shares one network namespace, so both consumers reach it over
|
||||
127.0.0.1 — no service name, no DNS, no ingress:
|
||||
|
||||
Prowlarr -> Newznab indexer, http://127.0.0.1:5008
|
||||
Sonarr/Radarr -> SABnzbd host 127.0.0.1, port 5007, API path /download/api
|
||||
|
||||
A request to /api on 5007 answers with a Newznab validation error and sends
|
||||
you looking at the wrong half of the app — the upstream documentation only
|
||||
mentions port 5007 at all.
|
||||
|
||||
Sonarr and Radarr must import from
|
||||
|
||||
/downloads/{{ $dir }}/complete (one sub-directory per category below it)
|
||||
|
||||
which is exactly the path the chart hands MediathekArr and which it reports
|
||||
back as the SABnzbd complete_dir. Any deviation — a remote path mapping, a
|
||||
different mount — imports NOTHING, without an error.
|
||||
|
||||
The UI (setup wizard and download queue) has no ingress{{ if and .Values.mediathekarr.ingress.enabled .Values.mediathekarr.ingress.host }}, other than the one
|
||||
you gave it on {{ .Values.mediathekarr.ingress.host }}{{ end }}:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ $name }}-mediathekarr-service 5007:5007
|
||||
{{- if $gate.enabled }}
|
||||
|
||||
IT DELIBERATELY STARTS LATE, AND THAT IS THE POINT
|
||||
The container waits (up to {{ $gate.timeoutSeconds }}s) for gluetun to report healthy before
|
||||
it starts MediathekArr{{ if gt (int $gate.settleSeconds) 0 }}, plus {{ $gate.settleSeconds }}s of settling time{{ end }}. Started before the tunnel,
|
||||
MediathekArr fails its one ruleset fetch, never retries it, and then answers
|
||||
EVERY search with 0 results — container up, Ready, nothing in the log. That
|
||||
is why a green pod proves nothing here and why the gate exits instead of
|
||||
starting anyway on timeout: a crash-looping container is visible, a silently
|
||||
empty indexer is not.
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} logs {{ $name }}-sfs-0 -c mediathekarr | head -3
|
||||
|
||||
The startup probe has to outlast the gate ({{ mul (int $mediaStartup.periodSeconds) (int $mediaStartup.failureThreshold) }}s budget against a {{ $gate.timeoutSeconds }}s gate),
|
||||
otherwise the kubelet kills the container while it is still waiting.
|
||||
{{- else }}
|
||||
|
||||
THE START GATE IS OFF (mediathekarr.startGate.enabled: false)
|
||||
MediathekArr may then start before the Wireguard tunnel is up. Its ruleset
|
||||
fetch fails once, is never retried, and the indexer answers every search
|
||||
with 0 results afterwards — measured over eleven minutes, with the container
|
||||
up, Ready and no error anywhere. Nothing reports this state. Check a search
|
||||
by hand after every restart, or switch the gate back on.
|
||||
{{- end }}
|
||||
|
||||
A FAILED MEDIATHEKARR DOWNLOAD NEVER SAYS WHY
|
||||
MediathekArr does not check the HTTP status code. On a geo-blocked fetch it
|
||||
writes the ~476-byte HTML error page out as .mp4 and logs "Download
|
||||
completed"; only mkvmerge then refuses the file and the status flips to
|
||||
Failed — with fail_message: null. Measured A/B with one identical NZB:
|
||||
direct 94,235,525 bytes and Completed, through a foreign exit 0 bytes and
|
||||
Failed, fail_message null in BOTH cases.
|
||||
|
||||
So a geo block is indistinguishable from a broken server, an expired URL or a
|
||||
full disk, in Sonarr and in the container log alike. And the protection is an
|
||||
accident: it rests entirely on mkvmerge rejecting an HTML page. Anything a
|
||||
geo endpoint returns that mkvmerge accepts is reported as Completed and walks
|
||||
into your library.
|
||||
|
||||
WHETHER A SHOW WORKS THROUGH YOUR EXIT: READ THE MARKER, NOT A LIST
|
||||
The tunnel currently exits {{ with .Values.gluetun.vpn.serverCities }}in {{ . }}{{ else }}{{ with .Values.gluetun.vpn.serverCountries }}in {{ . }}{{ else }}wherever gluetun picks{{ end }}{{ end }}. ARD and ZDF block parts of their
|
||||
catalogue outside Germany, and every container hangs in gluetun's namespace —
|
||||
there is no per-container exit.
|
||||
|
||||
A table of series goes stale; the marker in the media URL does not. It
|
||||
predicted the outcome in 584 of 584 measured cases:
|
||||
|
||||
/de/, /geo/de/, /deutschlandweit/, geoavailability_D -> blocked outside DE
|
||||
/weltweit/ -> open
|
||||
/none/ (ZDF) -> open
|
||||
/dach/, deChAt (WDR variant) -> open
|
||||
|
||||
The rule of thumb "own production = available worldwide" is MEASURABLY WRONG
|
||||
(an ARD own production was blocked, a licensed series was open). Only the
|
||||
marker counts.
|
||||
|
||||
The escape route is one value — gluetun.vpn.serverCountries/serverCities set
|
||||
to a German exit — and its price is that it moves the WHOLE stack, all ten
|
||||
containers, qBittorrent included, into that jurisdiction and onto those
|
||||
servers. That is a deployment decision, not a chart setting to flip lightly.
|
||||
|
||||
OPEN, EXPLICITLY UNMEASURED: HLS ENTRIES
|
||||
Part of the catalogue is served as an HLS playlist instead of a progressive
|
||||
MP4. The image carries an ffmpeg-based M3U8 path, but whether such an entry
|
||||
completes end to end has not been measured. If it does not, those entries fail
|
||||
regardless of any geo block — and, per the paragraph above, without a reason.
|
||||
{{- end }}
|
||||
|
||||
EVERY SERVICE POINTS AT THE SAME POD IP
|
||||
One ClusterIP service per app{{ if .Values.recyclarr.enabled }} (recyclarr has none — no port in daemon mode){{ end }}, but all
|
||||
|
||||
@@ -135,7 +135,7 @@ implementation lives in template-chart.
|
||||
{{/*
|
||||
Render additive env entries (the repo-wide <app>.env convention: tpl-rendered,
|
||||
supporting the value, secretKeyRef and configMapKeyRef forms). Shared as a
|
||||
helper here because this chart has nine containers, each with its own env block.
|
||||
helper here because this chart has ten containers, each with its own env block.
|
||||
|
||||
Usage:
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.<app>.env) | nindent 12 }}
|
||||
@@ -176,6 +176,14 @@ recyclarr is deliberately absent: in daemon mode it serves no port, so it gets
|
||||
no container port, no service and no ingress. flaresolverr is present - it
|
||||
listens on 8191 and therefore gets a service - but it has no ingress block in
|
||||
values and is never exposed from outside the cluster.
|
||||
|
||||
"port" is the app's PRIMARY port: the one its service is named after and the one
|
||||
an ingress points at. mediathekarr is the only app with a second one, because it
|
||||
runs two processes in one container - the SABnzbd role on 5007 (the UI, hence
|
||||
the primary) and the Newznab role Prowlarr consumes on 5008. Optional
|
||||
"extraPorts" entries ({name, port}) are additional ports of the SAME service and
|
||||
are declared to gluetun's firewall alongside the primary; they never take part
|
||||
in the ingress, which addresses a service port by name.
|
||||
*/}}
|
||||
{{- define "freesailarr.portApps" -}}
|
||||
{{- $apps := list -}}
|
||||
@@ -186,7 +194,8 @@ values and is never exposed from outside the cluster.
|
||||
(dict "name" "radarr" "port" 7878)
|
||||
(dict "name" "sonarr" "port" 8989)
|
||||
(dict "name" "seerr" "port" 5055)
|
||||
(dict "name" "bazarr" "port" 6767) -}}
|
||||
(dict "name" "bazarr" "port" 6767)
|
||||
(dict "name" "mediathekarr" "port" 5007 "extraPorts" (list (dict "name" "ma-newznab" "port" 5008))) -}}
|
||||
{{- if (index $.Values $app.name).enabled -}}
|
||||
{{- $apps = append $apps $app -}}
|
||||
{{- end -}}
|
||||
@@ -214,8 +223,9 @@ ingress block with enabled: true. Used by the ingress template.
|
||||
{{/*
|
||||
The sub-path an app is served under in the shared-domain mode, e.g. "/sonarr".
|
||||
Null-safe by construction: an absent or erased <app>.ingress.path falls back to
|
||||
"/<app>", and to "/" for seerr, which has no base-path support at all and can
|
||||
therefore only live at the root.
|
||||
"/<app>", and to "/" for the two apps that can only live at the root - seerr,
|
||||
which has no base-path support at all, and mediathekarr, whose UI links its
|
||||
assets with root-absolute paths (see the guard in sharedIngressApps).
|
||||
|
||||
Input: dict "root" $ "app" <app name>.
|
||||
*/}}
|
||||
@@ -226,7 +236,7 @@ Input: dict "root" $ "app" <app name>.
|
||||
{{- $path = $config.path -}}
|
||||
{{- end -}}
|
||||
{{- if or (kindIs "invalid" $path) (eq (toString $path) "") -}}
|
||||
{{- if eq .app "seerr" -}}
|
||||
{{- if has .app (list "seerr" "mediathekarr") -}}
|
||||
{{- $path = "/" -}}
|
||||
{{- else -}}
|
||||
{{- $path = printf "/%s" .app -}}
|
||||
@@ -250,9 +260,12 @@ Two guards live here rather than in the ingress template, so that every
|
||||
consumer - the ingress objects, the UrlBase env vars and NOTES.txt - sees the
|
||||
same validated list:
|
||||
|
||||
1. seerr under any path other than "/". seerr has no base-path support
|
||||
(upstream request open since May 2022), so a chart that rendered it under
|
||||
a sub-path would produce something that provably cannot work.
|
||||
1. seerr or mediathekarr under any path other than "/". seerr has no base-path
|
||||
support (upstream request open since May 2022) and MediathekArr's UI links
|
||||
its assets with root-absolute paths, so a chart that rendered either under
|
||||
a sub-path would produce something that provably cannot work. Both at "/"
|
||||
on one host is caught by guard 2 below - one of them then needs a host of
|
||||
its own.
|
||||
2. Two apps claiming the same path on the shared host. Only paths on the
|
||||
shared host can collide; apps with their own host are on a different host
|
||||
and are therefore not part of this check.
|
||||
@@ -269,11 +282,14 @@ same validated list:
|
||||
{{- $config := (index $root.Values $app.name).ingress -}}
|
||||
{{- if not ($config.host | default "") -}}
|
||||
{{- $path := include "freesailarr.appPath" (dict "root" $root "app" $app.name) -}}
|
||||
{{- if and (eq $app.name "seerr") (ne $path "/") -}}
|
||||
{{- fail (printf "seerr.ingress.path is %q, but seerr has no base-path support and can only be served at \"/\" (https://github.com/Fallenbagel/jellyseerr/issues/97). Either leave seerr.ingress.path at \"/\" or give seerr its own seerr.ingress.host." $path) -}}
|
||||
{{- $rootOnly := dict
|
||||
"seerr" "seerr has no base-path support (https://github.com/Fallenbagel/jellyseerr/issues/97)"
|
||||
"mediathekarr" "MediathekArr links its UI assets with root-absolute paths (/download/css/..., measured), so a sub-path stays broken even behind a prefix-stripping rewrite - the browser then asks for those assets at the host root" -}}
|
||||
{{- if and (hasKey $rootOnly $app.name) (ne $path "/") -}}
|
||||
{{- fail (printf "%s.ingress.path is %q, but %s and it can therefore only be served at \"/\". Either leave %s.ingress.path at \"/\" or give it its own %s.ingress.host." $app.name $path (index $rootOnly $app.name) $app.name $app.name) -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey $seen $path -}}
|
||||
{{- fail (printf "%s.ingress.path and %s.ingress.path are both %q on the shared host %q - every app under ingress.host needs its own path" (index $seen $path) $app.name $path $host) -}}
|
||||
{{- fail (printf "%s.ingress.path and %s.ingress.path are both %q on the shared host %q - every app under ingress.host needs its own path. Where neither can move (seerr and mediathekarr can only be served at \"/\"), give one of them its own <app>.ingress.host." (index $seen $path) $app.name $path $host) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $seen $path $app.name -}}
|
||||
{{- $apps = append $apps (dict "name" $app.name "port" $app.port "path" $path) -}}
|
||||
@@ -356,6 +372,9 @@ alive. It is not what opens the ports today.
|
||||
{{- $ports := list -}}
|
||||
{{- range $app := include "freesailarr.portApps" . | fromJsonArray -}}
|
||||
{{- $ports = append $ports (int $app.port) -}}
|
||||
{{- range $extra := $app.extraPorts | default list -}}
|
||||
{{- $ports = append $ports (int $extra.port) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $ports = append $ports 9999 -}}
|
||||
{{- join "," $ports -}}
|
||||
@@ -366,7 +385,7 @@ Whether a media category is mounted at all, i.e. whether at least one app that
|
||||
mounts it is enabled. "true" when it is, the empty string when it is not (an
|
||||
empty string is falsy in a template "if", so the result can be used directly).
|
||||
|
||||
downloads: qbittorrent, radarr, sonarr
|
||||
downloads: qbittorrent, radarr, sonarr, mediathekarr
|
||||
movies: radarr, bazarr
|
||||
tv: sonarr, bazarr
|
||||
|
||||
@@ -375,7 +394,7 @@ Input: dict "root" $ "category" <"downloads"|"movies"|"tv">.
|
||||
{{- define "freesailarr.mediaCategoryEnabled" -}}
|
||||
{{- $v := .root.Values -}}
|
||||
{{- if eq .category "downloads" -}}
|
||||
{{- if or $v.qbittorrent.enabled $v.radarr.enabled $v.sonarr.enabled -}}true{{- end -}}
|
||||
{{- if or $v.qbittorrent.enabled $v.radarr.enabled $v.sonarr.enabled $v.mediathekarr.enabled -}}true{{- end -}}
|
||||
{{- else if eq .category "movies" -}}
|
||||
{{- if or $v.radarr.enabled $v.bazarr.enabled -}}true{{- end -}}
|
||||
{{- else if eq .category "tv" -}}
|
||||
@@ -885,6 +904,47 @@ bazarr:
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
mediathekarr:
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- >-
|
||||
wget -q -T 5 -O /dev/null "http://127.0.0.1:5008/api?t=caps" &&
|
||||
wget -q -T 5 -O /dev/null "http://127.0.0.1:5007/download/api?mode=version&output=json&apikey=x"
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 15
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /download/
|
||||
port: 5007
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /download/
|
||||
port: 5007
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 60
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
configInit:
|
||||
resources:
|
||||
limitFactor: 3
|
||||
@@ -905,7 +965,7 @@ configInit:
|
||||
|
||||
{{/*
|
||||
The effective pod security context. Separate from the per-container table
|
||||
because it is the one block shared by all nine containers.
|
||||
because it is the one block shared by all containers of the pod.
|
||||
*/}}
|
||||
{{- define "freesailarr.podSecurityContext" -}}
|
||||
{{- $given := (fromYaml (include "freesailarr.subBlock" (dict "block" .Values.securityContext "key" "pod" "path" "securityContext"))).value -}}
|
||||
|
||||
@@ -21,5 +21,16 @@ spec:
|
||||
port: {{ int $app.port }}
|
||||
targetPort: {{ $app.name }}
|
||||
name: {{ $app.name }}
|
||||
{{- /* An app with more than one port keeps them in ONE service rather than
|
||||
getting a second object: they belong to the same container and the
|
||||
same pod IP, and only the primary port is ever addressed by an
|
||||
ingress. mediathekarr is the only such app today - 5007 (SABnzbd
|
||||
role, primary) and 5008 (Newznab role). */}}
|
||||
{{- range $extra := $app.extraPorts | default list }}
|
||||
- protocol: TCP
|
||||
port: {{ int $extra.port }}
|
||||
targetPort: {{ $extra.name }}
|
||||
name: {{ $extra.name }}
|
||||
{{- end }}
|
||||
type: ClusterIP
|
||||
{{- end }}
|
||||
@@ -521,6 +521,109 @@ spec:
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.mediathekarr.enabled }}
|
||||
# MediathekArr, the tenth container and the only one off by default: a fake
|
||||
# Usenet indexer for Prowlarr (Newznab, port 5008) and a fake SABnzbd
|
||||
# for Sonarr/Radarr (port 5007), fetching from the ARD and ZDF media
|
||||
# libraries. Both consumers sit in this pod and reach it over
|
||||
# 127.0.0.1, so it needs neither a service nor DNS to do its job.
|
||||
- name: mediathekarr
|
||||
# Tag AND digest: the tag stays readable, the digest is what is
|
||||
# actually pulled. "beta" moves, and "latest" is a 2024 build - see
|
||||
# the values comment.
|
||||
image: "{{ .Values.mediathekarr.image.repository }}:{{ .Values.mediathekarr.image.tag }}@{{ required "mediathekarr.image.digest is required - the tag alone is a moving reference" .Values.mediathekarr.image.digest }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
{{- if .Values.mediathekarr.startGate.enabled }}
|
||||
# THE START GATE. Kubernetes cannot order two regular containers, and
|
||||
# an initContainer waiting for gluetun would deadlock (gluetun only
|
||||
# starts once the init containers are done). So the wait happens here,
|
||||
# in front of the image entrypoint: poll gluetun's health server -
|
||||
# 200 only while the tunnel actually works - and exec the entrypoint
|
||||
# afterwards.
|
||||
#
|
||||
# Why this is not optional decoration: MediathekArr fetches its
|
||||
# ruleset once at start and never retries. Started before the tunnel,
|
||||
# it answered every Newznab search with 0 results for over eleven
|
||||
# minutes - container up, Ready, nothing in the log. The failure case
|
||||
# looks exactly like the healthy one, which is why the gate exits
|
||||
# instead of starting anyway when the timeout passes: a crash-looping
|
||||
# container is visible, a silently empty indexer is not.
|
||||
#
|
||||
# wget comes with the (Debian) image, bash is its entrypoint's own
|
||||
# interpreter - no extra tooling and no shell built into the chart.
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
deadline=$(( SECONDS + {{ int .Values.mediathekarr.startGate.timeoutSeconds }} ))
|
||||
echo "mediathekarr: waiting for gluetun to report healthy (127.0.0.1:9999)"
|
||||
until wget -q -T 5 -O /dev/null http://127.0.0.1:9999/; do
|
||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||
echo "mediathekarr: gluetun did not report healthy within {{ int .Values.mediathekarr.startGate.timeoutSeconds }}s - exiting instead of starting without a tunnel, because a MediathekArr started too early answers every search with 0 results and reports nothing" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep {{ int .Values.mediathekarr.startGate.intervalSeconds }}
|
||||
done
|
||||
echo "mediathekarr: gluetun healthy after ${SECONDS}s"
|
||||
{{- if gt (int .Values.mediathekarr.startGate.settleSeconds) 0 }}
|
||||
sleep {{ int .Values.mediathekarr.startGate.settleSeconds }}
|
||||
{{- end }}
|
||||
exec /app/docker_start.sh
|
||||
{{- end }}
|
||||
env:
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- /* Both download paths are set by the chart, not left to the
|
||||
image: complete/ has to be visible to Sonarr/Radarr under
|
||||
the SAME absolute path (they are handed this value as the
|
||||
SABnzbd complete_dir, and a deviation imports nothing
|
||||
without an error), and incomplete/ has to live on the media
|
||||
claim rather than in an emptyDir, which would count against
|
||||
the container's ephemeral-storage limit and let one large
|
||||
download evict the whole pod. Setting them also keeps the
|
||||
image's built-in /data/mediathek default out of play, which
|
||||
the read-only root filesystem does not provide. */}}
|
||||
- name: DOWNLOAD_INCOMPLETE_PATH
|
||||
value: {{ printf "/downloads/%s/incomplete" .Values.mediathekarr.downloads.directory | quote }}
|
||||
- name: DOWNLOAD_COMPLETE_PATH
|
||||
value: {{ printf "/downloads/%s/complete" .Values.mediathekarr.downloads.directory | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.mediathekarr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 5007
|
||||
protocol: TCP
|
||||
name: mediathekarr
|
||||
- containerPort: 5008
|
||||
protocol: TCP
|
||||
name: ma-newznab
|
||||
livenessProbe:
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "mediathekarr" "kind" "livenessProbe" "given" .Values.mediathekarr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "mediathekarr" "kind" "readinessProbe" "given" .Values.mediathekarr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "mediathekarr" "kind" "startupProbe" "given" .Values.mediathekarr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "mediathekarr" "given" .Values.mediathekarr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "mediathekarr" "given" .Values.mediathekarr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /app/config
|
||||
name: mediathekarr-config
|
||||
{{- /* The same claim, the same mount path as sonarr/radarr - that
|
||||
is what makes the complete_dir path identical on both
|
||||
sides. */}}
|
||||
- mountPath: /downloads
|
||||
name: {{ include "freesailarr.mediaVolumeName" (dict "root" $ "category" "downloads") }}
|
||||
{{- with .Values.media.downloads.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
# Not a measured requirement: the container ran through with a
|
||||
# read-only root and without a writable /tmp. Kept as cheap
|
||||
# insurance for the .NET runtime, which is the kind of consumer that
|
||||
# reaches for /tmp on a code path nobody exercised yet.
|
||||
- mountPath: /tmp
|
||||
name: mediathekarr-tmp
|
||||
{{- end }}
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
# The tun device gluetun needs to create the Wireguard tunnel.
|
||||
@@ -542,11 +645,11 @@ spec:
|
||||
setup with three separate claims still renders byte-identically to
|
||||
2.0.1 and the diff itself proves the change is inert there.
|
||||
*/}}
|
||||
{{- if or .Values.qbittorrent.enabled .Values.radarr.enabled .Values.sonarr.enabled }}
|
||||
{{- if or .Values.qbittorrent.enabled .Values.radarr.enabled .Values.sonarr.enabled .Values.mediathekarr.enabled }}
|
||||
{{- if include "freesailarr.mediaVolumeOwned" (dict "root" $ "category" "downloads") }}
|
||||
- name: media-downloads
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ required "media.downloads.existingClaim is required when qbittorrent, radarr or sonarr is enabled" .Values.media.downloads.existingClaim }}
|
||||
claimName: {{ required "media.downloads.existingClaim is required when qbittorrent, radarr, sonarr or mediathekarr is enabled" .Values.media.downloads.existingClaim }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if or .Values.radarr.enabled .Values.bazarr.enabled }}
|
||||
@@ -579,12 +682,16 @@ spec:
|
||||
- name: seerr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.mediathekarr.enabled }}
|
||||
- name: mediathekarr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
# Config volumes for gluetun and the enabled apps. NOTE: volumeClaimTemplates
|
||||
# are immutable - toggling an app's enabled flag later requires recreating
|
||||
# the StatefulSet once (kubectl delete statefulset <name> --cascade=orphan
|
||||
# before upgrading; existing PVCs and their data survive).
|
||||
volumeClaimTemplates:
|
||||
{{- range $app := list "gluetun" "qbittorrent" "prowlarr" "radarr" "recyclarr" "sonarr" "seerr" "bazarr" }}
|
||||
{{- range $app := list "gluetun" "qbittorrent" "prowlarr" "radarr" "recyclarr" "sonarr" "seerr" "bazarr" "mediathekarr" }}
|
||||
{{- $config := index $.Values $app }}
|
||||
{{- if or (eq $app "gluetun") $config.enabled }}
|
||||
- metadata:
|
||||
|
||||
Reference in new issue
Block a user