Files
serverAppConfigs/serverAppConfigs-docs/Dankserver/Jellyfin.md
T
Frank 1e1527d101 docs(media): the SSD metadata view has never been deployed on Dankserver
The vault described the overlay as the live shape. It is not: `media-view.sh
status` answers NOT MOUNTED, /var/lib/media-meta does not exist, and the
installed quadlets under ~/.config/containers/systemd still carry the pre-view
binds (/mnt/entropy:/data:ro for Jellyfin, /mnt/entropy:/mnt/entropy:ro for
Plex) while git binds the view with ConditionPathIsMountPoint. The deploy gate
(deploy-remote.sh:ensure_media_view) has therefore never run: the first
`deploy.sh dankserver --service jellyfin|plex` is an untested cutover of both
media servers, and Jellyfin's read-only sidecar error is today's expected state,
not a fault.

Same commit, the scan-acceptance law from tonight's TV handoff: it logged
`jellyfin scan: 401`, so Jellyfin never rescanned a 58k-move library until the
request was re-issued with the identical key and header form (204). Anything
that is not 204 means no scan was queued — re-issue, do not diagnose.
2026-10-31 03:13:53 -07:00

11 KiB

type, title, description, tags, timestamp
type title description tags timestamp
reference Jellyfin Jellyfin on Dankserver: quadlet unit, overlay metadata view, provider keys, ports, access, 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 (the repo quadlet; the live unit is still the pre-view generation — see Data layout)

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 in git — the Media Metadata View overlay, writable, sidecars land on the NVMe and the Entropy Volume array underneath stays untouched. The live unit is still pre-view: /mnt/entropy:/data:ro — nothing is written into the library until the view is deployed
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     (git: the overlay view)
/mnt/entropy                             -> /data     (live today: the array, read-only)

Library folders point at /data paths: stable across container recreation and identical to the raw array layout below. Which side of that path the container is on is the open question on this host — the installed unit binds the read-only array, and scripts/media-view.sh status reports NOT MOUNTED (2026-10-31).

Local metadata — save metadata to media folders — is enabled, and it is the reason the view exists: movie.nfo, tvshow.nfo, episode.nfo and artwork written once on the NVMe upper layer are files Plex can read too. While /data is bound read-only every item update logs the error below and no sidecar is written; Jellyfin becomes the fleet's only metadata writer with the deploy that raises the view.

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. On Dankserver that is today's expected state — the view has never been installed (scripts/media-view.sh status). Once it is deployed, the same line is a fault: the quadlet drifted or the mount died.

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).

Metadata providers and their keys (2026-10-30)

TMDb, OMDb, StudioImages and MusicBrainz are core plugins in Jellyfin 12 — no DLL, just a configuration file the running container owns under the data root. TVDB is not core (its metadata provider was removed in 10.9), so it is an official-catalog plugin.

Provider Shape on disk Key lives in
TMDb config/plugins/configurations/Jellyfin.Plugin.Tmdb.xml → <TmdbApiKey> /home/dank/Containers/jellyfin/provider-keys → TMDB_API_KEY= (mode 600)
TVDB config/plugins/thetvdb_24.0.0.0/ + …Jellyfin.Plugin.Tvdb.xml → <ApiKey> the same file → TVDB_API_KEY=
OMDb …Jellyfin.Plugin.Omdb.xml not configured — its key is issued by email, 1 000 req/day, and its ToS forbids storing the returned data; ratings stay Jellyfin's own lookup

None of it is tracked in git. Those XML files belong to the container and hold credentials, and tests/jellyfin_providers.bats fails the suite if one is ever committed. The keys themselves sit in the per-service host env file that is the fleet's convention for container credentials (grafana, caddy, pihole, copyparty, romm, seerr): provider-keys, mode 600, beside the data root. scripts/jellyfin-providers.sh is the only thing that writes the XML:

scripts/jellyfin-providers.sh plan      # what would change; prints no key
scripts/jellyfin-providers.sh install   # plugin + keys, previous copy to trash
scripts/jellyfin-providers.sh verify    # plugin + key LENGTHS, never key bytes
  • A key never enters git, argv, or the tool's output. The installer reads that 600-perm file itself (a value in argv is world-readable through /proc/<pid>/cmdline) and refuses it when it is group- or world-readable — an exposed key is an error, not a warning. A key that shows up in a transcript or ps output has leaked: rotate it with the provider, rewrite provider-keys, re-run install.
  • The TVDB artifact is pinned three ways: sha256 6302fba3857bdde8d186dd099fac09ca43247041f531a528a2c788add70a5319, the official plugin guid a677c0da-fac5-4cde-941a-7134223f14c8, and version 24.0.0.0 (targetAbi 12.0.0.0, which fits the deployed 12.4.0). A sha match alone only proves the bytes are the bytes fetched — the guid check is what says it is the official plugin. Rollback: move thetvdb_24.0.0.0/ into the trash and revert the pin; a second thetvdb_* directory is a refusal, never a quiet second copy.
  • The homelab TVDB key is a licensed key (its JWT claims say community_supported: false), so it needs no PIN — the optional TVDB_PIN= line exists only for a subscriber-shaped key.
  • TVDB is installed and keyed but is not a metadata agent: TMDb still identifies everything. Enabling TVDB as a provider would re-fetch show metadata across the library, so it is a deliberate, separate change.
  • The deployer places the keys before it restarts jellyfin (ensure_provider_keys in scripts/deploy-remote.sh) and warns instead of failing when provider-keys is absent, so a host without it still deploys.
  • Keys and plugins are read at start-up: after a manual install the change is inert until ./scripts/deploy.sh dankserver --service jellyfin.
  • TMDb's terms are the reason this stays narrow: attribution required, no caching beyond 6 months, no derivative works. Fine for a personal homelab, and the reason no bulk enrichment pass runs against it.

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