Add freesailarr chart: gluetun-tunneled media automation stack
Single-pod StatefulSet running gluetun (appVersion 3.41.1) as Wireguard VPN gateway with qbittorrent, prowlarr, flaresolverr, radarr, recyclarr, sonarr, seerr and bazarr sharing its network namespace. Config volumes via volumeClaimTemplates, media libraries as existing claims, per-app enable/ingress toggles, computed FIREWALL_INPUT_PORTS, hardened security contexts with documented per-image deviations. Wires up publish workflow, change-app-version choice, install-test exclusion and README overview.
This commit is contained in:
12 files changed
+1334
No files matched your search
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v2
|
||||
name: freesailarr
|
||||
description: A Helm chart to deploy a Wireguard-tunneled media automation stack (gluetun, qbittorrent, the *arrs, seerr) on Kubernetes
|
||||
icon: https://raw.githubusercontent.com/qdm12/gluetun/master/doc/logo_256.png
|
||||
|
||||
type: application
|
||||
|
||||
version: 1.0.0+up3.41.1
|
||||
appVersion: "3.41.1"
|
||||
|
||||
maintainers:
|
||||
- name: jklein
|
||||
@@ -0,0 +1,189 @@
|
||||
{{/*
|
||||
Render the body of a container "resources:" block from a values dict of the form
|
||||
|
||||
resources:
|
||||
limitFactor: 3 # optional, default 3
|
||||
requests: { cpu, memory, ephemeral-storage / ephemeralStorage, ... }
|
||||
limits: { ... } # optional, every entry optional
|
||||
|
||||
Rules (variant B, see issue #19):
|
||||
- requests are rendered as given (camelCase "ephemeralStorage" is normalized
|
||||
to the Kubernetes key "ephemeral-storage").
|
||||
- an explicitly set limit always wins.
|
||||
- a missing memory / ephemeral-storage limit is computed as
|
||||
limitFactor x the corresponding request.
|
||||
- a missing cpu limit is NOT set at all (the kubelet then treats CPU as
|
||||
unlimited; CPU is compressible, so this cannot kill the workload).
|
||||
|
||||
Usage:
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.<app>.resources | nindent 12 }}
|
||||
|
||||
NOTE: charts in this repo deliberately have no dependencies, so this helper 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.resources" -}}
|
||||
{{- $resources := . | default dict -}}
|
||||
{{- $factor := 3.0 -}}
|
||||
{{- if not (kindIs "invalid" $resources.limitFactor) -}}
|
||||
{{- $factor = float64 $resources.limitFactor -}}
|
||||
{{- end -}}
|
||||
{{- $requests := dict -}}
|
||||
{{- range $key, $value := ($resources.requests | default dict) -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $_ := set $requests (include "freesailarr.resourceKey" $key) $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $limits := dict -}}
|
||||
{{- range $key, $value := ($resources.limits | default dict) -}}
|
||||
{{- if not (kindIs "invalid" $value) -}}
|
||||
{{- $_ := set $limits (include "freesailarr.resourceKey" $key) $value -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- range $key, $request := $requests -}}
|
||||
{{- if and (ne $key "cpu") (not (hasKey $limits $key)) -}}
|
||||
{{- $_ := set $limits $key (include "freesailarr.multiplyQuantity" (dict "quantity" $request "factor" $factor)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $out := dict -}}
|
||||
{{- with $requests }}{{- $_ := set $out "requests" . }}{{- end -}}
|
||||
{{- with $limits }}{{- $_ := set $out "limits" . }}{{- end -}}
|
||||
{{- toYaml $out -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Normalize a resource name from values to its Kubernetes key.
|
||||
*/}}
|
||||
{{- define "freesailarr.resourceKey" -}}
|
||||
{{- if eq . "ephemeralStorage" -}}ephemeral-storage{{- else -}}{{ . }}{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Multiply a Kubernetes quantity by a factor, keeping its suffix.
|
||||
Input: dict "quantity" <string|number> "factor" <float64>.
|
||||
Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary
|
||||
("512Mi", "1Gi", "10Ki") and milli/SI suffixes ("100m", "500M"). Integer
|
||||
results are printed without a decimal point.
|
||||
*/}}
|
||||
{{- define "freesailarr.multiplyQuantity" -}}
|
||||
{{- $quantity := printf "%v" .quantity -}}
|
||||
{{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}}
|
||||
{{- if not $number -}}
|
||||
{{- fail (printf "cannot parse resource quantity %q" $quantity) -}}
|
||||
{{- end -}}
|
||||
{{- $suffix := trimPrefix $number $quantity -}}
|
||||
{{- if not (regexMatch "^(m|k|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei)?$" $suffix) -}}
|
||||
{{- fail (printf "unsupported resource quantity suffix %q in %q" $suffix $quantity) -}}
|
||||
{{- end -}}
|
||||
{{- $product := mulf (float64 $number) .factor -}}
|
||||
{{- if eq $product (floor $product) -}}
|
||||
{{- printf "%d%s" (int64 $product) $suffix -}}
|
||||
{{- else -}}
|
||||
{{- printf "%v%s" $product $suffix -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Render the replica count for a Deployment/StatefulSet.
|
||||
|
||||
Input: dict
|
||||
"scaleDown" the chart-global .Values.scaleDown flag (may be nil)
|
||||
"replicas" the workload's replicas value (may be nil)
|
||||
|
||||
Rules (issue #25):
|
||||
- scaleDown: true takes precedence and always yields 0.
|
||||
- otherwise an explicitly set replicas value is used as given, including an
|
||||
explicit 0 — Helm's "default" would swallow 0, hence the explicit
|
||||
kindIs "invalid" nil check.
|
||||
- a missing replicas value defaults to 1.
|
||||
|
||||
Usage:
|
||||
replicas: {{ include "freesailarr.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.replicas) }}
|
||||
|
||||
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.replicas" -}}
|
||||
{{- if .scaleDown -}}
|
||||
0
|
||||
{{- else if not (kindIs "invalid" .replicas) -}}
|
||||
{{- .replicas -}}
|
||||
{{- else -}}
|
||||
1
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
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.
|
||||
|
||||
Usage:
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.<app>.env) | nindent 12 }}
|
||||
*/}}
|
||||
{{- define "freesailarr.env" -}}
|
||||
{{- $root := .root -}}
|
||||
{{- range $value := .env }}
|
||||
{{- if $value.value }}
|
||||
- name: {{ $value.name }}
|
||||
value: {{ tpl $value.value $root | quote }}
|
||||
{{- else if $value.valueFrom }}
|
||||
{{- if $value.valueFrom.secretKeyRef }}
|
||||
- name: {{ $value.name }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ tpl $value.valueFrom.secretKeyRef.name $root | quote }}
|
||||
key: {{ tpl $value.valueFrom.secretKeyRef.key $root | quote }}
|
||||
{{- else if $value.valueFrom.configMapKeyRef }}
|
||||
- name: {{ $value.name }}
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ tpl $value.valueFrom.configMapKeyRef.name $root | quote }}
|
||||
key: {{ tpl $value.valueFrom.configMapKeyRef.key $root | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The enabled apps that expose a web UI, as a JSON array of {name, port} dicts
|
||||
(container port name == app name == ingress subdomain). Consumed by the
|
||||
service, the ingress and the firewall-ports helper below:
|
||||
|
||||
{{- $apps := include "freesailarr.uiApps" . | fromJsonArray }}
|
||||
*/}}
|
||||
{{- define "freesailarr.uiApps" -}}
|
||||
{{- $apps := list -}}
|
||||
{{- range $app := list
|
||||
(dict "name" "qbittorrent" "port" 8080)
|
||||
(dict "name" "prowlarr" "port" 9696)
|
||||
(dict "name" "radarr" "port" 7878)
|
||||
(dict "name" "sonarr" "port" 8989)
|
||||
(dict "name" "seerr" "port" 5055)
|
||||
(dict "name" "bazarr" "port" 6767) -}}
|
||||
{{- if (index $.Values $app.name).enabled -}}
|
||||
{{- $apps = append $apps $app -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- toJson $apps -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Comma-separated FIREWALL_INPUT_PORTS for gluetun: the UI ports of the enabled
|
||||
apps (ingress traffic arrives on the pod IP), flaresolverr's 8191 when enabled
|
||||
(the kubelet probes connect to the pod IP too) and 9999 for gluetun's own
|
||||
health server. Without these the gluetun firewall drops the traffic.
|
||||
*/}}
|
||||
{{- define "freesailarr.firewallInputPorts" -}}
|
||||
{{- $ports := list -}}
|
||||
{{- range $app := include "freesailarr.uiApps" . | fromJsonArray -}}
|
||||
{{- $ports = append $ports (int $app.port) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.flaresolverr.enabled -}}
|
||||
{{- $ports = append $ports 8191 -}}
|
||||
{{- end -}}
|
||||
{{- $ports = append $ports 9999 -}}
|
||||
{{- join "," $ports -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-gluetun-secret
|
||||
spec:
|
||||
itemPath: {{ required "You must provide a gluetunSecret Reference" .Values.secrets.gluetunSecretRef }}
|
||||
@@ -0,0 +1,38 @@
|
||||
{{- $apps := list }}
|
||||
{{- range $app := include "freesailarr.uiApps" . | fromJsonArray }}
|
||||
{{- if (index $.Values $app.name).ingress.enabled }}
|
||||
{{- $apps = append $apps $app }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and .Values.ingress.enabled (gt (len $apps) 0) }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
rules:
|
||||
{{- range $apps }}
|
||||
- host: {{ .name }}.{{ required "A Domain/Host must be provided" $.Values.ingress.domain }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ $.Release.Name }}-{{ $.Chart.Name }}-service
|
||||
port:
|
||||
name: {{ .name }}
|
||||
path: /
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
tls:
|
||||
- hosts:
|
||||
{{- range $apps }}
|
||||
- {{ .name }}.{{ $.Values.ingress.domain }}
|
||||
{{- end }}
|
||||
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{- $apps := include "freesailarr.uiApps" . | fromJsonArray }}
|
||||
{{- if gt (len $apps) 0 }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
spec:
|
||||
selector:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
ports:
|
||||
{{- range $apps }}
|
||||
- protocol: TCP
|
||||
port: {{ int .port }}
|
||||
targetPort: {{ .name }}
|
||||
name: {{ .name }}
|
||||
{{- end }}
|
||||
type: ClusterIP
|
||||
{{- end }}
|
||||
@@ -0,0 +1,459 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
spec:
|
||||
replicas: {{ include "freesailarr.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.replicas) }}
|
||||
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
template:
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
# gluetun is the VPN gateway: all app containers share the pod's
|
||||
# network namespace, talk to each other over localhost and reach the
|
||||
# internet only through the Wireguard tunnel (gluetun's firewall acts
|
||||
# as kill switch).
|
||||
- name: gluetun
|
||||
image: "qmcgaw/gluetun:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
- name: VPN_SERVICE_PROVIDER
|
||||
value: {{ .Values.gluetun.vpn.serviceProvider | quote }}
|
||||
- name: VPN_TYPE
|
||||
value: {{ .Values.gluetun.vpn.type | quote }}
|
||||
- name: WIREGUARD_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-gluetun-secret
|
||||
key: wireguard-private-key
|
||||
- name: WIREGUARD_ADDRESSES
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-gluetun-secret
|
||||
key: wireguard-addresses
|
||||
{{- with .Values.gluetun.vpn.serverCities }}
|
||||
- name: SERVER_CITIES
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.gluetun.vpn.serverCountries }}
|
||||
- name: SERVER_COUNTRIES
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.gluetun.vpn.dnsUpstreamResolvers }}
|
||||
- name: DNS_UPSTREAM_RESOLVERS
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.gluetun.vpn.firewallOutboundSubnets }}
|
||||
- name: FIREWALL_OUTBOUND_SUBNETS
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
# Open the firewall for the UI/probe ports of the enabled apps and
|
||||
# the health server - kubelet probes and ingress traffic arrive on
|
||||
# the pod IP and would be dropped otherwise.
|
||||
- name: FIREWALL_INPUT_PORTS
|
||||
value: {{ include "freesailarr.firewallInputPorts" . | quote }}
|
||||
# Bind the health server to all interfaces so the kubelet HTTP
|
||||
# probes (which connect to the pod IP, not localhost) reach it.
|
||||
- name: HEALTH_SERVER_ADDRESS
|
||||
value: ":9999"
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.gluetun.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 9999
|
||||
protocol: TCP
|
||||
name: health
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.gluetun.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.gluetun.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.gluetun.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.gluetun.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.gluetun.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /gluetun
|
||||
name: gluetun-config
|
||||
- mountPath: /dev/net/tun
|
||||
name: dev-net-tun
|
||||
{{- if .Values.qbittorrent.enabled }}
|
||||
- name: qbittorrent
|
||||
image: "{{ .Values.qbittorrent.image.repository }}:{{ .Values.qbittorrent.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PUID
|
||||
value: {{ .Values.puid | quote }}
|
||||
- name: PGID
|
||||
value: {{ .Values.pgid | quote }}
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
- name: WEBUI_PORT
|
||||
value: "8080"
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.qbittorrent.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
name: qbittorrent
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.qbittorrent.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.qbittorrent.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.qbittorrent.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.qbittorrent.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.qbittorrent.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: qbittorrent-config
|
||||
- mountPath: /downloads
|
||||
name: media-downloads
|
||||
# Writable paths for s6-overlay so the root filesystem stays
|
||||
# read-only (same pattern for all linuxserver.io containers).
|
||||
- mountPath: /run
|
||||
name: qbittorrent-run
|
||||
- mountPath: /tmp
|
||||
name: qbittorrent-tmp
|
||||
{{- end }}
|
||||
{{- if .Values.prowlarr.enabled }}
|
||||
- name: prowlarr
|
||||
image: "{{ .Values.prowlarr.image.repository }}:{{ .Values.prowlarr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PUID
|
||||
value: {{ .Values.puid | quote }}
|
||||
- name: PGID
|
||||
value: {{ .Values.pgid | quote }}
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.prowlarr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 9696
|
||||
protocol: TCP
|
||||
name: prowlarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.prowlarr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.prowlarr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.prowlarr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.prowlarr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.prowlarr.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: prowlarr-config
|
||||
- mountPath: /run
|
||||
name: prowlarr-run
|
||||
- mountPath: /tmp
|
||||
name: prowlarr-tmp
|
||||
{{- end }}
|
||||
{{- if .Values.flaresolverr.enabled }}
|
||||
- name: flaresolverr
|
||||
image: "{{ .Values.flaresolverr.image.repository }}:{{ .Values.flaresolverr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.flaresolverr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 8191
|
||||
protocol: TCP
|
||||
name: flaresolverr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.flaresolverr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.flaresolverr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.flaresolverr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.flaresolverr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .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
|
||||
# chromium needs more shared memory than the container default.
|
||||
- mountPath: /tmp
|
||||
name: flaresolverr-tmp
|
||||
- mountPath: /dev/shm
|
||||
name: flaresolverr-shm
|
||||
{{- end }}
|
||||
{{- if .Values.radarr.enabled }}
|
||||
- name: radarr
|
||||
image: "{{ .Values.radarr.image.repository }}:{{ .Values.radarr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PUID
|
||||
value: {{ .Values.puid | quote }}
|
||||
- name: PGID
|
||||
value: {{ .Values.pgid | quote }}
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.radarr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 7878
|
||||
protocol: TCP
|
||||
name: radarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.radarr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.radarr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.radarr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.radarr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.radarr.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: radarr-config
|
||||
- mountPath: /movies
|
||||
name: media-movies
|
||||
- mountPath: /downloads
|
||||
name: media-downloads
|
||||
- mountPath: /run
|
||||
name: radarr-run
|
||||
- mountPath: /tmp
|
||||
name: radarr-tmp
|
||||
{{- end }}
|
||||
{{- if .Values.recyclarr.enabled }}
|
||||
- name: recyclarr
|
||||
image: "{{ .Values.recyclarr.image.repository }}:{{ .Values.recyclarr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
- name: RECYCLARR_CREATE_CONFIG
|
||||
value: "true"
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.recyclarr.env) | nindent 12 }}
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.recyclarr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.recyclarr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.recyclarr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.recyclarr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
{{- toYaml .Values.recyclarr.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: recyclarr-config
|
||||
# Writable scratch space for the .NET runtime so the root
|
||||
# filesystem stays read-only.
|
||||
- mountPath: /tmp
|
||||
name: recyclarr-tmp
|
||||
{{- end }}
|
||||
{{- if .Values.sonarr.enabled }}
|
||||
- name: sonarr
|
||||
image: "{{ .Values.sonarr.image.repository }}:{{ .Values.sonarr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PUID
|
||||
value: {{ .Values.puid | quote }}
|
||||
- name: PGID
|
||||
value: {{ .Values.pgid | quote }}
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.sonarr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 8989
|
||||
protocol: TCP
|
||||
name: sonarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.sonarr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.sonarr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.sonarr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.sonarr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.sonarr.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: sonarr-config
|
||||
- mountPath: /tv
|
||||
name: media-tv
|
||||
- mountPath: /downloads
|
||||
name: media-downloads
|
||||
- mountPath: /run
|
||||
name: sonarr-run
|
||||
- mountPath: /tmp
|
||||
name: sonarr-tmp
|
||||
{{- end }}
|
||||
{{- if .Values.seerr.enabled }}
|
||||
- name: seerr
|
||||
image: "{{ .Values.seerr.image.repository }}:{{ .Values.seerr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.seerr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 5055
|
||||
protocol: TCP
|
||||
name: seerr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.seerr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.seerr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.seerr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.seerr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: {{ .Values.puid }}
|
||||
runAsGroup: {{ .Values.pgid }}
|
||||
{{- toYaml .Values.seerr.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /app/config
|
||||
name: seerr-config
|
||||
- mountPath: /tmp
|
||||
name: seerr-tmp
|
||||
{{- end }}
|
||||
{{- if .Values.bazarr.enabled }}
|
||||
- name: bazarr
|
||||
image: "{{ .Values.bazarr.image.repository }}:{{ .Values.bazarr.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PUID
|
||||
value: {{ .Values.puid | quote }}
|
||||
- name: PGID
|
||||
value: {{ .Values.pgid | quote }}
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
{{- include "freesailarr.env" (dict "root" $ "env" .Values.bazarr.env) | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 6767
|
||||
protocol: TCP
|
||||
name: bazarr
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.bazarr.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.bazarr.readinessProbe | nindent 12 }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.bazarr.startupProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- include "freesailarr.resources" .Values.bazarr.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.bazarr.securityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /config
|
||||
name: bazarr-config
|
||||
- mountPath: /movies
|
||||
name: media-movies
|
||||
- mountPath: /tv
|
||||
name: media-tv
|
||||
- mountPath: /run
|
||||
name: bazarr-run
|
||||
- mountPath: /tmp
|
||||
name: bazarr-tmp
|
||||
{{- end }}
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
# The tun device gluetun needs to create the Wireguard tunnel.
|
||||
- name: dev-net-tun
|
||||
hostPath:
|
||||
path: /dev/net/tun
|
||||
type: CharDevice
|
||||
{{- if or .Values.qbittorrent.enabled .Values.radarr.enabled .Values.sonarr.enabled }}
|
||||
- name: media-downloads
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ required "media.downloads.existingClaim is required when qbittorrent, radarr or sonarr is enabled" .Values.media.downloads.existingClaim }}
|
||||
{{- end }}
|
||||
{{- if or .Values.radarr.enabled .Values.bazarr.enabled }}
|
||||
- name: media-movies
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ required "media.movies.existingClaim is required when radarr or bazarr is enabled" .Values.media.movies.existingClaim }}
|
||||
{{- end }}
|
||||
{{- if or .Values.sonarr.enabled .Values.bazarr.enabled }}
|
||||
- name: media-tv
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ required "media.tv.existingClaim is required when sonarr or bazarr is enabled" .Values.media.tv.existingClaim }}
|
||||
{{- end }}
|
||||
{{- if .Values.qbittorrent.enabled }}
|
||||
- name: qbittorrent-run
|
||||
emptyDir: {}
|
||||
- name: qbittorrent-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.prowlarr.enabled }}
|
||||
- name: prowlarr-run
|
||||
emptyDir: {}
|
||||
- name: prowlarr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.flaresolverr.enabled }}
|
||||
- name: flaresolverr-tmp
|
||||
emptyDir: {}
|
||||
- name: flaresolverr-shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 512Mi
|
||||
{{- end }}
|
||||
{{- if .Values.radarr.enabled }}
|
||||
- name: radarr-run
|
||||
emptyDir: {}
|
||||
- name: radarr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.recyclarr.enabled }}
|
||||
- name: recyclarr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.sonarr.enabled }}
|
||||
- name: sonarr-run
|
||||
emptyDir: {}
|
||||
- name: sonarr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.seerr.enabled }}
|
||||
- name: seerr-tmp
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.bazarr.enabled }}
|
||||
- name: bazarr-run
|
||||
emptyDir: {}
|
||||
- name: bazarr-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" }}
|
||||
{{- $config := index $.Values $app }}
|
||||
{{- if or (eq $app "gluetun") $config.enabled }}
|
||||
- metadata:
|
||||
name: {{ $app }}-config
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ $config.storage.size }}
|
||||
{{- with $.Values.storage.storageClass }}
|
||||
storageClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,571 @@
|
||||
# freesailarr runs gluetun plus the whole media automation stack in a single
|
||||
# StatefulSet pod: every app container shares gluetun's network namespace, the
|
||||
# containers talk to each other over localhost and all traffic leaves through
|
||||
# the Wireguard tunnel.
|
||||
#
|
||||
# Image pinning (documented deviation from the Chart.AppVersion standard):
|
||||
# appVersion pins the gluetun image only. All other app images are pinned via
|
||||
# values (<app>.image.repository / <app>.image.tag) because the stack combines
|
||||
# nine independently versioned upstream projects; the pinned tags are reviewed
|
||||
# alongside every app-version update round, like side images in other charts.
|
||||
|
||||
# Scale the whole chart down without uninstalling it: scaleDown: true renders
|
||||
# every Deployment/StatefulSet with replicas: 0 (and suspends CronJobs) and
|
||||
# takes precedence over any per-workload replicas value. PVCs, Secrets and
|
||||
# Services stay in place; scaleDown: false brings the workloads back up.
|
||||
scaleDown: false
|
||||
|
||||
# Replica count for the whole stack (one pod contains all containers); 0 is
|
||||
# allowed and scales it down. The chart-global scaleDown flag takes precedence.
|
||||
# More than 1 replica is not meaningful: every replica would run its own
|
||||
# isolated stack on its own config volumes.
|
||||
replicas: 1
|
||||
|
||||
secrets:
|
||||
# op:// item path for the gluetun VPN credentials. The 1Password item must
|
||||
# contain the fields "wireguard-private-key" (the Wireguard private key) and
|
||||
# "wireguard-addresses" (the Wireguard interface address, e.g. 10.x.y.z/32).
|
||||
gluetunSecretRef: null
|
||||
|
||||
# User/group the linuxserver.io containers drop to after init (their PUID/PGID
|
||||
# mechanism) and the runAsUser/runAsGroup for the seerr and recyclarr
|
||||
# containers. Media and config volumes must be writable for this user.
|
||||
puid: 1000
|
||||
pgid: 1000
|
||||
# Timezone for all containers.
|
||||
timezone: Europe/Zurich
|
||||
|
||||
securityContext:
|
||||
# Pod-level security context. No pod-level runAsNonRoot/runAsUser: the pod
|
||||
# mixes root containers (gluetun, linuxserver.io images) with non-root ones
|
||||
# (seerr, recyclarr, flaresolverr); users are set per container instead.
|
||||
# fsGroup (keep in sync with pgid) makes freshly provisioned config volumes
|
||||
# group-writable for the non-root containers; fsGroupChangePolicy
|
||||
# OnRootMismatch avoids recursively re-chowning the (large, shared) media
|
||||
# claims on every pod start.
|
||||
pod:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
|
||||
storage:
|
||||
# StorageClass for all config volumeClaimTemplates (null = cluster default).
|
||||
storageClass: null
|
||||
|
||||
# Media libraries as pre-existing PVCs (shared with the separately deployed
|
||||
# Jellyfin) - the chart mounts but never creates them. Each claim is required
|
||||
# as soon as an app that mounts it is enabled:
|
||||
# downloads: qbittorrent, radarr, sonarr
|
||||
# movies: radarr, bazarr
|
||||
# tv: sonarr, bazarr
|
||||
media:
|
||||
downloads:
|
||||
existingClaim: null
|
||||
movies:
|
||||
existingClaim: null
|
||||
tv:
|
||||
existingClaim: null
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
clusterIssuer: null
|
||||
domain: null
|
||||
className: nginx
|
||||
# Extra annotations for the Ingress; merged with the cert-manager
|
||||
# cluster-issuer annotation the chart always sets.
|
||||
annotations: {}
|
||||
|
||||
gluetun:
|
||||
# Image tag comes from Chart.AppVersion (gluetun is the chart's main app).
|
||||
vpn:
|
||||
serviceProvider: mullvad
|
||||
type: wireguard
|
||||
serverCities: Zurich
|
||||
serverCountries: Switzerland
|
||||
dnsUpstreamResolvers: quad9
|
||||
# Comma-separated CIDRs the gluetun firewall allows outbound traffic to
|
||||
# outside the VPN tunnel. Set this to the cluster pod and service CIDRs
|
||||
# (e.g. "10.42.0.0/16,10.43.0.0/16" on k3s) - without it the firewall
|
||||
# blocks cluster-internal egress such as DNS lookups against cluster DNS
|
||||
# and seerr talking to the in-cluster Jellyfin.
|
||||
firewallOutboundSubnets: null
|
||||
storage:
|
||||
# Size of the gluetun config volume (server list cache under /gluetun).
|
||||
size: 1Gi
|
||||
# Extra environment variables, appended to the ones the chart always sets.
|
||||
# Values are rendered through tpl; value, secretKeyRef and configMapKeyRef
|
||||
# forms are supported. (Same for every <app>.env block below.)
|
||||
env: []
|
||||
# gluetun's built-in HTTP health server; the chart binds it to :9999 (env
|
||||
# HEALTH_SERVER_ADDRESS) so the kubelet probes, which connect to the pod IP,
|
||||
# can reach it. It returns 200 only while the VPN tunnel is up and working.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 9999
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 9999
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Budget for image start plus
|
||||
# Wireguard handshake: 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 9999
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
# Limits are optional: memory and ephemeral-storage limits default to
|
||||
# limitFactor x the request, an explicitly set limit always wins, and a
|
||||
# CPU limit is only set when configured explicitly under "limits".
|
||||
# (Same for every <app>.resources block below.)
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 128Mi
|
||||
ephemeralStorage: 50Mi
|
||||
# Deviations from the hardening baseline, forced by gluetun's role as VPN
|
||||
# gateway:
|
||||
# - runs as root (no runAsNonRoot): required to create the tun device and
|
||||
# program the firewall.
|
||||
# - NET_ADMIN capability: creates the Wireguard tunnel and the iptables
|
||||
# rules (firewall, kill switch).
|
||||
# - readOnlyRootFilesystem: false: gluetun rewrites /etc/resolv.conf and
|
||||
# other files on its root filesystem at runtime.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_ADMIN
|
||||
|
||||
qbittorrent:
|
||||
enabled: true
|
||||
image:
|
||||
repository: linuxserver/qbittorrent
|
||||
tag: 5.2.1
|
||||
ingress:
|
||||
enabled: true
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.2
|
||||
memory: 512Mi
|
||||
ephemeralStorage: 100Mi
|
||||
# Deviations from the hardening baseline, forced by the linuxserver.io
|
||||
# images (s6-overlay init that starts as root and drops to PUID/PGID):
|
||||
# - no runAsNonRoot: s6-overlay must start as root to create the abc user
|
||||
# from PUID/PGID and chown /config.
|
||||
# - capabilities: ALL dropped except what the s6-overlay init needs:
|
||||
# CHOWN/FOWNER/DAC_OVERRIDE (taking ownership of /config and /run) and
|
||||
# SETUID/SETGID (dropping from root to the PUID/PGID user).
|
||||
# readOnlyRootFilesystem stays true: the writable paths (/run, /tmp) are
|
||||
# mounted as emptyDirs. (Same block for all linuxserver.io containers:
|
||||
# qbittorrent, prowlarr, radarr, sonarr, bazarr.)
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
|
||||
prowlarr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: linuxserver/prowlarr
|
||||
tag: 2.3.5
|
||||
ingress:
|
||||
enabled: true
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
# The *arr apps expose /ping as an unauthenticated health endpoint.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 9696
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 9696
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 9696
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
# linuxserver.io image - see the qbittorrent securityContext comment.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
|
||||
flaresolverr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/flaresolverr/flaresolverr
|
||||
tag: v3.5.0
|
||||
# No ingress and no config volume: flaresolverr is stateless and only
|
||||
# consumed by prowlarr over localhost.
|
||||
env: []
|
||||
# /health is flaresolverr's dedicated lightweight health endpoint.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8191
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8191
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8191
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.2
|
||||
memory: 512Mi
|
||||
ephemeralStorage: 200Mi
|
||||
# The image runs as its own non-root user; chromium's writable paths (/tmp,
|
||||
# /dev/shm) are emptyDirs, so the full hardening baseline holds.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
radarr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: linuxserver/radarr
|
||||
tag: 6.1.1
|
||||
ingress:
|
||||
enabled: true
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 7878
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 7878
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 7878
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
# linuxserver.io image - see the qbittorrent securityContext comment.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
|
||||
recyclarr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/recyclarr/recyclarr
|
||||
tag: 8.6.0
|
||||
# No ingress: recyclarr has no web UI, it syncs TRaSH guides to radarr and
|
||||
# sonarr on a schedule (daemon mode).
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
# recyclarr serves no HTTP port in daemon mode, so all probes exec the CLI.
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- recyclarr
|
||||
- --version
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- recyclarr
|
||||
- --version
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
exec:
|
||||
command:
|
||||
- recyclarr
|
||||
- --version
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.05
|
||||
memory: 128Mi
|
||||
ephemeralStorage: 50Mi
|
||||
# The image supports running as an arbitrary non-root user; runAsUser and
|
||||
# runAsGroup are set from the global puid/pgid values in the template - do
|
||||
# not set them here.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
sonarr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: linuxserver/sonarr
|
||||
tag: 4.0.17
|
||||
ingress:
|
||||
enabled: true
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8989
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8989
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8989
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
# linuxserver.io image - see the qbittorrent securityContext comment.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
|
||||
seerr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/seerr-team/seerr
|
||||
tag: v3.3.0
|
||||
ingress:
|
||||
enabled: true
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
# /api/v1/settings/public answers unauthenticated once the app is up.
|
||||
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
|
||||
# The startup probe gates liveness/readiness. First boot runs database
|
||||
# migrations: budget 10s * 30 = 300s.
|
||||
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
|
||||
# The image runs fine as an arbitrary non-root user; runAsUser and
|
||||
# runAsGroup are set from the global puid/pgid values in the template - do
|
||||
# not set them here.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
bazarr:
|
||||
enabled: true
|
||||
image:
|
||||
repository: linuxserver/bazarr
|
||||
tag: 1.5.6
|
||||
ingress:
|
||||
enabled: true
|
||||
storage:
|
||||
size: 1Gi
|
||||
env: []
|
||||
# bazarr has no unauthenticated health endpoint; / serves the UI with 200.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 6767
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 6767
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 6767
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
limitFactor: 3
|
||||
requests:
|
||||
cpu: 0.1
|
||||
memory: 256Mi
|
||||
ephemeralStorage: 100Mi
|
||||
# linuxserver.io image - see the qbittorrent securityContext comment.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
Reference in new issue
Block a user