From fe689dc08e76905fb7dd7555a82ec7ffe997090e Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Tue, 25 Aug 2026 20:06:53 +1000 Subject: [PATCH] Replace legacy jellyfin app with fafflix (adult, cheeztv pattern) (#415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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: https://git.unkin.net/unkin/argocd-apps/pulls/415 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- .../cephrgw-config-backup.yaml | 24 +++---- .../{jellyfin => fafflix}/cnpg_backup.yaml | 28 ++++---- .../{jellyfin => fafflix}/cnpg_cluster.yaml | 24 +++---- .../{jellyfin => fafflix}/cnpg_pooler.yaml | 14 ++-- apps/base/{jellyfin => fafflix}/gateway.yaml | 11 +++- .../base/{jellyfin => fafflix}/httproute.yaml | 12 ++-- .../{jellyfin => fafflix}/kustomization.yaml | 0 .../base/{jellyfin => fafflix}/namespace.yaml | 2 +- apps/base/{jellyfin => fafflix}/pdb.yaml | 6 +- .../pv-media-movies.yaml | 10 +-- .../{jellyfin => fafflix}/pv-media-tv.yaml | 10 +-- .../{jellyfin => fafflix}/pvc-config.yaml | 8 +-- .../pvc-media-movies.yaml | 12 ++-- .../{jellyfin => fafflix}/pvc-media-tv.yaml | 12 ++-- .../{jellyfin => fafflix}/pvc-transcode.yaml | 6 +- apps/base/{jellyfin => fafflix}/schedule.yaml | 18 +++--- apps/base/{jellyfin => fafflix}/service.yaml | 6 +- .../{jellyfin => fafflix}/statefulset.yaml | 64 +++++++++++-------- apps/base/{jellyfin => fafflix}/valkey.yaml | 14 ++-- .../base/{jellyfin => fafflix}/vaultauth.yaml | 4 +- .../vaultstaticsecret.yaml | 14 ++-- .../{jellyfin => fafflix}/vmpodscrape.yaml | 4 +- .../{jellyfin => fafflix}/kustomization.yaml | 2 +- argocd/applicationsets/media.yaml | 2 +- argocd/projects/media.yaml | 2 +- 25 files changed, 161 insertions(+), 148 deletions(-) rename apps/base/{jellyfin => fafflix}/cephrgw-config-backup.yaml (51%) rename apps/base/{jellyfin => fafflix}/cnpg_backup.yaml (62%) rename apps/base/{jellyfin => fafflix}/cnpg_cluster.yaml (85%) rename apps/base/{jellyfin => fafflix}/cnpg_pooler.yaml (67%) rename apps/base/{jellyfin => fafflix}/gateway.yaml (67%) rename apps/base/{jellyfin => fafflix}/httproute.yaml (86%) rename apps/base/{jellyfin => fafflix}/kustomization.yaml (100%) rename apps/base/{jellyfin => fafflix}/namespace.yaml (72%) rename apps/base/{jellyfin => fafflix}/pdb.yaml (81%) rename apps/base/{jellyfin => fafflix}/pv-media-movies.yaml (76%) rename apps/base/{jellyfin => fafflix}/pv-media-tv.yaml (77%) rename apps/base/{jellyfin => fafflix}/pvc-config.yaml (66%) rename apps/base/{jellyfin => fafflix}/pvc-media-movies.yaml (67%) rename apps/base/{jellyfin => fafflix}/pvc-media-tv.yaml (68%) rename apps/base/{jellyfin => fafflix}/pvc-transcode.yaml (83%) rename apps/base/{jellyfin => fafflix}/schedule.yaml (74%) rename apps/base/{jellyfin => fafflix}/service.yaml (84%) rename apps/base/{jellyfin => fafflix}/statefulset.yaml (83%) rename apps/base/{jellyfin => fafflix}/valkey.yaml (81%) rename apps/base/{jellyfin => fafflix}/vaultauth.yaml (90%) rename apps/base/{jellyfin => fafflix}/vaultstaticsecret.yaml (53%) rename apps/base/{jellyfin => fafflix}/vmpodscrape.yaml (84%) rename apps/overlays/au-syd1/{jellyfin => fafflix}/kustomization.yaml (74%) diff --git a/apps/base/jellyfin/cephrgw-config-backup.yaml b/apps/base/fafflix/cephrgw-config-backup.yaml similarity index 51% rename from apps/base/jellyfin/cephrgw-config-backup.yaml rename to apps/base/fafflix/cephrgw-config-backup.yaml index 14ad7ed..23982b9 100644 --- a/apps/base/jellyfin/cephrgw-config-backup.yaml +++ b/apps/base/fafflix/cephrgw-config-backup.yaml @@ -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 diff --git a/apps/base/jellyfin/cnpg_backup.yaml b/apps/base/fafflix/cnpg_backup.yaml similarity index 62% rename from apps/base/jellyfin/cnpg_backup.yaml rename to apps/base/fafflix/cnpg_backup.yaml index 723c82c..67e7ca5 100644 --- a/apps/base/jellyfin/cnpg_backup.yaml +++ b/apps/base/fafflix/cnpg_backup.yaml @@ -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 diff --git a/apps/base/jellyfin/cnpg_cluster.yaml b/apps/base/fafflix/cnpg_cluster.yaml similarity index 85% rename from apps/base/jellyfin/cnpg_cluster.yaml rename to apps/base/fafflix/cnpg_cluster.yaml index 46a2476..adbb0c4 100644 --- a/apps/base/jellyfin/cnpg_cluster.yaml +++ b/apps/base/fafflix/cnpg_cluster.yaml @@ -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 diff --git a/apps/base/jellyfin/cnpg_pooler.yaml b/apps/base/fafflix/cnpg_pooler.yaml similarity index 67% rename from apps/base/jellyfin/cnpg_pooler.yaml rename to apps/base/fafflix/cnpg_pooler.yaml index 50f9263..f4322ab 100644 --- a/apps/base/jellyfin/cnpg_pooler.yaml +++ b/apps/base/fafflix/cnpg_pooler.yaml @@ -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 diff --git a/apps/base/jellyfin/gateway.yaml b/apps/base/fafflix/gateway.yaml similarity index 67% rename from apps/base/jellyfin/gateway.yaml rename to apps/base/fafflix/gateway.yaml index a491519..4890cd1 100644 --- a/apps/base/jellyfin/gateway.yaml +++ b/apps/base/fafflix/gateway.yaml @@ -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 diff --git a/apps/base/jellyfin/httproute.yaml b/apps/base/fafflix/httproute.yaml similarity index 86% rename from apps/base/jellyfin/httproute.yaml rename to apps/base/fafflix/httproute.yaml index 1778dcd..70aa8f6 100644 --- a/apps/base/jellyfin/httproute.yaml +++ b/apps/base/fafflix/httproute.yaml @@ -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: diff --git a/apps/base/jellyfin/kustomization.yaml b/apps/base/fafflix/kustomization.yaml similarity index 100% rename from apps/base/jellyfin/kustomization.yaml rename to apps/base/fafflix/kustomization.yaml diff --git a/apps/base/jellyfin/namespace.yaml b/apps/base/fafflix/namespace.yaml similarity index 72% rename from apps/base/jellyfin/namespace.yaml rename to apps/base/fafflix/namespace.yaml index 3122e11..115d3e0 100644 --- a/apps/base/jellyfin/namespace.yaml +++ b/apps/base/fafflix/namespace.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: jellyfin + name: fafflix diff --git a/apps/base/jellyfin/pdb.yaml b/apps/base/fafflix/pdb.yaml similarity index 81% rename from apps/base/jellyfin/pdb.yaml rename to apps/base/fafflix/pdb.yaml index 4b21b5b..556f82c 100644 --- a/apps/base/jellyfin/pdb.yaml +++ b/apps/base/fafflix/pdb.yaml @@ -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 diff --git a/apps/base/jellyfin/pv-media-movies.yaml b/apps/base/fafflix/pv-media-movies.yaml similarity index 76% rename from apps/base/jellyfin/pv-media-movies.yaml rename to apps/base/fafflix/pv-media-movies.yaml index b9ddfcc..aeb6337 100644 --- a/apps/base/jellyfin/pv-media-movies.yaml +++ b/apps/base/fafflix/pv-media-movies.yaml @@ -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 diff --git a/apps/base/jellyfin/pv-media-tv.yaml b/apps/base/fafflix/pv-media-tv.yaml similarity index 77% rename from apps/base/jellyfin/pv-media-tv.yaml rename to apps/base/fafflix/pv-media-tv.yaml index a3df747..5ecde81 100644 --- a/apps/base/jellyfin/pv-media-tv.yaml +++ b/apps/base/fafflix/pv-media-tv.yaml @@ -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 diff --git a/apps/base/jellyfin/pvc-config.yaml b/apps/base/fafflix/pvc-config.yaml similarity index 66% rename from apps/base/jellyfin/pvc-config.yaml rename to apps/base/fafflix/pvc-config.yaml index 53e6a4f..e8cf284 100644 --- a/apps/base/jellyfin/pvc-config.yaml +++ b/apps/base/fafflix/pvc-config.yaml @@ -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 diff --git a/apps/base/jellyfin/pvc-media-movies.yaml b/apps/base/fafflix/pvc-media-movies.yaml similarity index 67% rename from apps/base/jellyfin/pvc-media-movies.yaml rename to apps/base/fafflix/pvc-media-movies.yaml index 4fe15f6..161bfd2 100644 --- a/apps/base/jellyfin/pvc-media-movies.yaml +++ b/apps/base/fafflix/pvc-media-movies.yaml @@ -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 diff --git a/apps/base/jellyfin/pvc-media-tv.yaml b/apps/base/fafflix/pvc-media-tv.yaml similarity index 68% rename from apps/base/jellyfin/pvc-media-tv.yaml rename to apps/base/fafflix/pvc-media-tv.yaml index 6593bc5..dab57f2 100644 --- a/apps/base/jellyfin/pvc-media-tv.yaml +++ b/apps/base/fafflix/pvc-media-tv.yaml @@ -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 diff --git a/apps/base/jellyfin/pvc-transcode.yaml b/apps/base/fafflix/pvc-transcode.yaml similarity index 83% rename from apps/base/jellyfin/pvc-transcode.yaml rename to apps/base/fafflix/pvc-transcode.yaml index 01750ad..809f19e 100644 --- a/apps/base/jellyfin/pvc-transcode.yaml +++ b/apps/base/fafflix/pvc-transcode.yaml @@ -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" diff --git a/apps/base/jellyfin/schedule.yaml b/apps/base/fafflix/schedule.yaml similarity index 74% rename from apps/base/jellyfin/schedule.yaml rename to apps/base/fafflix/schedule.yaml index 6043a18..a91dfd3 100644 --- a/apps/base/jellyfin/schedule.yaml +++ b/apps/base/fafflix/schedule.yaml @@ -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 diff --git a/apps/base/jellyfin/service.yaml b/apps/base/fafflix/service.yaml similarity index 84% rename from apps/base/jellyfin/service.yaml rename to apps/base/fafflix/service.yaml index a8c810e..9f35a56 100644 --- a/apps/base/jellyfin/service.yaml +++ b/apps/base/fafflix/service.yaml @@ -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 diff --git a/apps/base/jellyfin/statefulset.yaml b/apps/base/fafflix/statefulset.yaml similarity index 83% rename from apps/base/jellyfin/statefulset.yaml rename to apps/base/fafflix/statefulset.yaml index 8943d53..67e109b 100644 --- a/apps/base/jellyfin/statefulset.yaml +++ b/apps/base/fafflix/statefulset.yaml @@ -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" diff --git a/apps/base/jellyfin/valkey.yaml b/apps/base/fafflix/valkey.yaml similarity index 81% rename from apps/base/jellyfin/valkey.yaml rename to apps/base/fafflix/valkey.yaml index 38a66c8..1f641e9 100644 --- a/apps/base/jellyfin/valkey.yaml +++ b/apps/base/fafflix/valkey.yaml @@ -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: diff --git a/apps/base/jellyfin/vaultauth.yaml b/apps/base/fafflix/vaultauth.yaml similarity index 90% rename from apps/base/jellyfin/vaultauth.yaml rename to apps/base/fafflix/vaultauth.yaml index 9fbc2c2..00014ba 100644 --- a/apps/base/jellyfin/vaultauth.yaml +++ b/apps/base/fafflix/vaultauth.yaml @@ -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 diff --git a/apps/base/jellyfin/vaultstaticsecret.yaml b/apps/base/fafflix/vaultstaticsecret.yaml similarity index 53% rename from apps/base/jellyfin/vaultstaticsecret.yaml rename to apps/base/fafflix/vaultstaticsecret.yaml index 9de7ef2..5f53f89 100644 --- a/apps/base/jellyfin/vaultstaticsecret.yaml +++ b/apps/base/fafflix/vaultstaticsecret.yaml @@ -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 diff --git a/apps/base/jellyfin/vmpodscrape.yaml b/apps/base/fafflix/vmpodscrape.yaml similarity index 84% rename from apps/base/jellyfin/vmpodscrape.yaml rename to apps/base/fafflix/vmpodscrape.yaml index 4cf7c67..024dff2 100644 --- a/apps/base/jellyfin/vmpodscrape.yaml +++ b/apps/base/fafflix/vmpodscrape.yaml @@ -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: diff --git a/apps/overlays/au-syd1/jellyfin/kustomization.yaml b/apps/overlays/au-syd1/fafflix/kustomization.yaml similarity index 74% rename from apps/overlays/au-syd1/jellyfin/kustomization.yaml rename to apps/overlays/au-syd1/fafflix/kustomization.yaml index d8d5df6..41f104f 100644 --- a/apps/overlays/au-syd1/jellyfin/kustomization.yaml +++ b/apps/overlays/au-syd1/fafflix/kustomization.yaml @@ -3,4 +3,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - - ../../../base/jellyfin + - ../../../base/fafflix diff --git a/argocd/applicationsets/media.yaml b/argocd/applicationsets/media.yaml index f958b85..a2d3cd8 100644 --- a/argocd/applicationsets/media.yaml +++ b/argocd/applicationsets/media.yaml @@ -10,7 +10,7 @@ spec: repoURL: https://git.unkin.net/unkin/argocd-apps revision: HEAD directories: - - path: apps/overlays/*/jellyfin + - path: apps/overlays/*/fafflix - path: apps/overlays/*/cheeztv - path: apps/overlays/*/arrstack template: diff --git a/argocd/projects/media.yaml b/argocd/projects/media.yaml index 00f179d..e3c4f23 100644 --- a/argocd/projects/media.yaml +++ b/argocd/projects/media.yaml @@ -9,7 +9,7 @@ spec: sourceRepos: - https://git.unkin.net/unkin/argocd-apps destinations: - - namespace: 'jellyfin' + - namespace: 'fafflix' server: https://kubernetes.default.svc - namespace: 'cheeztv' server: https://kubernetes.default.svc