Deploy jellyfin-ha to ArgoCD (jellyfin namespace, mediafs library) #348

Closed
unkinben wants to merge 3 commits from benvin/media-apps-argocd into main
Owner

Why

Deploy Jellyfin under ArgoCD first, ahead of the rest of the media stack, so the HA fork can be validated against the real library before the download and manager apps follow. This PR is jellyfin-only; it deploys the jellyfin-ha fork into its own jellyfin namespace on the existing mediafs library.

What this PR contains (jellyfin only)

  • Namespace jellyfin (its own namespace).
  • jellyfin-ha Deployment (git.unkin.net/unkin/jellyfin-ha:v0.1.0), clean name jellyfin, intel iGPU nodeSelector + gpu.intel.com/i915 request preserved, Recreate strategy (RWO config).
  • jellyfin-redis Deployment + Service + PVC (HA transcode session store) and a fresh RWX jellyfin-transcode scratch PVC; jellyfin-config (RWO, retain) and jellyfin-cache (RWO) PVCs.
  • Static CephFS PV + RWX media-library PVC bound to the in-use puppet mediafs library, mounted into jellyfin by subPath (/mnt/moviesmovies, /mnt/tvseriestvseries).
  • Gateway + HTTPRoute (traefik-internal) at jellyfin.k8s.syd1.au.unkin.net.
  • media AppProject + media-apps ApplicationSet scoped to the single jellyfin namespace/app, registered in the argocd bootstrap kustomizations.

No VSO/VaultAuth: the jellyfin-ha app needs no Vault secret.

Supersedes #237 (jellyfin-ha into an empty namespace); this wires it to the real library.

Static CephFS PV — mounts the real mediafs library, never provisions/deletes it

field value
driver cephfs.csi.ceph.com
clusterID cephfs_csi_ssd_ec_4_1 (maps to mons 198.18.23.9-13, which also serve mediafs)
fsName mediafs
rootPath / (mediafs root == /shared/media)
staticVolume "true" (ceph-csi mounts only; never provisions/deletes)
nodeStageSecretRef csi-cephfs/csi-cephfs-secret
persistentVolumeReclaimPolicy Retain — load-bearing

SAFETY — mediafs must never be destroyed

  • PV is persistentVolumeReclaimPolicy: Retain + staticVolume: "true": deleting the k8s PVC or PV can never reclaim/format/delete the underlying CephFS data that the VM/incus instances actively use.
  • Nothing in this PR provisions, formats, or deletes the library — jellyfin only mounts it.

Operator prerequisite (ceph-side, out of band — no key committed)

csi-cephfs-secret (userID/userKey) must have MDS/OSD caps for the mediafs filesystem (it is currently scoped to the CSI subvolumegroups on the cephfs fs). Grant that ceph client caps on mediafs (or supply a secret carrying the puppet client.media key) before first sync, or the mount fails permission-denied.

Verify-before-apply

Confirm /shared/media is the mediafs root (not a subtree). If it is a subtree, set the PV rootPath to that subtree path.

FUTURE — three-namespace layout (documented only; NOT built here)

The remaining six apps join in later PRs, split across three namespaces:

  • jellyfin — jellyfin (this PR).
  • media-managers — sonarr, radarr, prowlarr, bazarr.
  • media-downloaders — nzbget (may add/switch to a bittorrent client later).

Each future namespace mounts the same static mediafs library (or its own static PV to the same fs) with the same Retain/staticVolume guarantees. Once jellyfin is validated, the manager/downloader apps are added and terraform-{sonarr,radarr,prowlarr} get repointed off the VM/incus instances (see below).

FOLLOW-UPS (flagged, NOT done here)

  • Repoint + decommission (dual-writer risk): the k8s apps are intended to REPLACE the incus/VM instances. While both mount the same library, two *arr/media writers on one tree can conflict — bring up k8s, validate, then promptly retire the VM/incus instances and repoint terraform-{sonarr,radarr,prowlarr} from <app>.service.consul to the k8s instances. Do not run both long-term.
  • terraform-k8s cleanup is surgical: removes ONLY the legacy empty k8s objects; it must NEVER destroy/state rm anything touching the mediafs filesystem or the static jellyfin-media-library PV.

Validated: kustomize build + kubeconform clean (14 resources); pre-commit (incl. no-secrets) passing.

## Why Deploy Jellyfin under ArgoCD first, ahead of the rest of the media stack, so the HA fork can be validated against the real library before the download and manager apps follow. This PR is **jellyfin-only**; it deploys the jellyfin-ha fork into its own `jellyfin` namespace on the existing mediafs library. ## What this PR contains (jellyfin only) - **Namespace `jellyfin`** (its own namespace). - **jellyfin-ha** Deployment (`git.unkin.net/unkin/jellyfin-ha:v0.1.0`), clean name `jellyfin`, intel iGPU `nodeSelector` + `gpu.intel.com/i915` request preserved, `Recreate` strategy (RWO config). - **`jellyfin-redis`** Deployment + Service + PVC (HA transcode session store) and a fresh RWX **`jellyfin-transcode`** scratch PVC; `jellyfin-config` (RWO, retain) and `jellyfin-cache` (RWO) PVCs. - **Static CephFS PV + RWX `media-library` PVC** bound to the in-use puppet `mediafs` library, mounted into jellyfin by subPath (`/mnt/movies`←`movies`, `/mnt/tvseries`←`tvseries`). - Gateway + HTTPRoute (traefik-internal) at `jellyfin.k8s.syd1.au.unkin.net`. - `media` AppProject + `media-apps` ApplicationSet scoped to the single `jellyfin` namespace/app, registered in the argocd bootstrap kustomizations. No VSO/VaultAuth: the jellyfin-ha app needs no Vault secret. Supersedes #237 (jellyfin-ha into an empty namespace); this wires it to the real library. ## Static CephFS PV — mounts the real mediafs library, never provisions/deletes it | field | value | |---|---| | `driver` | `cephfs.csi.ceph.com` | | `clusterID` | `cephfs_csi_ssd_ec_4_1` (maps to mons 198.18.23.9-13, which also serve mediafs) | | `fsName` | `mediafs` | | `rootPath` | `/` (mediafs root == `/shared/media`) | | `staticVolume` | `"true"` (ceph-csi mounts only; never provisions/deletes) | | `nodeStageSecretRef` | `csi-cephfs/csi-cephfs-secret` | | **`persistentVolumeReclaimPolicy`** | **`Retain`** — load-bearing | ## SAFETY — mediafs must never be destroyed - PV is **`persistentVolumeReclaimPolicy: Retain`** + **`staticVolume: "true"`**: deleting the k8s PVC or PV can never reclaim/format/delete the underlying CephFS data that the VM/incus instances actively use. - Nothing in this PR provisions, formats, or deletes the library — jellyfin only mounts it. ## Operator prerequisite (ceph-side, out of band — no key committed) `csi-cephfs-secret` (`userID`/`userKey`) must have MDS/OSD caps for the `mediafs` filesystem (it is currently scoped to the CSI subvolumegroups on the `cephfs` fs). Grant that ceph client caps on `mediafs` (or supply a secret carrying the puppet `client.media` key) **before** first sync, or the mount fails permission-denied. ## Verify-before-apply Confirm `/shared/media` is the `mediafs` root (not a subtree). If it is a subtree, set the PV `rootPath` to that subtree path. ## FUTURE — three-namespace layout (documented only; NOT built here) The remaining six apps join in later PRs, split across three namespaces: - **`jellyfin`** — jellyfin (this PR). - **`media-managers`** — sonarr, radarr, prowlarr, bazarr. - **`media-downloaders`** — nzbget (may add/switch to a bittorrent client later). Each future namespace mounts the same static mediafs library (or its own static PV to the same fs) with the same Retain/staticVolume guarantees. Once jellyfin is validated, the manager/downloader apps are added and terraform-{sonarr,radarr,prowlarr} get repointed off the VM/incus instances (see below). ## FOLLOW-UPS (flagged, NOT done here) - **Repoint + decommission (dual-writer risk):** the k8s apps are intended to REPLACE the incus/VM instances. While both mount the same library, two `*arr`/media writers on one tree can conflict — bring up k8s, validate, then promptly retire the VM/incus instances and repoint terraform-{sonarr,radarr,prowlarr} from `<app>.service.consul` to the k8s instances. Do not run both long-term. - **terraform-k8s cleanup is surgical:** removes ONLY the legacy empty k8s objects; it must NEVER `destroy`/`state rm` anything touching the `mediafs` filesystem or the static `jellyfin-media-library` PV. Validated: kustomize build + kubeconform clean (14 resources); pre-commit (incl. no-secrets) passing.
unkinben added 1 commit 2026-08-09 13:25:43 +10:00
Add media-apps stack to ArgoCD
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
e03aeca101
Why:
The media stack (jellyfin plus the sonarr/radarr/prowlarr/bazarr/nzbget/
jellyseerr apps) runs in the media-apps namespace but is deployed out-of-band
by terraform-k8s rather than GitOps. Bringing it under ArgoCD makes the stack
declarative, self-healing, and consistent with every other cluster workload,
and prepares terraform-k8s to drop the media-apps config.

How:
- Add a media AppProject scoped to the media-apps namespace and a media-apps
  ApplicationSet that renders one Application per app plus a shared foundation.
- Add a shared media-apps foundation (namespace, media-apps-vault-reader
  ServiceAccount, default VaultAuth on k8s/au/syd1, and the RWX movies/tvseries
  library PVCs) that the whole stack mounts.
- Add per-app kustomize base and au-syd1 overlay for jellyfin and the six *arr
  apps, using plain resource names (jellyfin, sonarr, ...) with fresh PVCs.
- Deploy jellyfin from the jellyfin-ha fork (Redis transcode store, RWX
  transcode scratch) wired to the shared movies/tvseries library PVCs, keeping
  the intel iGPU nodeSelector and gpu.intel.com/i915 request.
- Source API keys and nzbget credentials through VSO VaultStaticSecrets from
  kv/service/media-apps/<app>; expose each app via a traefik-internal Gateway
  and HTTPRoute at <app>.k8s.syd1.au.unkin.net.
- Register the media project and applicationset in the argocd bootstrap
  kustomizations.
unkinben added 1 commit 2026-08-09 13:39:42 +10:00
Point media apps at the real mediafs library via a static CephFS PV
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
a52a419dfd
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.
unkinben added 1 commit 2026-08-09 21:08:14 +10:00
Reduce media PR to jellyfin-only in its own namespace
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
54c25be828
Why:
Jellyfin ships and gets validated first, ahead of the rest of the media stack.
Scoping this PR to jellyfin alone keeps the initial rollout small and lets the
HA fork prove out against the real library before the download and manager apps
follow.

How:
- Drop sonarr, radarr, prowlarr, bazarr, nzbget, and jellyseerr and their shared
  media-apps foundation from this PR; they land in later PRs.
- Move jellyfin into its own jellyfin namespace and fold the namespace and the
  static mediafs PV plus its RWX claim into the jellyfin base.
- Keep the static CephFS PV bound to the in-use mediafs library with
  reclaimPolicy Retain and staticVolume true so nothing can reclaim it, mounted
  into jellyfin by the movies and tvseries subPaths; keep redis, the fresh RWX
  transcode scratch, the intel iGPU nodeSelector and i915 request, gateway, and
  httproute.
- Scope the media AppProject and ApplicationSet to the single jellyfin
  namespace and app, extensible as the remaining apps are added.
unkinben changed title from Add media-apps stack to ArgoCD to Deploy jellyfin-ha to ArgoCD (jellyfin namespace, mediafs library) 2026-08-09 21:08:49 +10:00
unkinben closed this pull request 2026-08-10 23:44:52 +10:00
All checks were successful
ci/woodpecker/pr/vector-test Pipeline was successful
Required
Details
ci/woodpecker/pr/pre-commit Pipeline was successful
Required
Details
ci/woodpecker/pr/kubeconform Pipeline was successful
Required
Details

Pull request closed

Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/argocd-apps#348