Files
Frank 3b29e173c2 refactor(media): mount the view inside /var/lib/media-meta instead of under /mnt
The merged door was /mnt/media, which read like a third volume and split the host
state across two trees. It is now /var/lib/media-meta/view, so the array layer,
the SSD upper layer, the kernel workdir, the reap trash and the merged tree are
one directory — and the mount table gains no new /mnt entry beside the array.

systemd names a mount unit after its ESCAPED path, so the repo copy keeps the
readable name media-meta-view.mount while `install` writes the host file as
var-lib-media\x2dmeta-view.mount; `plan` prints both and the page records them, so
nobody hunts for a unit that "does not exist". install also creates the mount
point itself, because systemd will not.

The seam now additionally pins that the unit's Where= is exactly the path both
containers bind — guard and bind cannot drift apart. The Quadlet Lifecycle
phantom-path example is corrected too: it named an invented /mnt/media/*, while
the real phantom is the legacy /opt/jellyfin/* that dankserver/compose.yaml still
carries for a service whose roots moved to /home/dank/Containers/jellyfin.
2026-10-30 22:05:31 -07:00

6.7 KiB

type, title, description, tags, timestamp
type title description tags timestamp
reference Jellyfin Jellyfin on Dankserver: quadlet unit, overlay metadata view layout, ports, access, hardware-transcode status.
jellyfin
dankserver
media-server
2026-10-30T00:00:00Z

Jellyfin

Jellyfin is the free media server on Dankserver — a podman quadlet service (jellyfin.service, generated from dankserver/jellyfin/quadlet/jellyfin.container) serving music, movies, and shows over LAN. It replaced the earlier research brief; it runs alongside Plex, which remains the primary server for some clients.

Host: Dankserver (192.168.0.235) — Access: http://192.168.0.235:8096 public vhost jelly.franksdank.cloud behind the Anubis guard chain → Authentik outpost (post-auth route proxies localhost:8096).

Facts (verified from the live unit, 2026-09-03)

Item Value
Unit ~/.config/containers/systemd/jellyfin.container → jellyfin.service (user manager), Restart=always, RestartSec=5
Image ghcr.io/jellyfin/jellyfin:latest
Network dankserver_appnet; publishes 8096/tcp, 8920/tcp, 7359/udp, 1900/udp
Data roots /home/dank/Containers/jellyfin/config:/config, /home/dank/Containers/jellyfin/cache:/cache
Media Volume=/var/lib/media-meta/view:/data — the Media Metadata View overlay, writable; sidecars land on the NVMe, the Entropy Volume array underneath stays read-only and untouched
GPU none — no AddDevice; CPU decode only (jellyfin-ffmpeg path)
Env quirks DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true, LD_PRELOAD=libjemalloc.so.2, MALLOC_TRIM_THRESHOLD_=131072 kept verbatim from the compose-era container (zero-drift cutover)
Health curl -f http://localhost:8096/health every 30 s

Data layout

/home/dank/Containers/jellyfin/config   -> /config   (JELLYFIN_DATA_DIR)
/home/dank/Containers/jellyfin/cache    -> /cache    (JELLYFIN_CACHE_DIR, XDG_CACHE_HOME)
/var/lib/media-meta/view                -> /data     (the SSD-backed overlay view)

Library folders point at /data paths, which are the view's paths: stable across container recreation, and identical to the raw array layout below it.

Local metadata — save metadata to media folders — is enabled, and it works because /data is the Media Metadata View rather than the read-only array: movie.nfo, tvshow.nfo, episode.nfo and artwork are written once, on the NVMe upper layer, and Plex reads the same files. Jellyfin is the fleet's only metadata writer.

Failure signature: System.IO.IOException: Read-only file system : '/data/Movies/<film>/movie.nfo' means the container is binding the raw array instead of the view — the view is unmounted or the quadlet drifted. That used to be the expected state (pre-view, every item update logged it); today it is a fault. Run scripts/media-view.sh status.

Ports & access

Port Proto Purpose
8096 TCP Web UI + API (JELLYFIN_PublishedServerUrl=http://192.168.0.235:8096)
8920 TCP DLNA/UPnP root device control
7359 UDP service discovery (client auto-discovery)
1900 UDP SSDP discovery

Client access paths and the public-exposure policy (auth wall required before any public vhost; Plex-style claim tokens do not exist here — Jellyfin uses its own users/API keys) follow the fleet Authentik/Anubis chain pattern.

Authentik LDAP authentication (2026-09-25)

Jellyfin has no native external-auth, so it authenticates users through the LDAP-Auth plugin (jellyfin-plugin-ldapauth) against an Authentik LDAP provider. A user logs in with their authentik username + password; the plugin binds as a service account, searches the directory, and rebinds as the user to verify the password.

authentik side (on Dankclaw):

  • LDAP provider Jellyfin LDAP (Base DN dc=franksdank,dc=cloud, bind/search mode direct) + application Jellyfin.
  • LDAP outpost Jellyfin LDAP Outpost → container authentik-outpost-ldap (quadlet dankclaw/authentik/quadlet/authentik-outpost-ldap.container) publishing 389/636 on the Dankclaw host for cross-host binds.
  • Service account svc-jellyfin-ldap (DN cn=svc-jellyfin-ldap,ou=users,dc=franksdank,dc=cloud).
  • Access groups jellyfin_users (search filter (memberOf=cn=jellyfin_users,ou=groups,dc=franksdank,dc=cloud)) and jellyfin_admins (admin filter (cn=jellyfin_admins)). Add a user to jellyfin_users to grant them Jellyfin access; jellyfin_admins grants the Jellyfin admin role.

Jellyfin side (plugin config, set via admin → Plugins → LDAP-Auth):

  • Server 192.168.0.143:389 (plain LDAP over the trusted LAN), Base DN dc=franksdank,dc=cloud, bind user = the service-account DN, search attributes uid,cn,mail,displayName, uid attr uid, username attr cn CreateUsersFromLdap=true, all folders enabled.

Gotcha — the bind credential is a PASSWORD, not a token. The LDAP bind is verified by authentik's default authentication flow (standard-password backend). The service account must have a password set (admin → user → Recovery → Set password); an API/app token is not accepted as the bind password and the bind fails with flow error password: Invalid. The service-account password is stored only in the Jellyfin plugin config (the LdapBindPassword field) — it is never committed to this repo.

Verification (2026-09-25): ldapsearch bind + the jellyfin_users search both succeed from Dankclaw; a real user (alex, in jellyfin_users) logged into the web UI with their authentik password and the plugin created the matching Jellyfin user. Gotcha — the login endpoint changed in Jellyfin 12.1. The legacy POST /Users/Auth/Password route (and its LegacyEmbyRouteRewriteMiddleware) was removed; probing it now returns a silent 401 with no plugin log, which reads like an auth failure but is just a dead route. The web UI and modern clients use POST /Users/AuthenticateByName (with the Authorization: MediaBrowser Client=…, DeviceId=…, Device=…, Version=… header), which is where the LDAP plugin runs. Verified 2026-09-25: alex authenticates over AuthenticateByName (HTTP 200; full plugin flow logged — service bind, search, ldapUid, user bind, token).

Configuration workflow

  1. Create libraries pointing at /data folders (movies, shows, audio).
  2. Set metadata/language preferences (LANG=en_US.UTF-8 pinned in the unit).
  3. Configure clients to use http://192.168.0.235:8096 or the guarded vhost.

See Also