feat(k8s/media): implement and scale apps to zero for migration

This commit is contained in:
2026-02-21 22:56:32 +02:00
parent 43031e7484
commit 942887c997
18 changed files with 1185 additions and 2 deletions

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: media-common-env
namespace: media
data:
PUID: "1027"
PGID: "100"
TZ: Europe/Kyiv
UMASK: "002"

View File

@@ -0,0 +1,135 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: sonarr-db-backup
namespace: media
labels:
app: sonarr-db-backup
spec:
schedule: "0 3 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
metadata:
labels:
app: sonarr-db-backup
spec:
restartPolicy: OnFailure
initContainers:
- name: pg-dump
image: postgres:14.21
env:
- name: PGHOST
value: sonarr-db
- name: PGUSER
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_PASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_NAME
command:
- sh
- -c
- pg_dump --clean --if-exists > /backup/dump.sql
volumeMounts:
- name: backup
mountPath: /backup
containers:
- name: rclone-upload
image: rclone/rclone:1.69
command:
- sh
- -c
- rclone copy /backup/dump.sql r2crypt:sonarr/ --config /config/rclone/rclone.conf
volumeMounts:
- name: backup
mountPath: /backup
- name: rclone-config
mountPath: /config/rclone
readOnly: true
volumes:
- name: backup
emptyDir: {}
- name: rclone-config
secret:
secretName: rclone-config
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: radarr-db-backup
namespace: media
labels:
app: radarr-db-backup
spec:
schedule: "0 3 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
metadata:
labels:
app: radarr-db-backup
spec:
restartPolicy: OnFailure
initContainers:
- name: pg-dump
image: postgres:14.21
env:
- name: PGHOST
value: radarr-db
- name: PGUSER
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_PASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_NAME
command:
- sh
- -c
- pg_dump --clean --if-exists > /backup/dump.sql
volumeMounts:
- name: backup
mountPath: /backup
containers:
- name: rclone-upload
image: rclone/rclone:1.69
command:
- sh
- -c
- rclone copy /backup/dump.sql r2crypt:radarr/ --config /config/rclone/rclone.conf
volumeMounts:
- name: backup
mountPath: /backup
- name: rclone-config
mountPath: /config/rclone
readOnly: true
volumes:
- name: backup
emptyDir: {}
- name: rclone-config
secret:
secretName: rclone-config

View File

@@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: prowlarr
namespace: media
labels:
app: prowlarr
spec:
replicas: 0
strategy:
type: Recreate
selector:
matchLabels:
app: prowlarr
template:
metadata:
labels:
app: prowlarr
spec:
initContainers:
- name: wait-for-config
image: busybox:1.37
command:
- sh
- -c
- until ls /config > /dev/null 2>&1; do echo "Waiting for config volume..."; sleep 5; done
volumeMounts:
- name: config
mountPath: /config
containers:
- name: prowlarr
image: lscr.io/linuxserver/prowlarr:2.3.0.5236-ls137
envFrom:
- configMapRef:
name: media-common-env
ports:
- containerPort: 9696
name: http
protocol: TCP
livenessProbe:
httpGet:
port: 9696
path: /ping
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 5
readinessProbe:
httpGet:
port: 9696
path: /ping
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
persistentVolumeClaim:
claimName: prowlarr-config

View File

@@ -0,0 +1,75 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: qbittorrent
namespace: media
labels:
app: qbittorrent
spec:
replicas: 0
strategy:
type: Recreate
selector:
matchLabels:
app: qbittorrent
template:
metadata:
labels:
app: qbittorrent
spec:
initContainers:
- name: wait-for-nfs
image: busybox:1.37
command:
- sh
- -c
- until ls /media > /dev/null 2>&1; do echo "Waiting for NFS..."; sleep 5; done
volumeMounts:
- name: media
mountPath: /media
containers:
- name: qbittorrent
image: lscr.io/linuxserver/qbittorrent:5.1.4-r2-ls441
envFrom:
- configMapRef:
name: media-common-env
env:
- name: WEBUI_PORT
value: "8114"
ports:
- containerPort: 8114
name: webui
protocol: TCP
- containerPort: 23312
name: bt-tcp
protocol: TCP
- containerPort: 23312
name: bt-udp
protocol: UDP
livenessProbe:
httpGet:
port: 8114
path: /
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
port: 8114
path: /
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: config
mountPath: /config
- name: media
mountPath: /media
volumes:
- name: config
persistentVolumeClaim:
claimName: qbittorrent-config
- name: media
persistentVolumeClaim:
claimName: media-nfs

View File

@@ -0,0 +1,89 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: radarr
namespace: media
labels:
app: radarr
spec:
replicas: 0
strategy:
type: Recreate
selector:
matchLabels:
app: radarr
template:
metadata:
labels:
app: radarr
spec:
initContainers:
- name: wait-for-nfs
image: busybox:1.37
command:
- sh
- -c
- until ls /media > /dev/null 2>&1; do echo "Waiting for NFS..."; sleep 5; done
volumeMounts:
- name: media
mountPath: /media
- name: wait-for-db
image: busybox:1.37
command:
- sh
- -c
- until nc -z radarr-db 5432; do echo "Waiting for database..."; sleep 2; done
- name: init-log-db
image: postgres:14.21
env:
- name: PGHOST
value: radarr-db
- name: PGUSER
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_PASSWORD
command:
- sh
- -c
- psql -d postgres -c 'CREATE DATABASE "radarr-log"' || true
containers:
- name: radarr
image: lscr.io/linuxserver/radarr:6.0.4.10291-ls293
envFrom:
- configMapRef:
name: media-common-env
ports:
- containerPort: 7878
name: http
protocol: TCP
livenessProbe:
httpGet:
port: 7878
path: /ping
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 5
readinessProbe:
httpGet:
port: 7878
path: /ping
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: config
mountPath: /config
- name: media
mountPath: /media
volumes:
- name: config
persistentVolumeClaim:
claimName: radarr-config
- name: media
persistentVolumeClaim:
claimName: media-nfs

View File

@@ -0,0 +1,89 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarr
namespace: media
labels:
app: sonarr
spec:
replicas: 0
strategy:
type: Recreate
selector:
matchLabels:
app: sonarr
template:
metadata:
labels:
app: sonarr
spec:
initContainers:
- name: wait-for-nfs
image: busybox:1.37
command:
- sh
- -c
- until ls /media > /dev/null 2>&1; do echo "Waiting for NFS..."; sleep 5; done
volumeMounts:
- name: media
mountPath: /media
- name: wait-for-db
image: busybox:1.37
command:
- sh
- -c
- until nc -z sonarr-db 5432; do echo "Waiting for database..."; sleep 2; done
- name: init-log-db
image: postgres:14.21
env:
- name: PGHOST
value: sonarr-db
- name: PGUSER
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_USER
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_PASSWORD
command:
- sh
- -c
- psql -d postgres -c 'CREATE DATABASE "sonarr-log"' || true
containers:
- name: sonarr
image: lscr.io/linuxserver/sonarr:4.0.16.2944-ls303
envFrom:
- configMapRef:
name: media-common-env
ports:
- containerPort: 8989
name: http
protocol: TCP
livenessProbe:
httpGet:
port: 8989
path: /ping
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 5
readinessProbe:
httpGet:
port: 8989
path: /ping
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: config
mountPath: /config
- name: media
mountPath: /media
volumes:
- name: config
persistentVolumeClaim:
claimName: sonarr-config
- name: media
persistentVolumeClaim:
claimName: media-nfs

View File

@@ -0,0 +1,159 @@
# Middleware for API clients (NZB360 etc.) that use HTTP basic auth.
# Uses Authelia's legacy verify endpoint which responds with 401 +
# WWW-Authenticate instead of redirecting to the login page.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authelia-basic
namespace: media
spec:
forwardAuth:
address: http://authelia-authelia.authelia.svc.cluster.local/api/verify?auth=basic
trustForwardHeader: true
authResponseHeaders:
- Remote-User
- Remote-Groups
- Remote-Email
- Remote-Name
---
# qBittorrent - browser access via Authelia SSO
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: qbittorrent
namespace: media
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: authelia-chain-authelia-authelia-auth@kubernetescrd
spec:
tls:
- hosts:
- ${QBITTORRENT_HOST}
secretName: qbittorrent-tls
rules:
- host: ${QBITTORRENT_HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: qbittorrent
port:
number: 8114
---
# qBittorrent API - basic auth for NZB360.
# Uses IngressRoute with HeaderRegexp so only requests carrying an
# Authorization: Basic header are matched; browser XHR/fetch calls
# (which rely on the Authelia session cookie) fall through to the
# standard SSO Ingress above.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: qbittorrent-api
namespace: media
spec:
entryPoints:
- websecure
routes:
- match: Host(`${QBITTORRENT_HOST}`) && PathPrefix(`/api/v2`) && HeaderRegexp(`Authorization`, `^Basic .+$`)
kind: Rule
middlewares:
- name: authelia-basic
services:
- name: qbittorrent
port: 8114
tls:
secretName: qbittorrent-tls
---
# Sonarr - browser access via Authelia SSO
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sonarr
namespace: media
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: authelia-chain-authelia-authelia-auth@kubernetescrd
spec:
tls:
- hosts:
- ${SONARR_HOST}
secretName: sonarr-tls
rules:
- host: ${SONARR_HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sonarr
port:
number: 8989
---
# Sonarr API - basic auth for NZB360
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: sonarr-api
namespace: media
spec:
entryPoints:
- websecure
routes:
- match: Host(`${SONARR_HOST}`) && PathPrefix(`/api/v3`) && HeaderRegexp(`Authorization`, `^Basic .+$`)
kind: Rule
middlewares:
- name: authelia-basic
services:
- name: sonarr
port: 8989
tls:
secretName: sonarr-tls
---
# Radarr - browser access via Authelia SSO
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: radarr
namespace: media
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: authelia-chain-authelia-authelia-auth@kubernetescrd
spec:
tls:
- hosts:
- ${RADARR_HOST}
secretName: radarr-tls
rules:
- host: ${RADARR_HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: radarr
port:
number: 7878
---
# Radarr API - basic auth for NZB360
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: radarr-api
namespace: media
spec:
entryPoints:
- websecure
routes:
- match: Host(`${RADARR_HOST}`) && PathPrefix(`/api/v3`) && HeaderRegexp(`Authorization`, `^Basic .+$`)
kind: Rule
middlewares:
- name: authelia-basic
services:
- name: radarr
port: 7878
tls:
secretName: radarr-tls

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: media

View File

@@ -0,0 +1,145 @@
# Default deny all ingress in the media namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: media
spec:
podSelector: {}
policyTypes:
- Ingress
---
# Allow ingress controller to reach qbittorrent, sonarr, radarr
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-controller
namespace: media
spec:
podSelector:
matchExpressions:
- key: app
operator: In
values:
- qbittorrent
- sonarr
- radarr
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik
---
# sonarr-db: only reachable from sonarr and backup jobs
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: sonarr-db
namespace: media
spec:
podSelector:
matchLabels:
app: sonarr-db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: sonarr
- podSelector:
matchLabels:
app: sonarr-db-backup
---
# radarr-db: only reachable from radarr and backup jobs
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: radarr-db
namespace: media
spec:
podSelector:
matchLabels:
app: radarr-db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: radarr
- podSelector:
matchLabels:
app: radarr-db-backup
---
# Allow prowlarr to receive connections from sonarr and radarr
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-internal-comms
namespace: media
spec:
podSelector:
matchLabels:
app: prowlarr
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: sonarr
- podSelector:
matchLabels:
app: radarr
---
# Allow prowlarr to reach sonarr, radarr, and qbittorrent
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-prowlarr-to-apps
namespace: media
spec:
podSelector:
matchExpressions:
- key: app
operator: In
values:
- sonarr
- radarr
- qbittorrent
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: prowlarr
---
# Allow qbittorrent to receive connections from sonarr, radarr, and external BT traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-qbittorrent
namespace: media
spec:
podSelector:
matchLabels:
app: qbittorrent
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: sonarr
- podSelector:
matchLabels:
app: radarr
- ports:
- port: 23312
protocol: TCP
- port: 23312
protocol: UDP

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: media-nfs
spec:
capacity:
storage: 1Ti
accessModes:
- ReadWriteMany
storageClassName: ""
persistentVolumeReclaimPolicy: Retain
mountOptions:
- hard
- nointr
nfs:
server: synology.storage.lviv
path: ${MEDIA_NFS_PATH}

View File

@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: rclone-config
namespace: media
stringData:
rclone.conf: ENC[AES256_GCM,data:aTthn7P4ESDNtqRDW+R7RI1e0B2ftgHg6406vqHtsUPW25SEaNUiGuWlSY/BoiCuagBBM6TM+Gow2XrtTQauPND1irb5D5xyhcphFM5uF+8dE4qNJV4J09NjBd7Lzdp1udej2BJoRAeCwgAEwq8857log4TRwrbGzNrfRKOPrz7D3okBQP2hyxfZ85dlQH0ojUEodPi4U3mpwgXc8Kb0JhziZc1KjHbIzZ1/GkaKcYLI8Rl8q7esdqxnrA51sssWVkmSC4zoiPjwPwUWifSNMb7KC8L8bXRIVdpP8/f4rDyYJoa9uC7GW44nYIBVfLJrM015ZtpSzh05/IS9ev6N2REkjudLmOwwvOw52s8dKh1IRXixcV1JPk3knam2jnezYcnvmLYgyReryZFRFFP0xgaVOmZa5is=,iv:zrFW+ssUTt8T14TZz5n30rVr792FiDYxr89BdIIxn1c=,tag:XVtMvVbuo1vQU3hp8VTnHQ==,type:str]
sops:
age:
- recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlR1BZak5zZWdmZ2g3YktL
Q1lqZzVTWEtpVGhqSk9WamxmRm1WSFNnNkN3CjNzMEtyMnh6bEtjbW9BY0NGZm1G
dnMyOW5DVFpZT3JMVGNxVW9raWZkSzAKLS0tIHNHMXdZTlhXYWFRYmYvS21tQUps
MXB5QVkyZDZlQWlMd1NqdEl1V0g0d00KMSyMsWeN5oEx3s5Zh3x9MHiRywFvRuZm
lZEdl0ho90lJ8m+rPHIT+pI7vBMHLB3mJiBfIVR4KJQRUkhPjGSMRQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-22T16:12:51Z"
mac: ENC[AES256_GCM,data:gUyVh53pNCUHfW9+pww9sQ10kL/U92N0AV1Ys+fKt3W/wq4msyXLErQVVJBrHoPQo/ncKUMbCUGvMNHsUnJT7H8g8LuqMUxLbZhFju6rLHrgg+1hIKeA1cStOlTgBTQLbXA2gfsclQQ+nAf9zeAsgz3MfuK8PEHs3U2O8cSrtP4=,iv:fQd6lggTw3OI+8lZ1BOZvD+lLt7p5wlelf59sFsQHZE=,tag:4V8do9D3IiNPDDR5GRLXOg==,type:str]
encrypted_regex: ^(data|stringData|email)$
version: 3.11.0

View File

@@ -0,0 +1,27 @@
apiVersion: v1
kind: Secret
metadata:
name: media-db-credentials
namespace: media
stringData:
SONARR_DB_USER: ENC[AES256_GCM,data:4hElKqu/,iv:FxKGg5gBSVi6wwp7Evz/BeU1HXgkBSjlk5RqLm65qws=,tag:DeCX+CgbNUJVkvTLu7uBoQ==,type:str]
SONARR_DB_NAME: ENC[AES256_GCM,data:idbioCQ81Vd8oz4=,iv:uD2fJ/4ZANiEB/V1zH1nfLF20LVOCuh6Zzrb1KQeekc=,tag:/Cp0K2U488VBQdtJX3lvmQ==,type:str]
SONARR_DB_PASSWORD: ENC[AES256_GCM,data:Ut+XErV4XN60lgiLGZ6XTWEXWgLF8NHkrUUXv4M2XFNsL0/DvL4qfCTKigCP8FR5ayBNhXAoF3cQ467WUFpvUA==,iv:hfN6h0lLqWd7uSc/EHsZwPT2sjSyF0Oyr1HpCDfWkV8=,tag:Idb2bMtM/4OezqL2ttN2CA==,type:str]
RADARR_DB_USER: ENC[AES256_GCM,data:q8IuzrfO,iv:MVretLwHLhd5fsYYRa9jyq59ebcYdZB1flYWoBwrTII=,tag:+LV02NiQyd+T6mwqLzET5A==,type:str]
RADARR_DB_NAME: ENC[AES256_GCM,data:VxtGKMV+LO/exOo=,iv:6RUjbk+/tw1GQYK4nqElhELo5lqgvYhR7gYZIaLDOlM=,tag:FGexxymrlwncNcVMOLZ8/Q==,type:str]
RADARR_DB_PASSWORD: ENC[AES256_GCM,data:SB2AGVPdlhGGreSKUQ9fxJn1O6wyPMucy+knwWhQrxqyijzMgCJIYFRv8Gr3gpns3Zo1EvX1faxc/EfN19XSxw==,iv:SeqSpGuCTmN4V3V9AGWSD664Z2mMdZU9tWM2uTZYusM=,tag:xm9J4s5F8x8ulMualpJVtQ==,type:str]
sops:
age:
- recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBINHB0V21XRklkY2p5WGZE
a0UxaDVRODZoZUtlYm1nR1lnLzdITlZrZG44CnhwRDVkS1lhSWsrNU5ObE5kSWl5
bVZhZUt3NFIzdUdWYVZZTXV4d09mVGcKLS0tIHhrTUFvdDRKZ1NyZ2llaHZQTWhK
ZVZmNzRlOVRnK1FBdTc1ZmU1bzNidU0KBP4sIZzuVn7PU17e09p6Td0sMG7K+NsQ
AcvdVNFr6mOfivGn86Ao1R4xPE4ANqZfrNQCgIoKxsOQPcc13vOJbA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-22T10:20:21Z"
mac: ENC[AES256_GCM,data:36OEPmj2rFjEAWU9RG8DvvwYJGuMmLKZ9e2fW48N8SBajaGiSGLBfRkRKHy5ncTtNaGmlfBuRtM5CoA9/qaxNQJ/q7DefSx0E0KuS9zTamfRcf7oTZEqmaK3v8r8AXmCTi4od5eiISPT5d9oN48V+aNLd3iFLwt9fSUQ8dPOQ3A=,iv:yP145UUDKBU9NSPlIZ+wAeKB/TDBv4OUMlCvayzKTmU=,tag:Qt383JbWxzB3/GxF32NezA==,type:str]
encrypted_regex: ^(data|stringData|email)$
version: 3.11.0

View File

@@ -0,0 +1,124 @@
apiVersion: v1
kind: Service
metadata:
name: qbittorrent
namespace: media
labels:
app: qbittorrent
spec:
type: ClusterIP
ports:
- name: 8114-8114
port: 8114
targetPort: 8114
protocol: TCP
selector:
app: qbittorrent
---
apiVersion: v1
kind: Service
metadata:
name: qbittorrent-bt
namespace: media
labels:
app: qbittorrent
spec:
type: NodePort
ports:
- name: bt-tcp
port: 23312
targetPort: 23312
nodePort: 30312
protocol: TCP
- name: bt-udp
port: 23312
targetPort: 23312
nodePort: 30312
protocol: UDP
selector:
app: qbittorrent
---
apiVersion: v1
kind: Service
metadata:
name: prowlarr
namespace: media
labels:
app: prowlarr
spec:
type: ClusterIP
ports:
- name: 9696-9696
port: 9696
targetPort: 9696
protocol: TCP
selector:
app: prowlarr
---
apiVersion: v1
kind: Service
metadata:
name: sonarr
namespace: media
labels:
app: sonarr
spec:
type: ClusterIP
ports:
- name: 8989-8989
port: 8989
targetPort: 8989
protocol: TCP
selector:
app: sonarr
---
apiVersion: v1
kind: Service
metadata:
name: radarr
namespace: media
labels:
app: radarr
spec:
type: ClusterIP
ports:
- name: 7878-7878
port: 7878
targetPort: 7878
protocol: TCP
selector:
app: radarr
---
apiVersion: v1
kind: Service
metadata:
name: sonarr-db
namespace: media
labels:
app: sonarr-db
spec:
type: ClusterIP
ports:
- name: 5432-5432
port: 5432
targetPort: 5432
protocol: TCP
selector:
app: sonarr-db
---
apiVersion: v1
kind: Service
metadata:
name: radarr-db
namespace: media
labels:
app: radarr-db
spec:
type: ClusterIP
ports:
- name: 5432-5432
port: 5432
targetPort: 5432
protocol: TCP
selector:
app: radarr-db

View File

@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: radarr-db
namespace: media
labels:
app: radarr-db
spec:
replicas: 1
serviceName: radarr-db
selector:
matchLabels:
app: radarr-db
template:
metadata:
labels:
app: radarr-db
spec:
securityContext:
runAsUser: 1027
runAsGroup: 100
fsGroup: 100
containers:
- name: postgres
image: postgres:14.21
env:
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_NAME
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: media-db-credentials
key: RADARR_DB_PASSWORD
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
name: postgres
protocol: TCP
livenessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 5
readinessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: radarr-db

View File

@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sonarr-db
namespace: media
labels:
app: sonarr-db
spec:
replicas: 1
serviceName: sonarr-db
selector:
matchLabels:
app: sonarr-db
template:
metadata:
labels:
app: sonarr-db
spec:
securityContext:
runAsUser: 1027
runAsGroup: 100
fsGroup: 100
containers:
- name: postgres
image: postgres:14.21
env:
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_NAME
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: media-db-credentials
key: SONARR_DB_PASSWORD
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
name: postgres
protocol: TCP
livenessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 5
readinessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: sonarr-db

View File

@@ -0,0 +1,91 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: media-nfs
namespace: media
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: media-nfs
resources:
requests:
storage: 1Ti
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: qbittorrent-config
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prowlarr-config
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarr-config
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: radarr-config
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarr-db
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: radarr-db
namespace: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 5Gi

View File

@@ -6,6 +6,10 @@ metadata:
stringData: stringData:
LUBELOGGER_HOST: ENC[AES256_GCM,data:OvDY/XIE/YW8lSDJmhHYI63r4eLQOojsMjjkUIge,iv:v1JafZB4cmVFjX+yA7FjjoXfx7jPpZQaq1HyXvNXvsY=,tag:+h5Gg/q3bKP3l7xCNLaBqA==,type:str] LUBELOGGER_HOST: ENC[AES256_GCM,data:OvDY/XIE/YW8lSDJmhHYI63r4eLQOojsMjjkUIge,iv:v1JafZB4cmVFjX+yA7FjjoXfx7jPpZQaq1HyXvNXvsY=,tag:+h5Gg/q3bKP3l7xCNLaBqA==,type:str]
AUTHELIA_DOMAIN: ENC[AES256_GCM,data:mioy6n5AqiN8jPU9cMTO,iv:HFjyN0UCohNxuJkmt9dgcvnjHSTSAr7svin9Fjjykk8=,tag:2aknDljwRFGJBTzzmQ2UFA==,type:str] AUTHELIA_DOMAIN: ENC[AES256_GCM,data:mioy6n5AqiN8jPU9cMTO,iv:HFjyN0UCohNxuJkmt9dgcvnjHSTSAr7svin9Fjjykk8=,tag:2aknDljwRFGJBTzzmQ2UFA==,type:str]
QBITTORRENT_HOST: ENC[AES256_GCM,data:OxaHKav3STl9FKV0qtJ0FpfTHgSrvCXrkP25,iv:wzfie/2YkZH0A6R3wF3BJV8U5aKhewjQ7c9AfstFQMI=,tag:rxHw0XhzqBm/rN4OytclgQ==,type:str]
SONARR_HOST: ENC[AES256_GCM,data:dMEgQYHxa0tIilY8HPkppFgOWzH52Q==,iv:yCUF8ZVOSY2IfULa9B44ALGjgFyZyQrQWPZsyQzqwbM=,tag:kfhsZhPE1rZNYUJ3X2rRSQ==,type:str]
RADARR_HOST: ENC[AES256_GCM,data:lsWSI+D/qzru3qHGvJjKRepYEGHe5Q==,iv:cq9yNVCMuon+ZUpFwfkwBvVg9oIT0MXagjDi6l/29YA=,tag:koGF34SrtHe0brRiNKP8rg==,type:str]
MEDIA_NFS_PATH: ENC[AES256_GCM,data:BSDMu0n2Vx4koYBIMF8=,iv:c9kGdcTxObNaaaTzEhSRkyHvo+dxSN+o+96n9UqJieU=,tag:W9+MbyAuK85xajjwntRi0Q==,type:str]
sops: sops:
age: age:
- recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc - recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc
@@ -17,7 +21,7 @@ sops:
LzhUN3Z4cExIL1IyS3ZCNWh5aWpLbDgKQ7c3MmLykA00NaLoctKVDfJvPqTqh3Ia LzhUN3Z4cExIL1IyS3ZCNWh5aWpLbDgKQ7c3MmLykA00NaLoctKVDfJvPqTqh3Ia
cDZJUc6jYJXOJYM6YYyZOYcCL2z8V2RpIfA9sPg8PB2eiipZxjk+Cg== cDZJUc6jYJXOJYM6YYyZOYcCL2z8V2RpIfA9sPg8PB2eiipZxjk+Cg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-21T22:09:11Z" lastmodified: "2026-02-22T10:48:28Z"
mac: ENC[AES256_GCM,data:KXaSGwX9gmEgFvmVuSblrmGqNKS5nooAO1GJsCTP/QkAvTMzSsVZZcoUreQY3HU0bLxWABzb8ZPgOGVBNvL05sB0uwGwvUvpsUZp5ryYWVdKxsdCGeZpMdsXFbwIprdI/SerEnHzDvXCV7XKes22N8A5wsVYZXm6s+grkJhAR68=,iv:yLQR5N19/cThHRH4cqiut0GEXBDuo4CEzuKlC6G4N3I=,tag:j9lEXa/b3Kkf9+Ph2f1rkw==,type:str] mac: ENC[AES256_GCM,data:qw4wqmlHntZiybI2VPKcQEUJg0AG8kgWn7hEw6WlCltf7STbRWLLedR5TTRmKpcCQofE4T02ZZPLbzVVP7tSer1S4nAYSMwkfQhgkZ8DVvn/E2O/Yxzja/DXLV9tNMYpYexSkfVX8zYRS1zdd48VBgA4P4N0WR65kvoUnIScrgA=,iv:+brh3Gehjes1G6wNxPVDgHltI80Ih5d21Va/ADJxxCI=,tag:pay3NKWyLYF4BPZnCszW0Q==,type:str]
encrypted_regex: ^(data|stringData|email)$ encrypted_regex: ^(data|stringData|email)$
version: 3.11.0 version: 3.11.0

View File

@@ -16,6 +16,9 @@ spec:
namespace: flux-system namespace: flux-system
interval: 1m0s interval: 1m0s
values: values:
providers:
kubernetesCRD:
allowCrossNamespace: true
service: service:
type: ClusterIP type: ClusterIP
ports: ports: