diff --git a/apps/base/arrstack/kustomization.yaml b/apps/base/arrstack/kustomization.yaml index f975b7d..1ddf27f 100644 --- a/apps/base/arrstack/kustomization.yaml +++ b/apps/base/arrstack/kustomization.yaml @@ -8,9 +8,12 @@ resources: - pv-media-tv.yaml - pv-media-movies.yaml - pv-mediafs.yaml + - pv-mediastore.yaml - pvc-media-tv.yaml - pvc-media-movies.yaml - pvc-mediafs.yaml + - pvc-mediastore.yaml + - mediastore-bootstrap-job.yaml - media-bucket.yaml - backups-bucket.yaml - postgres diff --git a/apps/base/arrstack/mediastore-bootstrap-job.yaml b/apps/base/arrstack/mediastore-bootstrap-job.yaml new file mode 100644 index 0000000..846b070 --- /dev/null +++ b/apps/base/arrstack/mediastore-bootstrap-job.yaml @@ -0,0 +1,74 @@ +--- +# Seeds the directory skeleton on the freshly created mediastore subvolume so +# the arrs, nzbget and both jellyfins mount subPaths that already exist and are +# owned by uid/gid 1000 (the uid every arrstack media pod runs as). mkdir -p is +# idempotent, so re-running it on every sync is harmless and self-heals a tree +# someone deleted by hand. +# +# Sync hook with BeforeHookCreation delete: ArgoCD replaces the completed Job +# each sync instead of failing on the immutable pod template. No sync-wave is +# needed -- the PVC applies in the same wave and the pod simply stays Pending +# until it binds. +apiVersion: batch/v1 +kind: Job +metadata: + name: mediastore-bootstrap + namespace: arrstack + annotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + backoffLimit: 6 + ttlSecondsAfterFinished: 600 + template: + metadata: + labels: + app: mediastore-bootstrap + spec: + serviceAccountName: default + automountServiceAccountToken: false + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + seccompProfile: + type: RuntimeDefault + containers: + - name: mkdir + image: docker.io/library/alpine:3 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + set -eu + mkdir -p \ + /media/fafflix/tvseries \ + /media/fafflix/movies \ + /media/cheeztv/tvseries \ + /media/cheeztv/movies \ + /media/nzbget/downloads/complete + ls -la /media + volumeMounts: + - name: mediastore + mountPath: /media + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 200m + memory: 128Mi + volumes: + - name: mediastore + persistentVolumeClaim: + claimName: mediastore diff --git a/apps/base/arrstack/pv-mediastore.yaml b/apps/base/arrstack/pv-mediastore.yaml new file mode 100644 index 0000000..cad3614 --- /dev/null +++ b/apps/base/arrstack/pv-mediastore.yaml @@ -0,0 +1,32 @@ +--- +# Static PV for the shared MEDIASTORE CephFS subvolume: one 10Ti filesystem +# holding every library plus the nzbget download tree, so arr imports are +# same-filesystem hardlink moves across tv AND movies. Same rootPath as the +# fafflix/cheeztv mediastore PVs; each namespace gets its own PV (unique name + +# volumeHandle) pinned by claimRef. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: arrstack-mediastore +spec: + capacity: + storage: 10Ti + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + volumeMode: Filesystem + claimRef: + namespace: arrstack + name: mediastore + csi: + driver: cephfs.csi.ceph.com + volumeHandle: arrstack-mediastore-static + nodeStageSecretRef: + name: csi-cephfs-secret + namespace: csi-cephfs + volumeAttributes: + staticVolume: "true" + clusterID: cephfs_csi_ssd_ec_4_1 + fsName: cephfs + rootPath: /volumes/csi_ssd_ec_4_1/mediastore/a0152dac-a51b-4b95-ac5e-ecdd99bfe3f1 diff --git a/apps/base/arrstack/pvc-mediastore.yaml b/apps/base/arrstack/pvc-mediastore.yaml new file mode 100644 index 0000000..b6a4ebd --- /dev/null +++ b/apps/base/arrstack/pvc-mediastore.yaml @@ -0,0 +1,22 @@ +--- +# Whole media tree (/fafflix, /cheeztv, /nzbget) on one RWX filesystem, shared +# across the sonarr/radarr/nzbget pods. Statically bound to the +# arrstack-mediastore PV (the same CephFS subvolume fafflix and cheeztv mount). +# storageClassName "" + volumeName disables dynamic provisioning and binds the +# pre-created static PV. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mediastore + namespace: arrstack + annotations: + k8up.io/backup: "false" +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Ti + storageClassName: "" + volumeName: arrstack-mediastore + volumeMode: Filesystem diff --git a/apps/base/cheeztv/kustomization.yaml b/apps/base/cheeztv/kustomization.yaml index d2454f3..3c953ff 100644 --- a/apps/base/cheeztv/kustomization.yaml +++ b/apps/base/cheeztv/kustomization.yaml @@ -15,8 +15,10 @@ resources: - pvc-transcode.yaml - pv-media-tv.yaml - pv-media-movies.yaml + - pv-mediastore.yaml - pvc-media-tv.yaml - pvc-media-movies.yaml + - pvc-mediastore.yaml - statefulset.yaml - plugin-configmap.yaml - pdb.yaml diff --git a/apps/base/cheeztv/pv-mediastore.yaml b/apps/base/cheeztv/pv-mediastore.yaml new file mode 100644 index 0000000..91ae2a5 --- /dev/null +++ b/apps/base/cheeztv/pv-mediastore.yaml @@ -0,0 +1,31 @@ +--- +# Static PV for the shared MEDIASTORE CephFS subvolume. Same rootPath as +# arrstack's mediastore PV so the arrs write and cheeztv reads the identical +# library tree (cheeztv scans /cheeztv/{tvseries,movies}); each namespace gets +# its own PV (unique name + volumeHandle) pinned by claimRef. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: cheeztv-mediastore +spec: + capacity: + storage: 10Ti + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + volumeMode: Filesystem + claimRef: + namespace: cheeztv + name: cheeztv-mediastore + csi: + driver: cephfs.csi.ceph.com + volumeHandle: cheeztv-mediastore-static + nodeStageSecretRef: + name: csi-cephfs-secret + namespace: csi-cephfs + volumeAttributes: + staticVolume: "true" + clusterID: cephfs_csi_ssd_ec_4_1 + fsName: cephfs + rootPath: /volumes/csi_ssd_ec_4_1/mediastore/a0152dac-a51b-4b95-ac5e-ecdd99bfe3f1 diff --git a/apps/base/cheeztv/pvc-mediastore.yaml b/apps/base/cheeztv/pvc-mediastore.yaml new file mode 100644 index 0000000..95e6fe2 --- /dev/null +++ b/apps/base/cheeztv/pvc-mediastore.yaml @@ -0,0 +1,24 @@ +--- +# Shared media tree, read-many across replicas. Statically bound to the +# cheeztv-mediastore PV (the CephFS subvolume also used by arrstack and +# fafflix). storageClassName "" + volumeName disables dynamic provisioning and +# binds the pre-created static PV. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cheeztv-mediastore + namespace: cheeztv + annotations: + # Exclude from the cheeztv-config k8up Schedule (skipWithoutAnnotation is + # false cluster-wide, so unannotated PVCs are swept in). Only cheeztv-config + # is backed up; the media library is not restic-backup material. + k8up.io/backup: "false" +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Ti + storageClassName: "" + volumeName: cheeztv-mediastore + volumeMode: Filesystem diff --git a/apps/base/fafflix/kustomization.yaml b/apps/base/fafflix/kustomization.yaml index d2454f3..3c953ff 100644 --- a/apps/base/fafflix/kustomization.yaml +++ b/apps/base/fafflix/kustomization.yaml @@ -15,8 +15,10 @@ resources: - pvc-transcode.yaml - pv-media-tv.yaml - pv-media-movies.yaml + - pv-mediastore.yaml - pvc-media-tv.yaml - pvc-media-movies.yaml + - pvc-mediastore.yaml - statefulset.yaml - plugin-configmap.yaml - pdb.yaml diff --git a/apps/base/fafflix/pv-mediastore.yaml b/apps/base/fafflix/pv-mediastore.yaml new file mode 100644 index 0000000..717c2ca --- /dev/null +++ b/apps/base/fafflix/pv-mediastore.yaml @@ -0,0 +1,31 @@ +--- +# Static PV for the shared MEDIASTORE CephFS subvolume. Same rootPath as +# arrstack's mediastore PV so the arrs write and fafflix reads the identical +# library tree (fafflix scans /fafflix/{tvseries,movies}); each namespace gets +# its own PV (unique name + volumeHandle) pinned by claimRef. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: fafflix-mediastore +spec: + capacity: + storage: 10Ti + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + volumeMode: Filesystem + claimRef: + namespace: fafflix + name: fafflix-mediastore + csi: + driver: cephfs.csi.ceph.com + volumeHandle: fafflix-mediastore-static + nodeStageSecretRef: + name: csi-cephfs-secret + namespace: csi-cephfs + volumeAttributes: + staticVolume: "true" + clusterID: cephfs_csi_ssd_ec_4_1 + fsName: cephfs + rootPath: /volumes/csi_ssd_ec_4_1/mediastore/a0152dac-a51b-4b95-ac5e-ecdd99bfe3f1 diff --git a/apps/base/fafflix/pvc-mediastore.yaml b/apps/base/fafflix/pvc-mediastore.yaml new file mode 100644 index 0000000..ceb0011 --- /dev/null +++ b/apps/base/fafflix/pvc-mediastore.yaml @@ -0,0 +1,24 @@ +--- +# Shared media tree, read-many across replicas. Statically bound to the +# fafflix-mediastore PV (the CephFS subvolume also used by arrstack and +# cheeztv). storageClassName "" + volumeName disables dynamic provisioning and +# binds the pre-created static PV. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: fafflix-mediastore + namespace: fafflix + annotations: + # 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: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Ti + storageClassName: "" + volumeName: fafflix-mediastore + volumeMode: Filesystem