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.
This commit is contained in:
8 files changed
+559
-46
No files matched your search
@@ -194,6 +194,131 @@ ingress block with enabled: true. Used by the ingress template.
|
||||
{{- toJson $apps -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
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.
|
||||
|
||||
Input: dict "root" $ "app" <app name>.
|
||||
*/}}
|
||||
{{- define "freesailarr.appPath" -}}
|
||||
{{- $config := (index .root.Values .app).ingress -}}
|
||||
{{- $path := "" -}}
|
||||
{{- if kindIs "map" $config -}}
|
||||
{{- $path = $config.path -}}
|
||||
{{- end -}}
|
||||
{{- if or (kindIs "invalid" $path) (eq (toString $path) "") -}}
|
||||
{{- if eq .app "seerr" -}}
|
||||
{{- $path = "/" -}}
|
||||
{{- else -}}
|
||||
{{- $path = printf "/%s" .app -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $path -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The apps served under the shared domain (ingress.host), as a JSON array of
|
||||
{name, port, path} dicts, sorted by path length descending so the rendered list
|
||||
is deterministic.
|
||||
|
||||
The shared-domain mode is off unless ingress.host is set; then it covers the
|
||||
apps whose ingress renders at all (freesailarr.ingressApps, i.e. the enabled
|
||||
port apps with ingress.enabled) and which do not carry a host of their own. A
|
||||
per-app host always wins and keeps rendering its own object exactly as before,
|
||||
which is what makes mixed operation fall out for free.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
{{- $shared := include "freesailarr.sharedIngressApps" . | fromJsonArray }}
|
||||
*/}}
|
||||
{{- define "freesailarr.sharedIngressApps" -}}
|
||||
{{- $root := . -}}
|
||||
{{- $apps := list -}}
|
||||
{{- $host := $root.Values.ingress.host | default "" -}}
|
||||
{{- if and $root.Values.ingress.enabled $host -}}
|
||||
{{- $seen := dict -}}
|
||||
{{- range $app := include "freesailarr.ingressApps" $root | fromJsonArray -}}
|
||||
{{- $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) -}}
|
||||
{{- 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) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $seen $path $app.name -}}
|
||||
{{- $apps = append $apps (dict "name" $app.name "port" $app.port "path" $path) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- /* Sort by path length descending, ties broken alphabetically. The keys are
|
||||
unique because the guard above rejects duplicate paths. */ -}}
|
||||
{{- $keyed := dict -}}
|
||||
{{- $keys := list -}}
|
||||
{{- range $app := $apps -}}
|
||||
{{- $key := printf "%03d|%s" (sub 999 (len $app.path)) $app.path -}}
|
||||
{{- $_ := set $keyed $key $app -}}
|
||||
{{- $keys = append $keys $key -}}
|
||||
{{- end -}}
|
||||
{{- $sorted := list -}}
|
||||
{{- range $key := sortAlpha $keys -}}
|
||||
{{- $sorted = append $sorted (index $keyed $key) -}}
|
||||
{{- end -}}
|
||||
{{- toJson $sorted -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The value for a servarr app's <APP>__SERVER__URLBASE variable, or the empty
|
||||
string when the chart must not set it. Radarr, Sonarr and Prowlarr read their
|
||||
base path from this environment variable (schema
|
||||
<APP>__CONFIGNAMESPACE__CONFIGITEM, https://wiki.servarr.com/sonarr/environment-variables),
|
||||
so the chart can configure them itself as soon as they render under a sub-path.
|
||||
|
||||
Empty - i.e. the chart sets nothing - in three cases:
|
||||
|
||||
- the app does not render under the shared host, or renders at "/";
|
||||
- the app's own env block already carries the variable. The env blocks are
|
||||
documented as additive; emitting a second entry with the same name would
|
||||
break that promise, and two identically named variables in one container
|
||||
are not a defined state to build on.
|
||||
|
||||
Input: dict "root" $ "app" <"radarr"|"sonarr"|"prowlarr">.
|
||||
*/}}
|
||||
{{- define "freesailarr.urlBase" -}}
|
||||
{{- $root := .root -}}
|
||||
{{- $app := .app -}}
|
||||
{{- $var := printf "%s__SERVER__URLBASE" (upper $app) -}}
|
||||
{{- $path := "" -}}
|
||||
{{- range $entry := include "freesailarr.sharedIngressApps" $root | fromJsonArray -}}
|
||||
{{- if eq $entry.name $app -}}
|
||||
{{- $path = $entry.path -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if eq $path "/" -}}
|
||||
{{- $path = "" -}}
|
||||
{{- end -}}
|
||||
{{- if $path -}}
|
||||
{{- range $entry := (index $root.Values $app).env -}}
|
||||
{{- if eq (toString $entry.name) $var -}}
|
||||
{{- $path = "" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $path -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Comma-separated FIREWALL_INPUT_PORTS for gluetun: the ports of the enabled app
|
||||
containers (ingress traffic and the kubelet probes both arrive on the pod IP)
|
||||
|
||||
Reference in new issue
Block a user