14 Commits

Author SHA1 Message Date
unkin-agent cdaab736b5 Bump jellyfin-ha to v0.4.0 (#483)
The deployed v0.3.3 build returns 500 from /Shows/NextUp on PostgreSQL, breaking the home screen, and lets replicas diverge: library-visibility and shared-config changes never propagate, user data (resume, played state, favourites, ratings) is overwritten between pods, and eight scheduled tasks run on every replica instead of only the scan leader. v0.4.0 carries the fixes.

- Pin cheeztv and fafflix to jellyfin-ha:v0.4.0.

No config change needed: cross-pod invalidation reuses the transcode-store Redis connection string both apps already set.

Reviewed-on: #483
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-22 22:57:06 +10:00
unkin-agent b31517e6d9 Merge pull request #481 from benvin/jellyfin-sso-valkey-state
Roll jellyfin-ha to v0.3.3 and drop Service session affinity

Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-20 14:02:48 +10:00
unkin-agent 34dd70435e Auto-reload cheeztv and fafflix on plugin ConfigMap change (#471)
Edits to the cheeztv/fafflix plugin ConfigMaps only reach the pods via the inject-plugin-config initContainer, so a config change sat inert until someone manually rolled the StatefulSet. Reloader is deployed cluster-wide with autoReloadAll disabled, so each workload has to opt in.

- annotate both StatefulSets with configmap.reloader.stakater.com/auto: "true"

Reviewed-on: #471
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-19 12:25:08 +10:00
unkin-agent 6cc752336e Point jellyfin SSO at public Authentik hostname (#470)
The internal-CA identity.k8s.syd1.au.unkin.net host has no CA bundle mounted in the jellyfin pods, so the OIDC discovery fetch fails TLS handshake (PartialChain). Authentik's discovery response is host-relative, so the browser-facing hostname must be used, not the internal one.

- Change OidEndpoint to identity.unkin.net in fafflix plugin config
- Change OidEndpoint to identity.unkin.net in cheeztv plugin config

Reviewed-on: #470
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-19 12:09:31 +10:00
unkin-agent 47a2ab9152 Pin jellyfin-ha image to v0.3.2 (#468)
v0.3.0 and v0.3.1 crash-looped on Postgres migration/reader bugs and were reverted. v0.3.2 fixes both and was validated end to end against production-baseline Postgres and valkey: full migration chain completes, all previously-500 endpoints return 200, RedisTranscodeSessionStore and scan-leader gating confirmed active.

- Bump jellyfin-ha image tag v0.2.0 -> v0.3.2 in cheeztv and fafflix statefulsets

Depends on a pre-sync duplicate-username check and fresh pg_dump of both databases.

Reviewed-on: #468
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-13 23:08:44 +10:00
unkin-agent 8a00ddb82c Revert jellyfin-ha to v0.2.0 (#465)
v0.3.1 crash-loops both jellyfin StatefulSets deterministically on the
RatingLevels migration (concurrent Npgsql command in progress), failing
before any schema change commits. OrderedReady updates leave ordinal-1
stuck, stranding cheeztv and fafflix single-replica with no HA.

- revert cheeztv jellyfin-ha image v0.3.1 -> v0.2.0
- revert fafflix jellyfin-ha image v0.3.1 -> v0.2.0

Unblocks the stalled StatefulSet rollout.

Reviewed-on: #465
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-12 21:38:08 +10:00
unkin-agent d6a1279efe jellyfin: bump fafflix+cheeztv to v0.3.1 (#463)
v0.3.0 (#461) crash-looped existing databases on a broken Postgres
migration path; v0.3.1 restores the migration baseline, hardens guid/FK
handling, and fixes encoding.xml parsing.

- bump cheeztv jellyfin-ha image v0.2.0 -> v0.3.1
- bump fafflix jellyfin-ha image v0.2.0 -> v0.3.1

Requires manual pre-merge database verification before merge.

Reviewed-on: #463
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-12 20:32:41 +10:00
unkin-agent 783a3db0fd revert jellyfin-ha to v0.2.0 (#461)
v0.3.0 fails an EF-Core migration on boot (NormalizedUsername column
missing), crash-looping ordinal-1 pods so the StatefulSet rolling
update stalls and cheeztv/fafflix stay single-replica. Unblocks the
stuck rollout.

- revert cheeztv statefulset image to jellyfin-ha:v0.2.0
- revert fafflix statefulset image to jellyfin-ha:v0.2.0

Reviewed-on: #461
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-12 15:22:20 +10:00
unkin-agent 84f09f89ff bump jellyfin-ha to v0.3.0 (#460)
jellyfin-ha v0.3.0 is the first build tracking Jellyfin 12.0 (.NET 10 runtime, jellyfin-web 12.0, LDAP plugin 24).

- bump cheeztv jellyfin-ha image tag to v0.3.0
- bump fafflix jellyfin-ha image tag to v0.3.0

Reviewed-on: #460
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-09-12 14:23:39 +10:00
unkin-agent e10f0c09c9 arrstack,fafflix,cheeztv: provision shared mediastore CephFS volume (#428)
## Why

The media estate is currently split across two 1Ti CephFS subvolumes (`media-tv`
and `media-movies`), so a download that lands in one and imports to the other is
a cross-filesystem copy, not a hardlink move. A single 10Ti `mediastore`
subvolume (already created on `cephfs_csi_ssd_ec_4_1`) collapses that: one
filesystem holding `/{fafflix,cheeztv}/{tvseries,movies}` plus
`/nzbget/downloads/complete`, so every arr import is an atomic same-fs hardlink.

This PR only provisions the volume and seeds its tree. No existing PV, PVC or
workload is touched, and nothing mounts the new claims yet — the cutover of the
arrs and the two jellyfins is a separate change.

## How

- Add static PVs `arrstack-mediastore`, `fafflix-mediastore` and
  `cheeztv-mediastore`, all pointing at the same rootPath
  (`/volumes/csi_ssd_ec_4_1/mediastore/a0152dac-…`) with unique names and
  volumeHandles pinned by `claimRef` — the established pattern for the shared
  media subvolumes.
- Add the matching RWX 10Ti PVCs (`mediastore` in arrstack,
  `fafflix-mediastore`, `cheeztv-mediastore`), annotated
  `k8up.io/backup: "false"` and statically bound via `volumeName` +
  `storageClassName: ""`.
- Add `mediastore-bootstrap`, a one-shot ArgoCD Sync-hook Job
  (`hook-delete-policy: BeforeHookCreation`, no sync-wave needed) that mounts
  the arrstack claim and `mkdir -p`s the directory tree as uid/gid 1000 —
  the uid the arrstack media pods run as. Idempotent, so it self-heals on
  every sync.
- Wire the new manifests into the arrstack, fafflix and cheeztv bases.

## Validation

- `kustomize build` clean on `apps/overlays/au-syd1/{arrstack,fafflix,cheeztv}`
- `kubeconform` clean on all three overlays (91 / 29 / 32 resources valid)
- pre-commit (yamllint, check-yaml, no-plain-secrets) passed

Reviewed-on: #428
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-29 21:18:09 +10:00
unkin-agent c313c7cc3f fafflix,cheeztv: declarative SSO + LDAP plugin auth config (#424)
## Summary

- Adds SSO-Auth.xml, LDAP-Auth.xml, and branding.xml plugin config (rendered by an initContainer into /config) to fafflix and cheeztv
- Adds a per-namespace oauth-credentials VaultStaticSecret to each instance
- Adds a "Sign in with SSO" login link via jellyfin branding config

## Why

Phase-1 jellyfin auth: browser SSO via the 9p4 SSO plugin (Authentik OIDC, ak_groups claim) and phone/TV app-passwords via the LDAP plugin against the Authentik LDAP outpost. Config is declarative — restarts overwrite UI edits. Activates when the plugin-baked jellyfin-ha image lands (separate PR).

Reviewed-on: #424
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-29 20:47:54 +10:00
unkin-agent 0322fc6989 jellyfin: bump fafflix+cheeztv to v0.2.0 (#426)
## Summary

- Bumps the jellyfin-ha image to v0.2.0 on fafflix and cheeztv

## Why

v0.2.0 bakes the LDAP and SSO auth plugins into the image; with the plugin config PR this enables browser SSO and app-password logins.

Reviewed-on: #426
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-29 13:22:40 +10:00
unkin-agent d42d013541 cheeztv: terminate public cheeztv.unkin.net TLS with the LE *.unkin.net wildcard (#417)
why: cheeztv.unkin.net needs a publicly-trusted cert; DNS-01 via the delegated acme.unkin.net zone is now working (staging wildcard verified end-to-end in ~70s). A single centrally-managed wildcard avoids per-host CNAMEs and LE duplicate-order rate limits.

- add Certificate wildcard-unkin-net (cert-manager ns, ClusterIssuer letsencrypt prod, secret wildcard-unkin-net-tls) with emberstack reflector secretTemplate annotations
- reflect the secret into the cheeztv namespace (append future consumers to the two reflector namespace lists)
- point the public cheeztv gateway listener at wildcard-unkin-net-tls and drop its vault-issuer annotations (cheeztv-internal stays on vault-issuer)

Reviewed-on: #417
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-25 20:58:02 +10:00
unkin-agent 94da740a9d Add cheeztv kids Jellyfin instance + fafflix kids mounts (#413)
## Why
Kids need their own safe Jellyfin front (cheeztv.unkin.net) with a library that only ever exposes kids titles, while those same titles stay resumable in the existing adult instance (fafflix). The fafflix.unkin.net domain switch is deliberately out of scope.

## How
- **New `cheeztv` instance** (`apps/base/cheeztv`): mirrors the fafflix HA Jellyfin stack 1:1 - same `jellyfin-ha:v0.1.3` fork image, 2-replica StatefulSet, RWX transcode PVC + HA takeover, Intel iGPU transcode, CNPG Postgres + PgBouncer, Valkey transcode store, k8up config backup, VMPodScrape.
- **Kids-only media**: its own static PVs on the *same* shared movies/tv CephFS subvolumes, mounted with `subPath: kids` so it sees only `movies/kids` and `tvshows/kids`.
- **Fully separate state**: own namespace, config PVC, `cheeztv-postgres` cluster, `cheeztv-valkey`, and `cnpg-cheeztv` / `cheeztv-config-backup` RGW buckets - nothing shared with fafflix.
- **DNS/cert**: `cheeztv.unkin.net` via internal Traefik + external-dns (198.18.200.4) + cert-manager `cheeztv-tls`, matching logviewer.unkin.net; plus the cluster hostname `cheeztv.k8s.syd1.au.unkin.net` mirroring fafflix.
- **fafflix change**: ADDS `movies/kids` + `tvshows/kids` subPath mounts alongside its current media mounts so kids libraries are resumable there. Existing mounts, hostname and ingress untouched.
- Registers cheeztv in the media ApplicationSet generator and AppProject destinations.

## Validation
kubeconform clean: cheeztv 28/28, jellyfin 25/25 valid. No arr/watchstate/domain changes.

Note: the k8up restic repo password must be seeded at `kv/kubernetes/namespace/cheeztv/default/k8up-restic` (same manual step as fafflix); templated Vault policy already grants read, no terraform-vault change needed.
Reviewed-on: #413
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-25 19:59:08 +10:00