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.
This commit is contained in:
7 files changed
+1404
-57
No files matched your search
@@ -0,0 +1,106 @@
|
||||
{{- $name := printf "%s-%s" .Release.Name .Chart.Name -}}
|
||||
{{- $replicas := include "freesailarr.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.replicas) -}}
|
||||
{{- /* The effective (null-safe) startup probe, so the budget below matches the
|
||||
container even when the block has been erased (#99). */ -}}
|
||||
{{- $startup := fromYaml (include "freesailarr.probeFor" (dict "root" $ "app" "gluetun" "kind" "startupProbe" "given" .Values.gluetun.startupProbe)) -}}
|
||||
{{- $budget := mul (int $startup.periodSeconds) (int $startup.failureThreshold) -}}
|
||||
{{- $ingressApps := include "freesailarr.ingressApps" . | fromJsonArray -}}
|
||||
{{- $portApps := include "freesailarr.portApps" . | fromJsonArray -}}
|
||||
freesailarr — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
Nine 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
|
||||
the Wireguard tunnel is actually working. So "pod not Ready" almost always
|
||||
means "no tunnel", not "app broken". The startup budget is {{ $budget }}s before
|
||||
Kubernetes starts killing the container:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} logs {{ $name }}-sfs-0 -c gluetun -f
|
||||
|
||||
While gluetun is down its firewall keeps blocking every app container's
|
||||
egress. That is the kill switch doing its job — the apps stay up and simply
|
||||
reach nothing.
|
||||
|
||||
THE 1PASSWORD ITEM AND THE TWO FIELDS IT MUST CONTAIN
|
||||
secrets.gluetunSecretRef -> {{ .Values.secrets.gluetunSecretRef | default "(not set)" }}
|
||||
wireguard-private-key
|
||||
wireguard-addresses
|
||||
|
||||
A missing field does not fail the install: gluetun starts with the variable
|
||||
empty and never completes a handshake.
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get onepassworditem {{ $name }}-gluetun-secret
|
||||
{{- if not .Values.gluetun.vpn.firewallOutboundSubnets }}
|
||||
|
||||
SET gluetun.vpn.firewallOutboundSubnets — IT IS EMPTY RIGHT NOW
|
||||
Without it the gluetun firewall blocks cluster-internal egress as well:
|
||||
lookups against cluster DNS and seerr reaching an in-cluster Jellyfin both
|
||||
fail, in a way that looks like an application bug. Set it to the cluster pod
|
||||
and service CIDRs, e.g. "10.42.0.0/16,10.43.0.0/16" on k3s.
|
||||
{{- end }}
|
||||
|
||||
ACCESS
|
||||
{{- if and .Values.ingress.enabled $ingressApps }}
|
||||
{{- range $ingressApps }}
|
||||
{{- $cfg := (index $.Values .name).ingress }}
|
||||
{{ .name }}: https://{{ $cfg.host }}
|
||||
{{- end }}
|
||||
|
||||
Each app has its OWN Ingress and its own TLS secret; while cert-manager is
|
||||
still issuing, the browser warns:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get certificate
|
||||
{{- else }}
|
||||
No Ingress is rendered{{ if not .Values.ingress.enabled }} (ingress.enabled is false){{ end }}. Reach an app through its own service:
|
||||
{{- range $portApps }}
|
||||
|
||||
kubectl -n {{ $.Release.Namespace }} port-forward svc/{{ $name }}-{{ .name }}-service {{ .port }}:{{ .port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.qbittorrent.enabled }}
|
||||
|
||||
qBittorrent's WebUI rejects a Host header whose port differs from its own
|
||||
({{ "8080" }}) with a plain 401. A port-forward on a different local port therefore
|
||||
looks like a login failure — forward 8080:8080, or go through the ingress.
|
||||
{{- 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
|
||||
containers share gluetun's network namespace, so the services differ only in
|
||||
their port. They give you stable per-app DNS names, NOT network separation
|
||||
between the apps.
|
||||
|
||||
TOGGLING AN APP OFF AND ON LATER NEEDS ONE MANUAL STEP
|
||||
The config volumes come from volumeClaimTemplates, and those are immutable.
|
||||
Changing any <app>.enabled after the first install makes the upgrade fail
|
||||
until the StatefulSet is recreated — the data survives:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} delete statefulset {{ $name }}-sfs --cascade=orphan
|
||||
|
||||
THE MEDIA CLAIMS ARE YOURS, THE CONFIG VOLUMES ARE THE CHART'S
|
||||
media.<category>.existingClaim is mounted, never created, and is meant to be
|
||||
shared with your Jellyfin. One claim can serve all three categories via
|
||||
media.<category>.subPath.
|
||||
|
||||
Measured, so you do not plan around something that does not happen: Radarr
|
||||
and Sonarr do NOT hardlink between the categories, not even on one claim.
|
||||
Kubernetes makes every volumeMount its own bind mount and link() across two
|
||||
mount points fails with EXDEV regardless of subPath, so an import copies.
|
||||
|
||||
The containers write as {{ .Values.puid }}:{{ .Values.pgid }}. If the claims already hold data
|
||||
owned by someone else, fix the ownership before the first start — the pod
|
||||
will not do it for you (fsGroupChangePolicy is OnRootMismatch, deliberately,
|
||||
so a large library is not recursively re-chowned on every start).
|
||||
{{- if eq $replicas "0" }}
|
||||
|
||||
SCALED TO ZERO
|
||||
{{ if .Values.scaleDown }}scaleDown: true{{ else }}replicas: 0{{ end }} — the StatefulSet runs with replicas: 0. PVCs, secrets,
|
||||
services{{ if .Values.ingress.enabled }} and ingresses{{ end }} stay in place; set it back and upgrade to bring the
|
||||
stack up again.
|
||||
{{- else if ne $replicas "1" }}
|
||||
|
||||
MORE THAN ONE REPLICA IS NOT MEANINGFUL
|
||||
replicas is {{ $replicas }}. Each replica would run its own isolated stack on its own
|
||||
config volumes, all of them fighting over the same media claims and the same
|
||||
VPN credentials.
|
||||
{{- end }}
|
||||
@@ -218,3 +218,497 @@ alive. It is not what opens the ports today.
|
||||
{{- $ports = append $ports 9999 -}}
|
||||
{{- join "," $ports -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Recursive merge of an override onto defaults where null never deletes and set
|
||||
values always win, including false and 0 (issue #99).
|
||||
|
||||
Input: dict "defaults" <dict> "given" <dict|nil> "path" <values path, for errors>.
|
||||
|
||||
NOTE: like the resources helper above, this is duplicated into every chart's
|
||||
templates/_helpers.tpl with the chart's name as define prefix. The reference
|
||||
implementation lives in template-chart.
|
||||
*/}}
|
||||
{{- define "freesailarr.defaultedDict" -}}
|
||||
{{- $defaults := .defaults | default dict -}}
|
||||
{{- $given := .given -}}
|
||||
{{- $path := .path | default "value" -}}
|
||||
{{- if kindIs "invalid" $given -}}
|
||||
{{- $given = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $given) -}}
|
||||
{{- fail (printf "%s must be a mapping or null, got %s (%v)" $path (kindOf $given) $given) -}}
|
||||
{{- end -}}
|
||||
{{- $out := deepCopy $defaults -}}
|
||||
{{- range $key, $value := $given -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $default := index $defaults $key -}}
|
||||
{{- if and (kindIs "map" $value) (kindIs "map" $default) -}}
|
||||
{{- $_ := set $out $key (fromYaml (include "freesailarr.defaultedDict" (dict "defaults" $default "given" $value "path" (printf "%s.%s" $path $key)))) -}}
|
||||
{{- else if kindIs "map" $default -}}
|
||||
{{- fail (printf "%s.%s must be a mapping or null, got %s (%v)" $path $key (kindOf $value) $value) -}}
|
||||
{{- else -}}
|
||||
{{- $_ := set $out $key $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- toYaml $out -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Pick one sub-block out of a values block that may itself be null.
|
||||
Input: dict "block" <dict|nil> "key" <string> "path" <string>.
|
||||
Needed because ".Values.<app>.securityContext" can itself be erased, and
|
||||
indexing into nil inside a template is not an error but silently yields
|
||||
nothing.
|
||||
*/}}
|
||||
{{- define "freesailarr.subBlock" -}}
|
||||
{{- $block := .block -}}
|
||||
{{- if kindIs "invalid" $block -}}
|
||||
{{- $block = dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "map" $block) -}}
|
||||
{{- fail (printf "%s must be a mapping or null, got %s (%v)" .path (kindOf $block) $block) -}}
|
||||
{{- end -}}
|
||||
{{- toYaml (dict "value" (index $block .key)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The chart's own defaults for every block of the "silent class" from issue #99 -
|
||||
security contexts, probes and resources. An override that erases one of these
|
||||
blocks (`key:` with no children, i.e. null) would otherwise render a container
|
||||
without hardening, without a probe or without resource accounting: it comes up,
|
||||
reports green and is quietly weaker. The accessors below merge the user's block
|
||||
onto these defaults with freesailarr.defaultedDict, where null never deletes.
|
||||
|
||||
These MUST stay in sync with values.yaml, which remains the documented,
|
||||
user-facing place for them; this copy is only the fallback. The sync is not left
|
||||
to discipline: rendering the chart with every one of these blocks explicitly set
|
||||
to null must produce byte-identical output to rendering it with the defaults,
|
||||
which is how the table was verified and how a drift shows up.
|
||||
|
||||
Two rules this table exists to honour (README, "Chart style guidelines"):
|
||||
|
||||
- It reproduces THIS chart's defaults, deviations included, not the repo
|
||||
standard. The five linuxserver.io containers deliberately run without
|
||||
runAsNonRoot and with a writable root filesystem, because their s6-overlay
|
||||
init starts as root and drops to PUID/PGID; "restoring" the clean standard
|
||||
here would leave them on the image's built-in uid 911 and make the existing
|
||||
media volumes inaccessible - the exact failure calibre-web-automated hit.
|
||||
flaresolverr's numerically pinned uid/gid 1000 and gluetun's NET_ADMIN are
|
||||
reproduced for the same reason.
|
||||
- The probe defaults carry their CONTEXT, not just timings: every httpGet
|
||||
keeps its path and port, and recyclarr - which serves no HTTP port in
|
||||
daemon mode - keeps its exec command. A probe default without them is worse
|
||||
than no default.
|
||||
*/}}
|
||||
{{- define "freesailarr.fallbacks" -}}
|
||||
gluetun:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 9999
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 9999
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 9999
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 128Mi
|
||||
ephemeralStorage: 50Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_ADMIN
|
||||
qbittorrent:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.2
|
||||
memory: 512Mi
|
||||
ephemeralStorage: 100Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
prowlarr:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 9696
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 9696
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 9696
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
flaresolverr:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8191
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8191
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8191
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.2
|
||||
memory: 512Mi
|
||||
ephemeralStorage: 200Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
radarr:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 7878
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 7878
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 7878
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
recyclarr:
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- recyclarr
|
||||
- --version
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- recyclarr
|
||||
- --version
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
exec:
|
||||
command:
|
||||
- recyclarr
|
||||
- --version
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.05
|
||||
memory: 128Mi
|
||||
ephemeralStorage: 50Mi
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
sonarr:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8989
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8989
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8989
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
seerr:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/settings/public
|
||||
port: 5055
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/settings/public
|
||||
port: 5055
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /api/v1/settings/public
|
||||
port: 5055
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
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
|
||||
bazarr:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 6767
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 6767
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 6767
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
configInit:
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
ephemeralStorage: 1Mi
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective pod security context. Separate from the per-container table
|
||||
because it is the one block shared by all nine containers.
|
||||
*/}}
|
||||
{{- define "freesailarr.podSecurityContext" -}}
|
||||
{{- $given := (fromYaml (include "freesailarr.subBlock" (dict "block" .Values.securityContext "key" "pod" "path" "securityContext"))).value -}}
|
||||
{{- $defaults := dict
|
||||
"fsGroup" 1000
|
||||
"fsGroupChangePolicy" "OnRootMismatch"
|
||||
"seccompProfile" (dict "type" "RuntimeDefault") -}}
|
||||
{{- include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" $given "path" "securityContext.pod") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective probe / security context / resources for one container.
|
||||
|
||||
Input: dict "root" $ "app" <key in the fallback table> "given" <the values block>
|
||||
and, for probes, "kind" <livenessProbe|readinessProbe|startupProbe>.
|
||||
|
||||
"given" is passed in rather than derived from "app" because the qbittorrent init
|
||||
container's blocks live under qbittorrent.configInit, not under a top-level key
|
||||
of the same name.
|
||||
*/}}
|
||||
{{- define "freesailarr.probeFor" -}}
|
||||
{{- $fb := fromYaml (include "freesailarr.fallbacks" .root) -}}
|
||||
{{- $defaults := index $fb .app .kind -}}
|
||||
{{- include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "%s.%s" .app .kind)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "freesailarr.securityContextFor" -}}
|
||||
{{- $fb := fromYaml (include "freesailarr.fallbacks" .root) -}}
|
||||
{{- $defaults := index $fb .app "securityContext" -}}
|
||||
{{- include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "%s.securityContext" .app)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "freesailarr.resourcesFor" -}}
|
||||
{{- $fb := fromYaml (include "freesailarr.fallbacks" .root) -}}
|
||||
{{- $defaults := index $fb .app "resources" -}}
|
||||
{{- $merged := fromYaml (include "freesailarr.defaultedDict" (dict "defaults" $defaults "given" .given "path" (printf "%s.resources" .app))) -}}
|
||||
{{- include "freesailarr.resources" $merged -}}
|
||||
{{- end -}}
|
||||
@@ -17,12 +17,23 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
annotations:
|
||||
# There is no ConfigMap in this chart, so checksum/config does not
|
||||
# apply. What does apply is the op:// path: Helm never sees the secret's
|
||||
# contents - the 1Password operator fills the Secret in the cluster -
|
||||
# but it does see which item the OnePasswordItem points at. Repointing
|
||||
# secrets.gluetunSecretRef at a different vault item therefore takes
|
||||
# effect instead of silently leaving the old credentials in the running
|
||||
# pod. The name differs from checksum/config on purpose: that one
|
||||
# promises "the config changed", this one only "the config source
|
||||
# changed" (#82).
|
||||
checksum/secret-ref: {{ include (print $.Template.BasePath "/freesailarr-gluetun.secret.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext.pod | nindent 8 }}
|
||||
{{- include "freesailarr.podSecurityContext" . | nindent 8 }}
|
||||
{{- if and .Values.qbittorrent.enabled .Values.qbittorrent.networkInterface }}
|
||||
initContainers:
|
||||
# qBittorrent has no environment variable for the session's network
|
||||
@@ -71,11 +82,9 @@ spec:
|
||||
- name: QBT_INTERFACE
|
||||
value: {{ .Values.qbittorrent.networkInterface | quote }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.qbittorrent.configInit.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "configInit" "given" .Values.qbittorrent.configInit.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
{{- toYaml .Values.qbittorrent.configInit.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "configInit" "given" .Values.qbittorrent.configInit.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: qbittorrent-config
|
||||
@@ -151,15 +160,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: health
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.gluetun.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "gluetun" "kind" "livenessProbe" "given" .Values.gluetun.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.gluetun.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "gluetun" "kind" "readinessProbe" "given" .Values.gluetun.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.gluetun.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "gluetun" "kind" "startupProbe" "given" .Values.gluetun.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.gluetun.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "gluetun" "given" .Values.gluetun.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.gluetun.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "gluetun" "given" .Values.gluetun.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /gluetun
|
||||
name: gluetun-config
|
||||
@@ -201,15 +210,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: qbittorrent
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.qbittorrent.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "qbittorrent" "kind" "livenessProbe" "given" .Values.qbittorrent.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.qbittorrent.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "qbittorrent" "kind" "readinessProbe" "given" .Values.qbittorrent.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.qbittorrent.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "qbittorrent" "kind" "startupProbe" "given" .Values.qbittorrent.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.qbittorrent.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "qbittorrent" "given" .Values.qbittorrent.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.qbittorrent.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "qbittorrent" "given" .Values.qbittorrent.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: qbittorrent-config
|
||||
@@ -236,15 +245,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: prowlarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.prowlarr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "prowlarr" "kind" "livenessProbe" "given" .Values.prowlarr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.prowlarr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "prowlarr" "kind" "readinessProbe" "given" .Values.prowlarr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.prowlarr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "prowlarr" "kind" "startupProbe" "given" .Values.prowlarr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.prowlarr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "prowlarr" "given" .Values.prowlarr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.prowlarr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "prowlarr" "given" .Values.prowlarr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: prowlarr-config
|
||||
@@ -262,15 +271,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: flaresolverr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.flaresolverr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "flaresolverr" "kind" "livenessProbe" "given" .Values.flaresolverr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.flaresolverr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "flaresolverr" "kind" "readinessProbe" "given" .Values.flaresolverr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.flaresolverr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "flaresolverr" "kind" "startupProbe" "given" .Values.flaresolverr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.flaresolverr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "flaresolverr" "given" .Values.flaresolverr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.flaresolverr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "flaresolverr" "given" .Values.flaresolverr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
# Writable paths for headless chromium so the root filesystem
|
||||
# stays read-only; /dev/shm is a memory-backed emptyDir because
|
||||
@@ -297,15 +306,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: radarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.radarr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "radarr" "kind" "livenessProbe" "given" .Values.radarr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.radarr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "radarr" "kind" "readinessProbe" "given" .Values.radarr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.radarr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "radarr" "kind" "startupProbe" "given" .Values.radarr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.radarr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "radarr" "given" .Values.radarr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.radarr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "radarr" "given" .Values.radarr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: radarr-config
|
||||
@@ -331,17 +340,15 @@ spec:
|
||||
value: "true"
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.recyclarr.env) | nindent 12 }}
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.recyclarr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "recyclarr" "kind" "livenessProbe" "given" .Values.recyclarr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.recyclarr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "recyclarr" "kind" "readinessProbe" "given" .Values.recyclarr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.recyclarr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "recyclarr" "kind" "startupProbe" "given" .Values.recyclarr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.recyclarr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "recyclarr" "given" .Values.recyclarr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
{{- toYaml .Values.recyclarr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "recyclarr" "given" .Values.recyclarr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: recyclarr-config
|
||||
@@ -367,15 +374,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: sonarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.sonarr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "sonarr" "kind" "livenessProbe" "given" .Values.sonarr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.sonarr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "sonarr" "kind" "readinessProbe" "given" .Values.sonarr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.sonarr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "sonarr" "kind" "startupProbe" "given" .Values.sonarr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.sonarr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "sonarr" "given" .Values.sonarr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.sonarr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "sonarr" "given" .Values.sonarr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: sonarr-config
|
||||
@@ -403,17 +410,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: seerr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.seerr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "seerr" "kind" "livenessProbe" "given" .Values.seerr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.seerr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "seerr" "kind" "readinessProbe" "given" .Values.seerr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.seerr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "seerr" "kind" "startupProbe" "given" .Values.seerr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.seerr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "seerr" "given" .Values.seerr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
{{- toYaml .Values.seerr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "seerr" "given" .Values.seerr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /app/config
|
||||
name: seerr-config
|
||||
@@ -437,15 +442,15 @@ spec:
|
||||
protocol: TCP
|
||||
name: bazarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.bazarr.livenessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "bazarr" "kind" "livenessProbe" "given" .Values.bazarr.livenessProbe) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.bazarr.readinessProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "bazarr" "kind" "readinessProbe" "given" .Values.bazarr.readinessProbe) | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.bazarr.startupProbe | nindent 12 }}
|
||||
{{- include "freesailarr.probeFor" (dict "root" $ "app" "bazarr" "kind" "startupProbe" "given" .Values.bazarr.startupProbe) | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.bazarr.resources | nindent 12 }}
|
||||
{{- include "freesailarr.resourcesFor" (dict "root" $ "app" "bazarr" "given" .Values.bazarr.resources) | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.bazarr.securityContext | nindent 12 }}
|
||||
{{- include "freesailarr.securityContextFor" (dict "root" $ "app" "bazarr" "given" .Values.bazarr.securityContext) | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: bazarr-config
|
||||
|
||||
Reference in new issue
Block a user