Compare commits

..

5 Commits

Author SHA1 Message Date
unkin-agent 0cd5446d6b Replace legacy jellyfin app with fafflix (adult, cheeztv pattern)
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
Rebuild the adult media instance as 'fafflix' following the same HA pattern
as the kids instance (cheeztv): Postgres-backed jellyfin-ha fork, Valkey
transcode-lease store, shared-RWX transcode, CNPG + k8up/restic backups,
static CephFS media PVs.

- Add apps/base/fafflix (namespace/labels/secrets/buckets/Vault path fafflix-*)
- fafflix mounts the shared movies/tv subvolumes' adult subtree at
  /media/{movies,tv} plus the kids subtree at /media/{movies,tv}-kids so it can
  resume kids content started on cheeztv; new unique static PV volumeHandles
- Keep serving the legacy hostname jellyfin.k8s.syd1.au.unkin.net (fafflix-tls);
  dedicated fafflix domain deferred, no new public host
- config PVC on cephfs-raid5-delete
- Remove apps/base/jellyfin + overlay; swap jellyfin->fafflix in the media
  ApplicationSet glob and AppProject namespace destination
- No data currently on the adult instance, so the wipe/replace is sanctioned
2026-08-24 22:50:15 +10:00
unkin-agent f272a1dccb Fix storage class: cephfs-raid5-retain → cephfs-raid5-delete (cheeztv-config)
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
2026-08-24 22:42:40 +10:00
unkin-agent b4f3491b18 Fix stale artifactapi comment in redis_exporter config
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
2026-08-24 22:30:17 +10:00
unkin-agent a7a96bd879 fix: convert third-party image URLs from artifactapi proxies to upstream registries
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
- valkey: docker.io/valkey/valkey (was artifactapi/dockerhub)
- redis_exporter: docker.io/oliver006/redis_exporter (was artifactapi/dockerhub)

Kept docker-internal images unchanged per policy.
2026-08-24 22:25:59 +10:00
unkin-agent a4253f09cd Add cheeztv kids Jellyfin instance and fafflix kids mounts
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
cheeztv is a second, kids-only Jellyfin instance in the media project. Kids
titles need a separate, safe library front (cheeztv.unkin.net) while staying
resumable in the existing adult instance (fafflix).

- apps/base/cheeztv: mirror the jellyfin (fafflix) HA stack 1:1 - same
  jellyfin-ha:v0.1.3 fork image, 2-replica StatefulSet, RWX transcode PVC,
  Intel iGPU transcode, CNPG Postgres + PgBouncer, Valkey transcode store,
  k8up config backup, VMPodScrape. Media mounts subPath kids on the shared
  movies/tv CephFS subvolumes so cheeztv sees only the kids trees.
- Separate state: own namespace, config PVC, cheeztv-postgres cluster,
  cheeztv-valkey, and cnpg-cheeztv / cheeztv-config-backup buckets - nothing
  shared with fafflix.
- Ingress/DNS: cheeztv.unkin.net (internal Traefik + external-dns at
  198.18.200.4, cert-manager cheeztv-tls) plus the cluster hostname variant
  cheeztv.k8s.syd1.au.unkin.net, matching how fafflix and logviewer.unkin.net
  are wired.
- fafflix: add movies/kids and tvshows/kids subPath mounts alongside its
  existing media mounts so kids libraries are browsable/resumable there; its
  existing mounts, hostname and ingress are untouched.
- Register cheeztv in the media ApplicationSet generator and AppProject
  destinations.
2026-08-24 22:08:04 +10:00
23 changed files with 1 additions and 947 deletions
-3
View File
@@ -19,8 +19,5 @@ resources:
- radarr
- prowlarr
- nzbget
- sonarr-kids
- radarr-kids
- nzbget-kids
- arrproxy
- mediamover
@@ -1,142 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nzbget-kids
namespace: arrstack
spec:
replicas: 1
strategy:
# RWO config PVC + single queue state: never run two pods at once.
type: Recreate
selector:
matchLabels:
app: nzbget-kids
template:
metadata:
labels:
app: nzbget-kids
spec:
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
initContainers:
# Seed download layout onto the kids media subtrees (not /config or an
# emptyDir) so completed kids downloads land beside the kids arr libraries
# and imports are same-filesystem hardlink moves. The media mounts use the
# tvshows/kids and movies/kids subPaths, so the in-container paths match
# the adult nzbget while the data stays scoped to the kids subtree. Reuses
# the image's own template and appends the path/category overrides once;
# the grep guard keeps re-runs idempotent so admin UI edits survive.
- name: seed-config
image: docker.io/linuxserver/nzbget:version-v26.2
command:
- sh
- -c
- |
set -e
if [ ! -f /config/nzbget.conf ]; then
cp /app/nzbget/share/nzbget/nzbget.conf /config/nzbget.conf
fi
if ! grep -q '# arrstack-managed' /config/nzbget.conf; then
cat >> /config/nzbget.conf << 'CONF'
# arrstack-managed download layout (appended once; last value wins).
# Downloads land on the shared media PVCs by category so sonarr-kids/
# radarr-kids import with atomic hardlink moves (download dir +
# library share one filesystem per media type). InterDir is empty:
# nzbget writes each download straight into its category DestDir, so
# BOTH tv and movies stay on their own PVC with no cross-filesystem
# intermediate copy.
MainDir=/media/tv
InterDir=
DestDir=/media/tv/downloads
NzbDir=/config/nzb
QueueDir=/config/queue
TempDir=/config/tmp
ControlIP=0.0.0.0
ControlPort=6789
Category1.Name=tv
Category1.DestDir=/media/tv/downloads
Category2.Name=movies
Category2.DestDir=/media/movies/downloads
CONF
fi
mkdir -p /media/tv/series /media/tv/downloads /media/movies/films /media/movies/downloads
chown 1000:1000 /config/nzbget.conf \
/media/tv /media/tv/series /media/tv/downloads \
/media/movies /media/movies/films /media/movies/downloads
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: config
mountPath: /config
- name: media-tv
mountPath: /media/tv
subPath: tvshows/kids
- name: media-movies
mountPath: /media/movies
subPath: movies/kids
containers:
- name: nzbget
image: docker.io/linuxserver/nzbget:version-v26.2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 6789
protocol: TCP
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: Australia/Sydney
livenessProbe:
# nzbget's root path requires auth (401); a TCP check is the
# dependency-free liveness signal for the web/JSON-RPC server.
tcpSocket:
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
# Headroom for par2 repair + unpack of large downloads.
cpu: "2"
memory: 2Gi
volumeMounts:
- name: config
mountPath: /config
- name: media-tv
mountPath: /media/tv
subPath: tvshows/kids
- name: media-movies
mountPath: /media/movies
subPath: movies/kids
volumes:
- name: config
persistentVolumeClaim:
claimName: nzbget-kids-config
- name: media-tv
persistentVolumeClaim:
claimName: media-tv
- name: media-movies
persistentVolumeClaim:
claimName: media-movies
@@ -1,8 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pvc-config.yaml
- deployment.yaml
- service.yaml
@@ -1,16 +0,0 @@
---
# NZBGet (kids) config + queue/temp state. RWO on cephrbd (block, fast-delete).
# The download data itself lives on the shared media PVCs, not here.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nzbget-kids-config
namespace: arrstack
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: cephrbd-fast-delete
volumeMode: Filesystem
@@ -1,15 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: nzbget-kids
namespace: arrstack
spec:
ports:
- name: http
port: 6789
protocol: TCP
targetPort: http
selector:
app: nzbget-kids
type: ClusterIP
@@ -86,30 +86,6 @@ spec:
connectionLimit: -1
passwordSecret:
name: prowlarr-db
- name: sonarr-kids
ensure: present
comment: Sonarr (kids) application role (owns sonarr-kids-main)
login: true
superuser: false
createdb: false
createrole: false
inherit: true
replication: false
connectionLimit: -1
passwordSecret:
name: sonarr-kids-db
- name: radarr-kids
ensure: present
comment: Radarr (kids) application role (owns radarr-kids-main)
login: true
superuser: false
createdb: false
createrole: false
inherit: true
replication: false
connectionLimit: -1
passwordSecret:
name: radarr-kids-db
enablePDB: true
enableSuperuserAccess: false
failoverDelay: 0
@@ -1,15 +0,0 @@
---
# Per-app database owned by the radarr-kids managed role. The fork's provider
# runs its own schema migrations on first start (advisory-locked, so only one
# replica migrates). retain: the database survives a Database CRD delete.
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: radarr-kids-main
namespace: arrstack
spec:
cluster:
name: arrstack-postgres
name: radarr-kids-main
owner: radarr-kids
databaseReclaimPolicy: retain
@@ -1,15 +0,0 @@
---
# Per-app database owned by the sonarr-kids managed role. The fork's provider
# runs its own schema migrations on first start (advisory-locked, so only one
# replica migrates). retain: the database survives a Database CRD delete.
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: sonarr-kids-main
namespace: arrstack
spec:
cluster:
name: arrstack-postgres
name: sonarr-kids-main
owner: sonarr-kids
databaseReclaimPolicy: retain
@@ -9,5 +9,3 @@ resources:
- database-sonarr.yaml
- database-radarr.yaml
- database-prowlarr.yaml
- database-sonarr-kids.yaml
- database-radarr-kids.yaml
@@ -58,37 +58,3 @@ spec:
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: sonarr-kids-db
namespace: arrstack
spec:
destination:
create: true
name: sonarr-kids-db
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/arrstack/default/sonarr-kids-db
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: radarr-kids-db
namespace: arrstack
spec:
destination:
create: true
name: radarr-kids-db
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/arrstack/default/radarr-kids-db
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
@@ -1,25 +0,0 @@
---
# Non-secret env for the -unkin2 fork (kids tier). Identical mechanism to the
# adult radarr, pointed at its own shared-Postgres database (radarr-kids-main)
# and its own UrlBase (/3df803/radarr) so arrproxy path-routing reaches the kids
# instance separately. Shares the one arrstack Valkey (keys namespaced by the
# fork's radarr:ratelimit: prefix). User/Password/ApiKey come from Secrets (see
# deployment.yaml), not here.
apiVersion: v1
kind: ConfigMap
metadata:
name: radarr-kids-env
namespace: arrstack
data:
Radarr__Postgres__Host: arrstack-postgres-rw.arrstack.svc.cluster.local
Radarr__Postgres__Port: "5432"
Radarr__Postgres__MainDb: radarr-kids-main
Radarr__Log__DbEnabled: "false"
Radarr__Auth__Method: External
Radarr__Auth__Required: DisabledForLocalAddresses
Radarr__App__InstanceName: Radarr
Radarr__Server__Port: "7878"
Radarr__Server__UrlBase: /3df803/radarr
Radarr__Update__Mechanism: External
Radarr__Redis__Host: valkey-arrstack-valkey.arrstack.svc.cluster.local
Radarr__Redis__Port: "6379"
@@ -1,245 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: radarr-kids
namespace: arrstack
annotations:
# Reloader rolls the Deployment when radarr-kids-env changes (plain envFrom
# ConfigMap does not trigger a rollout on its own).
configmap.reloader.stakater.com/auto: "true"
spec:
# Active-active: the -unkin2 fork keeps all state in the shared Postgres
# (arrstack-postgres) and coordinates via Postgres advisory locks, so N
# replicas run concurrently behind the radarr-kids Service. RollingUpdate is
# safe — no SQLite, no RWO lock.
replicas: 3
strategy:
type: RollingUpdate
selector:
matchLabels:
app: radarr-kids
template:
metadata:
labels:
app: radarr-kids
spec:
securityContext:
# Fork image has no USER; pin it to a non-root UID and group-write the
# shared RWX CephFS media subtree. OnRootMismatch avoids a recursive
# chown of the whole media tree.
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
initContainers:
# Gate the app on its own Postgres database+role being reachable.
# waitfordb reads the PG* env as a libpq fallback, so the password never
# lands in argv.
- name: wait-for-db
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/waitfordb:v0.1.0
env:
- name: WAITFORDB_TIMEOUT
value: 5m
- name: WAITFORDB_SSLMODE
value: disable
- name: PGHOST
value: arrstack-postgres-rw.arrstack.svc.cluster.local
- name: PGPORT
value: "5432"
- name: PGDATABASE
value: radarr-kids-main
- name: PGUSER
valueFrom:
secretKeyRef:
name: radarr-kids-db
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: radarr-kids-db
key: password
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
containers:
- name: radarr
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/radarr:v6.4.2-unkin7
imagePullPolicy: IfNotPresent
command:
- /app/Radarr
args:
- -nobrowser
- -data=/config
# Bypass the single-instance guard so multiple replicas can share one
# /config. Cross-replica safety is the Postgres layer, not a lock file.
- -nosingleinstancecheck
ports:
- name: http
containerPort: 7878
protocol: TCP
envFrom:
- configMapRef:
name: radarr-kids-env
env:
- name: Radarr__Postgres__User
valueFrom:
secretKeyRef:
name: radarr-kids-db
key: username
- name: Radarr__Postgres__Password
valueFrom:
secretKeyRef:
name: radarr-kids-db
key: password
- name: Radarr__Auth__ApiKey
valueFrom:
secretKeyRef:
name: radarr-kids-apikey
key: apitoken
# MediaCover object store (shared arrstack-media Ceph RGW bucket,
# partitioned by the radarr-kids key prefix).
- name: Radarr__MediaCover__S3__Endpoint
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: S3_ENDPOINT
- name: Radarr__MediaCover__S3__AccessKey
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: AWS_ACCESS_KEY_ID
- name: Radarr__MediaCover__S3__SecretKey
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: AWS_SECRET_ACCESS_KEY
- name: Radarr__MediaCover__S3__Bucket
value: arrstack-media
- name: Radarr__MediaCover__S3__Prefix
value: radarr-kids
- name: Radarr__MediaCover__S3__ForcePathStyle
value: "true"
- name: Radarr__MediaCover__S3__CaCertPath
value: /etc/ssl/vault-ca/ca.crt
# Backup object store (shared arrstack-backups Ceph RGW bucket,
# partitioned by the radarr-kids key prefix).
- name: Radarr__BackupS3__Endpoint
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: S3_ENDPOINT
- name: Radarr__BackupS3__AccessKey
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: AWS_ACCESS_KEY_ID
- name: Radarr__BackupS3__SecretKey
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: AWS_SECRET_ACCESS_KEY
- name: Radarr__BackupS3__Bucket
value: arrstack-backups
- name: Radarr__BackupS3__Prefix
value: radarr-kids
- name: Radarr__BackupS3__ForcePathStyle
value: "true"
- name: Radarr__BackupS3__CaCertPath
value: /etc/ssl/vault-ca/ca.crt
livenessProbe:
httpGet:
path: /3df803/radarr/ping
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /3df803/radarr/ping
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: config
mountPath: /config
# Kids movies subtree of the shared media-movies PVC (same CephFS
# subvolume the adult radarr writes and jellyfin reads).
- name: media-movies
mountPath: /media/movies
subPath: movies/kids
- name: vault-ca
mountPath: /etc/ssl/vault-ca
readOnly: true
# exportarr sidecar: polls the local replica's API and exposes Prometheus
# metrics on :9708 (scraped by the radarr-kids-exportarr VMPodScrape).
- name: exportarr
image: ghcr.io/onedr0p/exportarr:v2.3.0
imagePullPolicy: IfNotPresent
args:
- radarr
env:
- name: PORT
value: "9708"
# URL includes the /3df803/radarr UrlBase (Radarr__Server__UrlBase).
- name: URL
value: http://localhost:7878/3df803/radarr
- name: APIKEY
valueFrom:
secretKeyRef:
name: radarr-kids-apikey
key: apitoken
ports:
- name: metrics
containerPort: 9708
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: metrics
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /healthz
port: metrics
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
volumes:
- name: config
emptyDir: {}
- name: media-movies
persistentVolumeClaim:
claimName: media-movies
# Estate CA for validating the Ceph RGW (s3.ceph.unkin.net) TLS cert.
- name: vault-ca
secret:
secretName: vault-ca-cert
items:
- key: ca.crt
path: ca.crt
@@ -1,10 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- vaultstaticsecret.yaml
- configmap.yaml
- deployment.yaml
- service.yaml
- vmpodscrape.yaml
@@ -1,15 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: radarr-kids
namespace: arrstack
spec:
ports:
- name: http
port: 7878
protocol: TCP
targetPort: http
selector:
app: radarr-kids
type: ClusterIP
@@ -1,25 +0,0 @@
---
# radarr-kids API key. Seeded out-of-band at
# kv/kubernetes/namespace/arrstack/default/radarr-kids (key: apitoken); the
# default k8s role's templated policy already grants read on
# kv/data/kubernetes/namespace/{{sa_namespace}}/{{sa_name}}/* for the
# arrstack/default ServiceAccount, so no terraform-vault change is needed. VSO
# syncs it into the radarr-kids-apikey Secret consumed by the Deployment.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: radarr-kids-apikey
namespace: arrstack
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
destination:
create: true
name: radarr-kids-apikey
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/arrstack/default/radarr-kids
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
@@ -1,16 +0,0 @@
---
# Scrape the exportarr sidecar (:9708) on every radarr-kids pod. Picked up by the
# observability VMAgent (selectAllByDefault). Pod-level rather than
# VMServiceScrape because the radarr-kids Service doesn't expose the metrics port.
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: radarr-kids-exportarr
namespace: arrstack
spec:
selector:
matchLabels:
app: radarr-kids
podMetricsEndpoints:
- port: metrics
path: /metrics
@@ -1,25 +0,0 @@
---
# Non-secret env for the -unkin2 fork (kids tier). Identical mechanism to the
# adult sonarr, pointed at its own shared-Postgres database (sonarr-kids-main)
# and its own UrlBase (/3df803/sonarr) so arrproxy path-routing reaches the kids
# instance separately. Shares the one arrstack Valkey (keys namespaced by the
# fork's sonarr:ratelimit: prefix). User/Password/ApiKey come from Secrets (see
# deployment.yaml), not here.
apiVersion: v1
kind: ConfigMap
metadata:
name: sonarr-kids-env
namespace: arrstack
data:
Sonarr__Postgres__Host: arrstack-postgres-rw.arrstack.svc.cluster.local
Sonarr__Postgres__Port: "5432"
Sonarr__Postgres__MainDb: sonarr-kids-main
Sonarr__Log__DbEnabled: "false"
Sonarr__Auth__Method: External
Sonarr__Auth__Required: DisabledForLocalAddresses
Sonarr__App__InstanceName: Sonarr
Sonarr__Server__Port: "8989"
Sonarr__Server__UrlBase: /3df803/sonarr
Sonarr__Update__Mechanism: External
Sonarr__Redis__Host: valkey-arrstack-valkey.arrstack.svc.cluster.local
Sonarr__Redis__Port: "6379"
@@ -1,245 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarr-kids
namespace: arrstack
annotations:
# Reloader rolls the Deployment when sonarr-kids-env changes (plain envFrom
# ConfigMap does not trigger a rollout on its own).
configmap.reloader.stakater.com/auto: "true"
spec:
# Active-active: the -unkin2 fork keeps all state in the shared Postgres
# (arrstack-postgres) and coordinates via Postgres advisory locks, so N
# replicas run concurrently behind the sonarr-kids Service. RollingUpdate is
# safe — no SQLite, no RWO lock.
replicas: 3
strategy:
type: RollingUpdate
selector:
matchLabels:
app: sonarr-kids
template:
metadata:
labels:
app: sonarr-kids
spec:
securityContext:
# Fork image has no USER; pin it to a non-root UID and group-write the
# shared RWX CephFS media subtree. OnRootMismatch avoids a recursive
# chown of the whole media tree.
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
initContainers:
# Gate the app on its own Postgres database+role being reachable.
# waitfordb reads the PG* env as a libpq fallback, so the password never
# lands in argv.
- name: wait-for-db
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/waitfordb:v0.1.0
env:
- name: WAITFORDB_TIMEOUT
value: 5m
- name: WAITFORDB_SSLMODE
value: disable
- name: PGHOST
value: arrstack-postgres-rw.arrstack.svc.cluster.local
- name: PGPORT
value: "5432"
- name: PGDATABASE
value: sonarr-kids-main
- name: PGUSER
valueFrom:
secretKeyRef:
name: sonarr-kids-db
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: sonarr-kids-db
key: password
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
containers:
- name: sonarr
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/sonarr:v5.0.0-unkin6
imagePullPolicy: IfNotPresent
command:
- /app/Sonarr
args:
- -nobrowser
- -data=/config
# Bypass the single-instance guard so multiple replicas can share one
# /config. Cross-replica safety is the Postgres layer, not a lock file.
- -nosingleinstancecheck
ports:
- name: http
containerPort: 8989
protocol: TCP
envFrom:
- configMapRef:
name: sonarr-kids-env
env:
- name: Sonarr__Postgres__User
valueFrom:
secretKeyRef:
name: sonarr-kids-db
key: username
- name: Sonarr__Postgres__Password
valueFrom:
secretKeyRef:
name: sonarr-kids-db
key: password
- name: Sonarr__Auth__ApiKey
valueFrom:
secretKeyRef:
name: sonarr-kids-apikey
key: apitoken
# MediaCover object store (shared arrstack-media Ceph RGW bucket,
# partitioned by the sonarr-kids key prefix).
- name: Sonarr__MediaCoverS3__Endpoint
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: S3_ENDPOINT
- name: Sonarr__MediaCoverS3__AccessKey
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: AWS_ACCESS_KEY_ID
- name: Sonarr__MediaCoverS3__SecretKey
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: AWS_SECRET_ACCESS_KEY
- name: Sonarr__MediaCoverS3__Bucket
value: arrstack-media
- name: Sonarr__MediaCoverS3__Prefix
value: sonarr-kids
- name: Sonarr__MediaCoverS3__ForcePathStyle
value: "true"
- name: Sonarr__MediaCoverS3__CaCertPath
value: /etc/ssl/vault-ca/ca.crt
# Backup object store (shared arrstack-backups Ceph RGW bucket,
# partitioned by the sonarr-kids key prefix).
- name: Sonarr__BackupS3__Endpoint
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: S3_ENDPOINT
- name: Sonarr__BackupS3__AccessKey
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: AWS_ACCESS_KEY_ID
- name: Sonarr__BackupS3__SecretKey
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: AWS_SECRET_ACCESS_KEY
- name: Sonarr__BackupS3__Bucket
value: arrstack-backups
- name: Sonarr__BackupS3__Prefix
value: sonarr-kids
- name: Sonarr__BackupS3__ForcePathStyle
value: "true"
- name: Sonarr__BackupS3__CaCertPath
value: /etc/ssl/vault-ca/ca.crt
livenessProbe:
httpGet:
path: /3df803/sonarr/ping
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /3df803/sonarr/ping
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: config
mountPath: /config
# Kids TV subtree of the shared media-tv PVC (same CephFS subvolume
# the adult sonarr writes and jellyfin reads).
- name: media-tv
mountPath: /media/tv
subPath: tvshows/kids
- name: vault-ca
mountPath: /etc/ssl/vault-ca
readOnly: true
# exportarr sidecar: polls the local replica's API and exposes Prometheus
# metrics on :9707 (scraped by the sonarr-kids-exportarr VMPodScrape).
- name: exportarr
image: ghcr.io/onedr0p/exportarr:v2.3.0
imagePullPolicy: IfNotPresent
args:
- sonarr
env:
- name: PORT
value: "9707"
# URL includes the /3df803/sonarr UrlBase (Sonarr__Server__UrlBase).
- name: URL
value: http://localhost:8989/3df803/sonarr
- name: APIKEY
valueFrom:
secretKeyRef:
name: sonarr-kids-apikey
key: apitoken
ports:
- name: metrics
containerPort: 9707
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: metrics
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /healthz
port: metrics
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
volumes:
- name: config
emptyDir: {}
- name: media-tv
persistentVolumeClaim:
claimName: media-tv
# Estate CA for validating the Ceph RGW (s3.ceph.unkin.net) TLS cert.
- name: vault-ca
secret:
secretName: vault-ca-cert
items:
- key: ca.crt
path: ca.crt
@@ -1,10 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- vaultstaticsecret.yaml
- configmap.yaml
- deployment.yaml
- service.yaml
- vmpodscrape.yaml
@@ -1,15 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: sonarr-kids
namespace: arrstack
spec:
ports:
- name: http
port: 8989
protocol: TCP
targetPort: http
selector:
app: sonarr-kids
type: ClusterIP
@@ -1,25 +0,0 @@
---
# sonarr-kids API key. Seeded out-of-band at
# kv/kubernetes/namespace/arrstack/default/sonarr-kids (key: apitoken); the
# default k8s role's templated policy already grants read on
# kv/data/kubernetes/namespace/{{sa_namespace}}/{{sa_name}}/* for the
# arrstack/default ServiceAccount, so no terraform-vault change is needed. VSO
# syncs it into the sonarr-kids-apikey Secret consumed by the Deployment.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: sonarr-kids-apikey
namespace: arrstack
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
destination:
create: true
name: sonarr-kids-apikey
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/arrstack/default/sonarr-kids
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
@@ -1,16 +0,0 @@
---
# Scrape the exportarr sidecar (:9707) on every sonarr-kids pod. Picked up by the
# observability VMAgent (selectAllByDefault). Pod-level rather than
# VMServiceScrape because the sonarr-kids Service doesn't expose the metrics port.
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: sonarr-kids-exportarr
namespace: arrstack
spec:
selector:
matchLabels:
app: sonarr-kids
podMetricsEndpoints:
- port: metrics
path: /metrics
+1 -1
View File
@@ -210,7 +210,7 @@ spec:
- name: media-tv
# Kids-only instance: mount just the tvshows/kids subtree of the
# shared TV subvolume (subPath kids) so cheeztv's library never
# exposes the adult tree. Path matches fafflix's /media/tv-kids
# exposes the adult tree. Path matches fafflix's /media/tv/kids
# mount so the same episode resolves identically across instances.
mountPath: /media/tv
subPath: kids