Replace legacy jellyfin app with fafflix (adult, cheeztv pattern) (#415)

## Why

Rebuild the adult media instance as `fafflix`, mirroring the kids instance (`cheeztv`) HA pattern (Postgres-backed jellyfin-ha fork, Valkey transcode-lease store, shared-RWX transcode, CNPG + k8up/restic backups, static CephFS media PVs). Ben: "replace the adult jellyfin with the same pattern as kids." The adult instance holds no data currently, so the wipe/replace is sanctioned.

Stacked on top of `benvin/cheeztv` (#413) — base is that branch, not main.

## How

- Add `apps/base/fafflix/` — cheeztv pattern with `s/cheeztv/fafflix` on names/namespace/labels/secrets/buckets and the Vault kv path `kubernetes/namespace/fafflix/default/k8up-restic`.
- Media: mount the shared movies/tv CephFS subvolumes' `adult` subtree at `/media/movies` and `/media/tv`, plus the `kids` subtree at `/media/movies-kids` and `/media/tv-kids` (same two static PVs, new unique volumeHandles `fafflix-media-{movies,tv}-static`) so fafflix can resume kids content started on cheeztv. All media mounts readOnly, matching cheeztv.
- **Hostname preserved:** fafflix keeps serving the legacy jellyfin host `jellyfin.k8s.syd1.au.unkin.net` (TLS secret `fafflix-tls`, cert-manager re-mints). The dedicated fafflix domain switch is explicitly deferred — no `fafflix.unkin.net` created.
- config PVC on `cephfs-raid5-delete` (delete variant).
- Remove `apps/base/jellyfin/` + its overlay; swap `jellyfin`->`fafflix` in the `media-apps` ApplicationSet directory glob and the `media` AppProject namespace destination.
- Third-party images stay canonical upstream; the in-house `jellyfin-ha` image ref is unchanged.

## Verification

- `kustomize build` + `kubeconform -strict` clean on the fafflix overlay and the whole media appset scope (fafflix/cheeztv/arrstack): 0 invalid, 0 errors.
- No dangling `apps/base/jellyfin` references; `jellyfin.k8s.syd1.au.unkin.net` now served by exactly one app (fafflix); no `fafflix.unkin.net` anywhere.

Reviewed-on: #415
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #415.
This commit is contained in:
2026-08-25 20:06:53 +10:00
committed by BenVincent
parent 9db52c5e26
commit fe689dc08e
25 changed files with 161 additions and 148 deletions
@@ -1,32 +1,32 @@
---
# Second Ceph RGW (S3) bucket owned by the existing jellyfin backup user
# (cnpg-jellyfin-backup, defined in cnpg_backup.yaml) — one user, two buckets:
# Second Ceph RGW (S3) bucket owned by the existing fafflix backup user
# (cnpg-fafflix-backup, defined in cnpg_backup.yaml) — one user, two buckets:
# the CNPG barman bucket plus this one, which k8up uses to hold restic backups
# of the jellyfin-config PVC. The BucketAccess emits read-write S3 creds into a
# of the fafflix-config PVC. The BucketAccess emits read-write S3 creds into a
# Secret the k8up Schedule consumes.
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: jellyfin-config-backup
namespace: jellyfin
name: fafflix-config-backup
namespace: fafflix
spec:
placementTarget: ec
bucketName: jellyfin-config-backup
ownerRef: cnpg-jellyfin-backup
bucketName: fafflix-config-backup
ownerRef: cnpg-fafflix-backup
versioning: false
tags:
app: jellyfin
app: fafflix
purpose: config-backup
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: BucketAccess
metadata:
name: jellyfin-config-backup
namespace: jellyfin
name: fafflix-config-backup
namespace: fafflix
spec:
bucketRef: jellyfin-config-backup
bucketRef: fafflix-config-backup
level: read-write
# Operator writes AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (+ S3_ENDPOINT,
# BUCKET_NAME) into this Secret; the k8up Schedule reads the access keys.
secretName: jellyfin-config-backup-s3
secretName: fafflix-config-backup-s3
@@ -1,31 +1,31 @@
---
# Ceph RGW (S3) backup target for the jellyfin CNPG cluster, provisioned by the
# Ceph RGW (S3) backup target for the fafflix CNPG cluster, provisioned by the
# in-estate cephrgw-operator: one dedicated bucket + owner user. CNPG reads the
# S3 credential Secret from its own namespace.
apiVersion: ceph.unkin.net/v1alpha1
kind: ObjectStoreUser
metadata:
name: cnpg-jellyfin-backup
namespace: jellyfin
name: cnpg-fafflix-backup
namespace: fafflix
spec:
displayName: "CNPG backup owner (jellyfin)"
uid: cnpg-jellyfin-backup
displayName: "CNPG backup owner (fafflix)"
uid: cnpg-fafflix-backup
maxBuckets: 5
secretName: cnpg-jellyfin-backup-s3
secretName: cnpg-fafflix-backup-s3
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-jellyfin
namespace: jellyfin
name: cnpg-fafflix
namespace: fafflix
spec:
placementTarget: ec
bucketName: cnpg-jellyfin
ownerRef: cnpg-jellyfin-backup
bucketName: cnpg-fafflix
ownerRef: cnpg-fafflix-backup
versioning: false
tags:
app: jellyfin
app: fafflix
purpose: cnpg-backup
retainOnDelete: true
---
@@ -34,12 +34,12 @@ spec:
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-jellyfin-nightly
namespace: jellyfin
name: cnpg-fafflix-nightly
namespace: fafflix
spec:
schedule: "0 35 3 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: jellyfin-postgres
name: fafflix-postgres
@@ -1,17 +1,17 @@
---
# Main Jellyfin database. The jellyfin-ha fork's experimental EF Core provider
# Main Jellyfin database. The fafflix-ha fork's experimental EF Core provider
# moves the entire Jellyfin DB (incl. library items) off SQLite into PostgreSQL,
# which is what makes a shared-nothing multi-replica deployment possible. No
# bootstrap secret is given, so CNPG generates the jellyfin-postgres-app secret
# bootstrap secret is given, so CNPG generates the fafflix-postgres-app secret
# (username/password/dbname) that the StatefulSet composes its DSN from.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: jellyfin-postgres
namespace: jellyfin
name: fafflix-postgres
namespace: fafflix
spec:
# Exclude the operator-managed data PVCs (jellyfin-postgres-N) from the
# jellyfin-config k8up Schedule (skipWithoutAnnotation is false cluster-wide,
# Exclude the operator-managed data PVCs (fafflix-postgres-N) from the
# fafflix-config k8up Schedule (skipWithoutAnnotation is false cluster-wide,
# so unannotated PVCs are swept in). Postgres has its own barmanObjectStore
# backup below; restic must not touch the raw RWO data volumes.
inheritedMetadata:
@@ -23,19 +23,19 @@ spec:
retentionPolicy: 30d
barmanObjectStore:
# Dedicated per-cluster Ceph RGW bucket (cephrgw-operator provisions it).
destinationPath: s3://cnpg-jellyfin
destinationPath: s3://cnpg-fafflix
endpointURL: https://s3.ceph.unkin.net
endpointCA:
name: vault-ca-cert
key: ca.crt
s3Credentials:
accessKeyId:
name: cnpg-jellyfin-backup-s3
name: cnpg-fafflix-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-jellyfin-backup-s3
name: cnpg-fafflix-backup-s3
key: AWS_SECRET_ACCESS_KEY
serverName: jellyfin
serverName: fafflix
data:
compression: bzip2
jobs: 2
@@ -44,11 +44,11 @@ spec:
maxParallel: 2
bootstrap:
initdb:
database: jellyfin
database: fafflix
encoding: UTF8
localeCType: C
localeCollate: C
owner: jellyfin
owner: fafflix
enablePDB: true
enableSuperuserAccess: false
failoverDelay: 0
@@ -1,15 +1,15 @@
---
# PgBouncer pooler in front of the jellyfin-postgres cluster. Jellyfin connects
# here (jellyfin-postgres-pooler:5432) rather than the -rw service so EF Core's
# PgBouncer pooler in front of the fafflix-postgres cluster. Jellyfin connects
# here (fafflix-postgres-pooler:5432) rather than the -rw service so EF Core's
# connection churn is absorbed by the pool.
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: jellyfin-postgres-pooler
namespace: jellyfin
name: fafflix-postgres-pooler
namespace: fafflix
spec:
cluster:
name: jellyfin-postgres
name: fafflix-postgres
instances: 2
pgbouncer:
parameters:
@@ -20,7 +20,7 @@ spec:
template:
metadata:
labels:
app: jellyfin-pooler
app: fafflix-pooler
spec:
affinity:
podAntiAffinity:
@@ -30,7 +30,7 @@ spec:
- key: app
operator: In
values:
- jellyfin-pooler
- fafflix-pooler
topologyKey: kubernetes.io/hostname
containers: []
type: rw
@@ -1,4 +1,9 @@
---
# Fafflix (adult instance) keeps serving the legacy jellyfin hostname
# (jellyfin.k8s.syd1.au.unkin.net) so the switch to a dedicated fafflix domain
# can be deferred. Same internal-Traefik + external-dns pattern the old jellyfin app
# used: external-dns publishes the A record at the internal LB VIP
# (198.18.200.4) and cert-manager mints fafflix-tls off the Vault-PKI issuer.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
@@ -10,8 +15,8 @@ metadata:
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: jellyfin.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: jellyfin
namespace: jellyfin
name: fafflix
namespace: fafflix
spec:
gatewayClassName: traefik-internal
listeners:
@@ -33,5 +38,5 @@ spec:
certificateRefs:
- group: ""
kind: Secret
name: jellyfin-tls
name: fafflix-tls
mode: Terminate
@@ -3,14 +3,14 @@ apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-redirect
namespace: jellyfin
namespace: fafflix
spec:
hostnames:
- jellyfin.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: jellyfin
name: fafflix
sectionName: http
rules:
- filters:
@@ -26,21 +26,21 @@ spec:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: jellyfin-route
namespace: jellyfin
name: fafflix-route
namespace: fafflix
spec:
hostnames:
- jellyfin.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: jellyfin
name: fafflix
sectionName: https
rules:
- backendRefs:
- group: ""
kind: Service
name: jellyfin
name: fafflix
port: 8096
weight: 1
matches:
@@ -2,4 +2,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: jellyfin
name: fafflix
@@ -4,10 +4,10 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: jellyfin
namespace: jellyfin
name: fafflix
namespace: fafflix
spec:
minAvailable: 1
selector:
matchLabels:
app: jellyfin
app: fafflix
@@ -1,11 +1,11 @@
---
# Static PV for the shared MOVIES CephFS subvolume. Same rootPath as arrstack's
# movies PV so radarr writes and jellyfin reads the identical library tree; each
# movies PV so radarr writes and fafflix reads the identical library tree; each
# namespace gets its own PV (unique name + volumeHandle) pinned by claimRef.
apiVersion: v1
kind: PersistentVolume
metadata:
name: jellyfin-media-movies
name: fafflix-media-movies
spec:
capacity:
storage: 1Ti
@@ -15,11 +15,11 @@ spec:
storageClassName: ""
volumeMode: Filesystem
claimRef:
namespace: jellyfin
name: jellyfin-media-movies
namespace: fafflix
name: fafflix-media-movies
csi:
driver: cephfs.csi.ceph.com
volumeHandle: jellyfin-media-movies-static
volumeHandle: fafflix-media-movies-static
nodeStageSecretRef:
name: csi-cephfs-secret
namespace: csi-cephfs
@@ -1,11 +1,11 @@
---
# Static PV for the shared TV CephFS subvolume. Same rootPath as arrstack's
# TV PV so sonarr writes and jellyfin reads the identical library tree; each
# TV PV so sonarr writes and fafflix reads the identical library tree; each
# namespace gets its own PV (unique name + volumeHandle) pinned by claimRef.
apiVersion: v1
kind: PersistentVolume
metadata:
name: jellyfin-media-tv
name: fafflix-media-tv
spec:
capacity:
storage: 1Ti
@@ -15,11 +15,11 @@ spec:
storageClassName: ""
volumeMode: Filesystem
claimRef:
namespace: jellyfin
name: jellyfin-media-tv
namespace: fafflix
name: fafflix-media-tv
csi:
driver: cephfs.csi.ceph.com
volumeHandle: jellyfin-media-tv-static
volumeHandle: fafflix-media-tv-static
nodeStageSecretRef:
name: csi-cephfs-secret
namespace: csi-cephfs
@@ -1,17 +1,17 @@
---
# Jellyfin config: metadata images, plugins, subtitles and config XML. Shared
# ReadWriteMany across replicas (all pods read/write the same library metadata);
# the main library DB now lives in PostgreSQL, not here. Retain — this is state.
# the main library DB now lives in PostgreSQL, not here (on CephFS, raid5-delete).
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-config
namespace: jellyfin
name: fafflix-config
namespace: fafflix
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
storageClassName: cephfs-raid5-retain
storageClassName: cephfs-raid5-delete
volumeMode: Filesystem
@@ -1,16 +1,16 @@
---
# Movie library, shared read-many across replicas. Statically bound to the
# jellyfin-media-movies PV (shared CephFS subvolume also used by arrstack/radarr).
# fafflix-media-movies PV (shared CephFS subvolume also used by arrstack/radarr).
# storageClassName "" + volumeName disables dynamic provisioning and binds the
# pre-created static PV.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-media-movies
namespace: jellyfin
name: fafflix-media-movies
namespace: fafflix
annotations:
# Exclude from the jellyfin-config k8up Schedule (skipWithoutAnnotation is
# false cluster-wide, so unannotated PVCs are swept in). Only jellyfin-config
# Exclude from the fafflix-config k8up Schedule (skipWithoutAnnotation is
# false cluster-wide, so unannotated PVCs are swept in). Only fafflix-config
# is backed up; the media library is not restic-backup material.
k8up.io/backup: "false"
spec:
@@ -20,5 +20,5 @@ spec:
requests:
storage: 1Ti
storageClassName: ""
volumeName: jellyfin-media-movies
volumeName: fafflix-media-movies
volumeMode: Filesystem
@@ -1,16 +1,16 @@
---
# TV library, shared read-many across replicas. Statically bound to the
# jellyfin-media-tv PV (shared CephFS subvolume also used by arrstack/sonarr).
# fafflix-media-tv PV (shared CephFS subvolume also used by arrstack/sonarr).
# storageClassName "" + volumeName disables dynamic provisioning and binds the
# pre-created static PV.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-media-tv
namespace: jellyfin
name: fafflix-media-tv
namespace: fafflix
annotations:
# Exclude from the jellyfin-config k8up Schedule (skipWithoutAnnotation is
# false cluster-wide, so unannotated PVCs are swept in). Only jellyfin-config
# Exclude from the fafflix-config k8up Schedule (skipWithoutAnnotation is
# false cluster-wide, so unannotated PVCs are swept in). Only fafflix-config
# is backed up; the media library is not restic-backup material.
k8up.io/backup: "false"
spec:
@@ -20,5 +20,5 @@ spec:
requests:
storage: 1Ti
storageClassName: ""
volumeName: jellyfin-media-tv
volumeName: fafflix-media-tv
volumeMode: Filesystem
@@ -6,10 +6,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-transcode
namespace: jellyfin
name: fafflix-transcode
namespace: fafflix
annotations:
# Exclude from the jellyfin-config k8up Schedule (skipWithoutAnnotation is
# Exclude from the fafflix-config k8up Schedule (skipWithoutAnnotation is
# false cluster-wide, so unannotated PVCs are swept in). Transcode is RWX
# scratch — nothing to back up.
k8up.io/backup: "false"
@@ -1,8 +1,8 @@
---
# k8up Schedule: restic backups of the jellyfin-config PVC (library metadata,
# k8up Schedule: restic backups of the fafflix-config PVC (library metadata,
# plugins, config XML) to the dedicated Ceph RGW config-backup bucket. S3 creds
# come from the cephrgw BucketAccess Secret (jellyfin-config-backup-s3); the
# restic repo password comes from Vault via the jellyfin-k8up-restic Secret.
# come from the cephrgw BucketAccess Secret (fafflix-config-backup-s3); the
# restic repo password comes from Vault via the fafflix-k8up-restic Secret.
#
# s3.ceph.unkin.net presents the internal unkin.net CA, which the k8up/restic
# image does not trust by default, so the reflected vault-ca-cert Secret is
@@ -10,21 +10,21 @@
apiVersion: k8up.io/v1
kind: Schedule
metadata:
name: jellyfin-config
namespace: jellyfin
name: fafflix-config
namespace: fafflix
spec:
backend:
repoPasswordSecretRef:
name: jellyfin-k8up-restic
name: fafflix-k8up-restic
key: password
s3:
endpoint: https://s3.ceph.unkin.net
bucket: jellyfin-config-backup
bucket: fafflix-config-backup
accessKeyIDSecretRef:
name: jellyfin-config-backup-s3
name: fafflix-config-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKeySecretRef:
name: jellyfin-config-backup-s3
name: fafflix-config-backup-s3
key: AWS_SECRET_ACCESS_KEY
tlsOptions:
caCert: /etc/k8up/ca/ca.crt
@@ -2,8 +2,8 @@
apiVersion: v1
kind: Service
metadata:
name: jellyfin
namespace: jellyfin
name: fafflix
namespace: fafflix
spec:
internalTrafficPolicy: Cluster
ports:
@@ -12,7 +12,7 @@ spec:
protocol: TCP
targetPort: http
selector:
app: jellyfin
app: fafflix
# Pin each client to one replica to reduce transcode-session churn/takeover.
sessionAffinity: ClientIP
type: ClusterIP
@@ -2,24 +2,24 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jellyfin
namespace: jellyfin
name: fafflix
namespace: fafflix
spec:
# HA: two replicas coordinate transcode session ownership through Valkey and
# resume each other's HLS segments off the shared RWX transcode PVC. Stable
# pod names (jellyfin-0/1) are the lease owner identity, hence StatefulSet.
# pod names (fafflix-0/1) are the lease owner identity, hence StatefulSet.
replicas: 2
serviceName: jellyfin
serviceName: fafflix
podManagementPolicy: Parallel
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: jellyfin
app: fafflix
template:
metadata:
labels:
app: jellyfin
app: fafflix
spec:
securityContext:
# Group-write the shared RWX volumes and grant the render/video groups so
@@ -41,7 +41,7 @@ spec:
podAffinityTerm:
labelSelector:
matchLabels:
app: jellyfin
app: fafflix
topologyKey: kubernetes.io/hostname
initContainers:
# Seed the fork's PostgreSQL provider (database.xml) and Intel iGPU
@@ -110,7 +110,7 @@ spec:
- name: config
mountPath: /config
containers:
- name: jellyfin
- name: fafflix
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/jellyfin-ha:v0.1.3
imagePullPolicy: IfNotPresent
ports:
@@ -137,9 +137,9 @@ spec:
# Config dir must differ from the data root (Jellyfin sanity check).
- name: JELLYFIN_CONFIG_DIR
value: /config/config
# Distributed transcode session store (jellyfin-ha additions).
# Distributed transcode session store (fafflix-ha additions).
- name: Jellyfin__TranscodeStore__RedisConnectionString
value: "valkey-jellyfin-valkey:6379,abortConnect=false"
value: "valkey-fafflix-valkey:6379,abortConnect=false"
- name: Jellyfin__TranscodeStore__LeaseDurationSeconds
value: "30"
# PostgreSQL main DB via the CNPG-generated app secret, routed through
@@ -148,22 +148,22 @@ spec:
- name: PGUSER
valueFrom:
secretKeyRef:
name: jellyfin-postgres-app
name: fafflix-postgres-app
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: jellyfin-postgres-app
name: fafflix-postgres-app
key: password
- name: PGDB
valueFrom:
secretKeyRef:
name: jellyfin-postgres-app
name: fafflix-postgres-app
key: dbname
- name: POSTGRES_CONNECTION_STRING
value: "postgresql://$(PGUSER):$(PGPASSWORD)@jellyfin-postgres-pooler:5432/$(PGDB)"
value: "postgresql://$(PGUSER):$(PGPASSWORD)@fafflix-postgres-pooler:5432/$(PGDB)"
- name: DATABASE_URL
value: "postgresql://$(PGUSER):$(PGPASSWORD)@jellyfin-postgres-pooler:5432/$(PGDB)"
value: "postgresql://$(PGUSER):$(PGPASSWORD)@fafflix-postgres-pooler:5432/$(PGDB)"
livenessProbe:
httpGet:
path: /health
@@ -208,42 +208,52 @@ spec:
- name: cache
mountPath: /cache
- name: media-tv
# Adult instance: mount the tvshows/adult subtree of the shared TV
# subvolume (subPath adult) as fafflix's primary TV library. Same
# rootPath the cheeztv instance reads under subPath kids, so an
# episode resolves identically across instances.
mountPath: /media/tv
subPath: adult
readOnly: true
- name: media-movies
mountPath: /media/movies
readOnly: true
# Kids subtrees surfaced as their own paths (same media PVCs, subPath
# kids) so a "Kids TV"/"Kids Movies" library can be added here and its
# titles are browsable and resume in this instance's own DB. The full
# /media/{tv,movies} mounts above are unchanged.
- name: media-tv
# Also mount the kids TV subtree (subPath kids) so fafflix can
# resume playback of kids content started on cheeztv — same
# underlying subvolume, different subtree, distinct mount path.
mountPath: /media/tv-kids
subPath: kids
readOnly: true
- name: media-movies
# Adult instance: mount the movies/adult subtree of the shared
# movies subvolume (subPath adult) as fafflix's primary movie
# library.
mountPath: /media/movies
subPath: adult
readOnly: true
- name: media-movies
# Also mount the kids movies subtree (subPath kids) for cross-resume
# of kids content started on cheeztv.
mountPath: /media/movies-kids
subPath: kids
readOnly: true
volumes:
- name: config
persistentVolumeClaim:
claimName: jellyfin-config
claimName: fafflix-config
- name: transcode
persistentVolumeClaim:
claimName: jellyfin-transcode
claimName: fafflix-transcode
- name: media-tv
persistentVolumeClaim:
claimName: jellyfin-media-tv
claimName: fafflix-media-tv
- name: media-movies
persistentVolumeClaim:
claimName: jellyfin-media-movies
claimName: fafflix-media-movies
volumeClaimTemplates:
# Per-pod scratch cache — RWO, disposable, one PVC per replica.
- metadata:
name: cache
annotations:
# Exclude the per-pod cache PVCs from the jellyfin-config k8up Schedule
# Exclude the per-pod cache PVCs from the fafflix-config k8up Schedule
# (skipWithoutAnnotation is false cluster-wide). Cache is disposable and
# RWO — it would also fail to mount into the backup pod while in use.
k8up.io/backup: "false"
@@ -19,20 +19,18 @@
apiVersion: valkey.io/v1alpha1
kind: ValkeyCluster
metadata:
name: jellyfin-valkey
namespace: jellyfin
name: fafflix-valkey
namespace: fafflix
spec:
shards: 1
replicas: 2
image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/valkey/valkey:9.0.0
image: docker.io/valkey/valkey:9.0.0
# redis_exporter sidecar (:9121, port name `metrics`) on every ValkeyNode pod;
# the operator manages a dedicated _exporter ACL user for it. Image overridden
# from the operator default (bare dockerhub oliver006/redis_exporter:v1.80.0)
# to the artifactapi-proxied pin. Scraped by the valkey-exporter VMPodScrape
# in vmpodscrape.yaml alongside this file.
# the operator manages a dedicated _exporter ACL user for it. Image version
# pinned; scraped by valkey-exporter VMPodScrape in vmpodscrape.yaml.
exporter:
enabled: true
image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/oliver006/redis_exporter:v1.89.0
image: docker.io/oliver006/redis_exporter:v1.89.0
scheduling:
node:
spread:
@@ -3,12 +3,12 @@ apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: jellyfin
namespace: fafflix
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
allowedNamespaces:
- jellyfin
- fafflix
kubernetes:
audiences:
- vault
@@ -1,24 +1,24 @@
---
# restic repository password for the k8up jellyfin-config backups. Seeded at
# kv/kubernetes/namespace/jellyfin/default/k8up-restic (key: password); the
# restic repository password for the k8up fafflix-config backups. Seeded at
# kv/kubernetes/namespace/fafflix/default/k8up-restic (key: password); the
# default k8s role's templated policy already grants read here, so no
# terraform-vault change is needed. VSO syncs it into the jellyfin-k8up-restic
# terraform-vault change is needed. VSO syncs it into the fafflix-k8up-restic
# Secret that the Schedule references via backend.repoPasswordSecretRef.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: jellyfin-k8up-restic
namespace: jellyfin
name: fafflix-k8up-restic
namespace: fafflix
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
destination:
create: true
name: jellyfin-k8up-restic
name: fafflix-k8up-restic
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/jellyfin/default/k8up-restic
path: kubernetes/namespace/fafflix/default/k8up-restic
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
@@ -1,6 +1,6 @@
---
# Scrape the operator-injected redis_exporter sidecar (:9121, port name
# `metrics`) on the jellyfin-valkey ValkeyNode pods. The valkey-operator gives
# `metrics`) on the fafflix-valkey ValkeyNode pods. The valkey-operator gives
# its pods fixed labels only (no pod-label passthrough on the ValkeyCluster CR),
# so select on the operator-managed labels. Picked up by the observability
# VMAgent (selectAllByDefault).
@@ -8,7 +8,7 @@ apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: valkey-exporter
namespace: jellyfin
namespace: fafflix
spec:
selector:
matchLabels:
@@ -3,4 +3,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/jellyfin
- ../../../base/fafflix