Files
Frank 327e78b950 docs(vault): destroy the dead weight — 53 pages and the change diary
The vault was 155 pages / 31,210 lines, most of it measurement ledgers for
payloads that no longer run. A throughput figure for a runtime nobody operates is
not a record: AI moves faster than the vault, so the number is wrong before
anyone reads it, and a wrong number in a runbook gets believed. Per the new
purpose test in `AGENTS.md` → *Documentation Policy*, it is destroyed.

Gone (`.AGENTTRASH/20261019-destruction/`, KILL-LIST.txt is the manifest):
- the `frank-medium` endpoint and its five era pages, `frank-npu`, FastFlowLM,
  the ikllama/kingjones/dealign/frank-redo eras, the ling/swift15/freetoken/ubatch
  campaign pages, qwen3.8 spec-matrix and strix-optimizations, the two
  "Running Qwen3.6 …" pages;
- the whole `Pipeline Parallelism/` folder (no live script passes a pipeline flag)
  and the multi-device/x4-pair/hybrid/offload experiment pages;
- every vLLM page — no vLLM endpoint exists on this fleet;
- `LLM/deployment.md` (`Architecture/Deployment Flow.md` is the test-referenced
  owner) and the external surveys (SGLang, Lemonade, llama.cpp forks/diff,
  self-hosted app census, the TTS survey) that were obsolete by construction;
- `log.md`, 2,380 lines. Git holds the chronology; a causal law belongs to the
  page that owns the thing it governs, never to a diary.

Survivors: 101 pages, 17,542 lines, 0 OKF violations, 0 unresolved links. Eras
tables keep the window, payload, runtime, reason and rollback pointer; the rate
columns are blanked. The live shapes stay verbatim — those numbers are what the
deployer and `models.yml` consume.
2026-10-19 10:56:37 -07:00

119 lines
5.9 KiB
Markdown

---
type: reference
title: Jellyfin
description: "Jellyfin on Dankserver: quadlet unit, entropy-volume data layout, ports, client access, hardware-transcode status."
tags: [jellyfin, dankserver, media-server]
timestamp: 2026-09-03T00: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=/mnt/entropy:/data:ro` — the whole [[Entropy Volume]] mounted read-only; nothing automated writes to it |
| 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)
/mnt/entropy -> /data (read-only media volume)
```
Library folders point at `/data` paths; because the mount uses the same
absolute path as on the host, library paths survive container recreation.
## 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
- [[Plex]] — co-resident media server on the same volume
- [[Entropy Volume]] — the 91 T array mounted read-only at `/data`
- [[Caddy]] · [[Anubis]] · [[Authentik]] — edge chain for `jelly.franksdank.cloud`