From a52a419dfded1526d713ddb998a5c825a3ecc938 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sun, 9 Aug 2026 13:39:40 +1000 Subject: [PATCH] Point media apps at the real mediafs library via a static CephFS PV Why: The media apps must serve and manage the actual media library, not empty volumes. That library already exists on the puppet-managed CephFS filesystem mediafs (mounted by the VM/incus instances at /shared/media) and is in active use, so the k8s apps must mount it in place rather than provision fresh storage. How: - Replace the two fresh movies/tvseries PVCs with one static CephFS PersistentVolume bound to mediafs and a single RWX media-library claim the whole stack shares. - Set the PV reclaim policy to Retain and mark it staticVolume so ceph-csi only mounts the pre-existing storage and can never provision or reclaim it; deleting the PVC or PV cannot destroy the underlying library. - Reuse the live csi-cephfs cluster parameters (clusterID cephfs_csi_ssd_ec_4_1 for mon discovery, csi-cephfs/csi-cephfs-secret node-stage secret) with fsName mediafs and rootPath / (the mediafs root that maps to /shared/media). - Mount the library into each app by subPath so the tree matches the VM layout: sonarr /mnt/tvseries (tvseries), radarr /mnt/movies (movies), jellyfin and nzbget both subtrees; prowlarr keeps no library mount. The jellyfin transcode PVC stays a fresh scratch volume. - Whitelist PersistentVolume in the media AppProject so the cluster-scoped PV can sync. --- apps/base/jellyfin/deployment.yaml | 13 +++---- apps/base/media-apps/kustomization.yaml | 4 +- apps/base/media-apps/pv_media-library.yaml | 41 +++++++++++++++++++++ apps/base/media-apps/pvc_media-library.yaml | 19 ++++++++++ apps/base/media-apps/pvc_movies.yaml | 16 -------- apps/base/media-apps/pvc_tvseries.yaml | 16 -------- apps/base/nzbget/deployment.yaml | 13 +++---- apps/base/radarr/deployment.yaml | 7 ++-- apps/base/sonarr/deployment.yaml | 7 ++-- argocd/projects/media.yaml | 2 + 10 files changed, 84 insertions(+), 54 deletions(-) create mode 100644 apps/base/media-apps/pv_media-library.yaml create mode 100644 apps/base/media-apps/pvc_media-library.yaml delete mode 100644 apps/base/media-apps/pvc_movies.yaml delete mode 100644 apps/base/media-apps/pvc_tvseries.yaml diff --git a/apps/base/jellyfin/deployment.yaml b/apps/base/jellyfin/deployment.yaml index 0d61e6e..2ac8c23 100644 --- a/apps/base/jellyfin/deployment.yaml +++ b/apps/base/jellyfin/deployment.yaml @@ -76,10 +76,12 @@ spec: mountPath: /cache - name: transcode mountPath: /transcode - - name: movies + - name: media-library mountPath: /mnt/movies - - name: tvseries + subPath: movies + - name: media-library mountPath: /mnt/tvseries + subPath: tvseries volumes: - name: config persistentVolumeClaim: @@ -90,9 +92,6 @@ spec: - name: transcode persistentVolumeClaim: claimName: jellyfin-transcode - - name: movies + - name: media-library persistentVolumeClaim: - claimName: movies - - name: tvseries - persistentVolumeClaim: - claimName: tvseries + claimName: media-library diff --git a/apps/base/media-apps/kustomization.yaml b/apps/base/media-apps/kustomization.yaml index 73ab92a..0969cf0 100644 --- a/apps/base/media-apps/kustomization.yaml +++ b/apps/base/media-apps/kustomization.yaml @@ -6,5 +6,5 @@ resources: - namespace.yaml - serviceaccount.yaml - vaultauth.yaml - - pvc_movies.yaml - - pvc_tvseries.yaml + - pv_media-library.yaml + - pvc_media-library.yaml diff --git a/apps/base/media-apps/pv_media-library.yaml b/apps/base/media-apps/pv_media-library.yaml new file mode 100644 index 0000000..0243e24 --- /dev/null +++ b/apps/base/media-apps/pv_media-library.yaml @@ -0,0 +1,41 @@ +--- +# Static CephFS PersistentVolume bound to the pre-existing, ACTIVELY-USED +# puppet media library (ceph filesystem "mediafs", mounted by the VM/incus +# instances at /shared/media). ceph-csi only mounts this volume; staticVolume +# tells it the storage pre-exists and it must never provision or delete it. +# +# reclaimPolicy MUST stay Retain: deleting this PV or its PVC must NEVER be able +# to reclaim or destroy the underlying CephFS data that the VM instances use. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: media-apps-media-library +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 10Ti + # Load-bearing safety control. Do not change to Delete. + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + volumeMode: Filesystem + # Pre-bind to the media-library claim so nothing else can grab this PV. + claimRef: + apiVersion: v1 + kind: PersistentVolumeClaim + name: media-library + namespace: media-apps + csi: + driver: cephfs.csi.ceph.com + volumeHandle: media-apps-media-library-static + nodeStageSecretRef: + name: csi-cephfs-secret + namespace: csi-cephfs + volumeAttributes: + # clusterID maps (in the csi-cephfs ceph-csi-config) to the mon set that + # also serves mediafs; for a static volume only the mon lookup is used. + clusterID: cephfs_csi_ssd_ec_4_1 + fsName: mediafs + staticVolume: "true" + # Filesystem-internal root of the library (mediafs root == /shared/media). + rootPath: / diff --git a/apps/base/media-apps/pvc_media-library.yaml b/apps/base/media-apps/pvc_media-library.yaml new file mode 100644 index 0000000..49d0652 --- /dev/null +++ b/apps/base/media-apps/pvc_media-library.yaml @@ -0,0 +1,19 @@ +--- +# Claim bound to the static mediafs PV. RWX so every app in the stack shares the +# one library. storageClassName "" + volumeName pin it to the static PV (no +# dynamic provisioning). Deleting this claim cannot reclaim the data (PV is +# Retain). +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: media-library + namespace: media-apps +spec: + accessModes: + - ReadWriteMany + storageClassName: "" + volumeName: media-apps-media-library + resources: + requests: + storage: 10Ti + volumeMode: Filesystem diff --git a/apps/base/media-apps/pvc_movies.yaml b/apps/base/media-apps/pvc_movies.yaml deleted file mode 100644 index e90dabe..0000000 --- a/apps/base/media-apps/pvc_movies.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -# Shared movies library, read-write-many across the *arr stack and jellyfin. -# Greenfield-empty: a fresh CephFS volume, not the puppet mediafs library. -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: movies - namespace: media-apps -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 500Gi - storageClassName: cephfs-raid5-retain - volumeMode: Filesystem diff --git a/apps/base/media-apps/pvc_tvseries.yaml b/apps/base/media-apps/pvc_tvseries.yaml deleted file mode 100644 index 7218af6..0000000 --- a/apps/base/media-apps/pvc_tvseries.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -# Shared tvseries library, read-write-many across the *arr stack and jellyfin. -# Greenfield-empty: a fresh CephFS volume, not the puppet mediafs library. -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: tvseries - namespace: media-apps -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 500Gi - storageClassName: cephfs-raid5-retain - volumeMode: Filesystem diff --git a/apps/base/nzbget/deployment.yaml b/apps/base/nzbget/deployment.yaml index e15e41c..46bffe9 100644 --- a/apps/base/nzbget/deployment.yaml +++ b/apps/base/nzbget/deployment.yaml @@ -65,17 +65,16 @@ spec: volumeMounts: - name: config mountPath: /config - - name: movies + - name: media-library mountPath: /mnt/movies - - name: tvseries + subPath: movies + - name: media-library mountPath: /mnt/tvseries + subPath: tvseries volumes: - name: config persistentVolumeClaim: claimName: nzbget-config - - name: movies + - name: media-library persistentVolumeClaim: - claimName: movies - - name: tvseries - persistentVolumeClaim: - claimName: tvseries + claimName: media-library diff --git a/apps/base/radarr/deployment.yaml b/apps/base/radarr/deployment.yaml index a9489fb..9ee837c 100644 --- a/apps/base/radarr/deployment.yaml +++ b/apps/base/radarr/deployment.yaml @@ -78,8 +78,9 @@ spec: volumeMounts: - name: config mountPath: /config - - name: movies + - name: media-library mountPath: /mnt/movies + subPath: movies - name: exportarr image: ghcr.io/onedr0p/exportarr:latest imagePullPolicy: IfNotPresent @@ -134,6 +135,6 @@ spec: - name: config-template configMap: name: radarr-config-xml - - name: movies + - name: media-library persistentVolumeClaim: - claimName: movies + claimName: media-library diff --git a/apps/base/sonarr/deployment.yaml b/apps/base/sonarr/deployment.yaml index c8ebbfd..840fb34 100644 --- a/apps/base/sonarr/deployment.yaml +++ b/apps/base/sonarr/deployment.yaml @@ -78,8 +78,9 @@ spec: volumeMounts: - name: config mountPath: /config - - name: tvseries + - name: media-library mountPath: /mnt/tvseries + subPath: tvseries - name: exportarr image: ghcr.io/onedr0p/exportarr:latest imagePullPolicy: IfNotPresent @@ -134,6 +135,6 @@ spec: - name: config-template configMap: name: sonarr-config-xml - - name: tvseries + - name: media-library persistentVolumeClaim: - claimName: tvseries + claimName: media-library diff --git a/argocd/projects/media.yaml b/argocd/projects/media.yaml index 040392d..3982035 100644 --- a/argocd/projects/media.yaml +++ b/argocd/projects/media.yaml @@ -14,6 +14,8 @@ spec: clusterResourceWhitelist: - group: '' kind: Namespace + - group: '' + kind: PersistentVolume namespaceResourceWhitelist: - group: '*' kind: '*'