Files
serverAppConfigs/serverAppConfigs-docs/Dankserver/Jellyfin.md
T

227 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Jellyfin
Jellyfin is a free, open-source media server and suite of applications for organizing, managing, and streaming personal media collections (movies, TV shows, music, photos) to networked devices. It is a community fork of Emby, written in C# (.NET), and uses FFmpeg for transcoding.
**Host:** Dankserver — **Hostname:** `jellyfin.dankserver.local` — **Docs:** https://jellyfin.org
## Facts
| Item | Value |
|------|-------|
| Latest version | Check https://jellyfin.org/downloads |
| Image | `jellyfin/jellyfin:latest` (Docker Hub) or `ghcr.io/jellyfin/jellyfin` |
| Base | Debian-based |
| Primary port | 8096 (HTTP, web UI + API) |
| HTTPS port | 8920 (optional) |
| Client discovery | 7359/udp |
| DLNA | 1900/udp (optional) |
| GPU passthrough | `/dev/dri` (Intel QSV / AMD VA-API) or NVIDIA NVENC |
| License | MIT |
## Architecture
```mermaid
graph LR
U[User] --> C[Caddy :80/:443]
C --> J[Jellyfin :8096]
J --> Lib[/media/movies]
J --> Lib2[/media/shows]
J --> Lib3[/media/music]
J --> Conf[Jellyfin config]
J --> Cache[Jellyfin cache]
```
## Docker Compose (reference — retired 2026-09-01; live mounts use `/home/dank/Containers/jellyfin`, see Deployment below)
```yaml
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
user: "1000:1000"
ports:
- "8096:8096/tcp"
- "8920:8920/tcp"
- "7359:7359/udp"
- "1900:1900/udp"
environment:
- JELLYFIN_PublishedServerUrl=http://192.168.0.235:8096
- DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true
volumes:
- /opt/jellyfin/config:/config
- /opt/jellyfin/cache:/cache
- /mnt/media/movies:/data/movies:ro
- /mnt/media/shows:/data/shows:ro
devices:
- /dev/dri:/dev/dri
group_add:
- "992" # Host render group GID — verify with `getent group render`
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
interval: 30s
timeout: 10s
retries: 3
```
## Caddy Configuration
```caddy
jellyfin.dankserver.local {
reverse_proxy 127.0.0.1:8096
transport http {
versions h2c 1.1
}
}
```
Set **Base URL** to `/` (or the path you configure) in Jellyfin Dashboard → Networking. Add `127.0.0.1` to **Known Proxies**.
## Storage
| Mount | Purpose | Size Estimate |
|-------|---------|---------------|
| `/home/dank/Containers/jellyfin/config` | SQLite database, settings, cached images | 10–100 GB |
| `/home/dank/Containers/jellyfin/cache` | Transcoding segments, extracted posters | Self-cleaning per session |
| `/mnt/entropy/Movies`, `/mnt/entropy/TVShows` | Media, **read-only binds** (`:ro`) — no automated write path | — |
The database must be on local storage (SSD/NVMe preferred). Media libraries can be on NFS/mergerfs. If media is on NFS, set `DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true`.
## Deployment (Quadlet)
Since 2026-09-01: `dankserver/jellyfin/quadlet/jellyfin.container` →
`jellyfin.service` (generator-enabled), deployed with
`./scripts/deploy.sh dankserver --service jellyfin`. GPU passthrough uses
`AddDevice=/dev/dri` — plain `Device=` is **not** a quadlet key and makes the
v1 generator reject the whole unit (learned live: "unsupported key 'Device'
in group 'Container'"). Entropy mounts stay `:ro`; the container runs as
root inside the user namespace, so the render-node ACLs apply via the device
cgroup regardless of `GroupAdd=keep-groups`.
## Authentication
- **Built-in auth:** per-user libraries, parental controls, max bitrate
limits. The local `dank` admin account is built-in; its password is
Frank's bootstrap value — never recorded in this repo. (2026-09-01: a
corrupted session-archive reading briefly suggested the password had
changed; verified offline against the stored PBKDF2-SHA512 hash, the
bootstrap value still authenticates.)
- **LDAP via Authentik (live 2026-09-01):** the LDAP-Auth plugin
authenticates users against the authentik LDAP outpost on Dankclaw — see
[[Authentik]] for the outpost unit, provider, and RBAC objects.
### LDAP-Auth plugin (v23.0.0.0, live 2026-09-01)
- **Install from the GitHub releases zip** — `repo.jellyfin.org`'s stable
manifest path 404s here, so fetch
`jellyfin/jellyfin-plugin-ldapauth` release asset
`ldap-authentication_23.0.0.0.zip` from GitHub and unzip into
`config/plugins/LDAP-Auth_23.0.0.0/`. The config lives at
`config/plugins/configurations/LDAP-Auth.xml` — write it directly, then
restart the service.
- **Live config:** server `192.168.0.143:389`, base DN
`dc=franksdank,dc=cloud`, bind DN
`cn=svc-jellyfin-ldap,ou=users,dc=franksdank,dc=cloud`, bind password =
the authentik outpost token (secret — the XML lives on the host only,
never in the repo), search filter
`(memberOf=cn=jellyfin_users,ou=groups,dc=franksdank,dc=cloud)`, admin
filter on `cn=jellyfin_admins,ou=groups,…`, `CreateUsersFromLdap=true`
(auto-create on first login).
- **Attribute separator is a comma.** `LdapSearchAttributes` is
`Split(',')` in the plugin source — a semicolon-separated list
(`uid;cn;mail;displayName`) parses as ONE bogus attribute, the search
silently matches nothing, and the log says `Found no users matching X`.
Use `uid,cn,mail,displayName`.
- **Username attribute is `cn`.** Authentik 2026.x maps `uid` to an opaque
per-user hash, not the username, so `uid=<name>` never matches. Set
`LdapUsernameAttribute=cn` (authentik exposes the username as `cn`).
- **Verify with a throwaway user:** create a test member of
`jellyfin_users` in authentik, then
`POST /Users/AuthenticateByName` with headers `Content-Type:
application/json` **and** `X-Emby-Authorization: MediaBrowser
Client="…", Device="…", DeviceId="…", Version="10.11.0"` — both are
required in 10.11 (missing `X-Emby-Authorization` → 400). 200 + non-admin
policy = LDAP path works. Delete the test user from authentik **and**
Jellyfin afterward.
- **Plugin load lines only show in `journalctl --user -u jellyfin.service`**
(`Loaded plugin: LDAP-Auth 23.0.0.0`) — `podman logs` does not carry the
loader lines in this image.
## Metadata Scraping
| Provider | Content | Type |
|----------|---------|------|
| TheMovieDb (TMDb) | Movies, TV shows, posters, fan art, overviews | Default |
| OMDb API | Movies only, English metadata | Default |
| TheTVDB | TV shows, episodes | Plugin |
| fanart.tv | Extra artwork, banners, thumbnails | Plugin |
| AniDB | Anime metadata | Plugin |
| MusicBrainz | Music metadata | Plugin |
| Open Subtitles | Subtitle downloads | Plugin |
| Intro Skipper | TV show intro detection | Plugin |
## Clients
| Platform | App | Quality |
|----------|-----|---------|
| Web | Jellyfin Web | Excellent |
| Android | Jellyfin (Google Play / F-Droid) | Excellent |
| Android TV / Fire TV | Jellyfin | Good |
| iOS / iPadOS | Swiftfin (third-party) | Good |
| Desktop | Jellyfin Media Player (mpv-based) | Excellent |
| Roku | Jellyfin (Channel Store) | Good |
| Kodi | Jellyfin addon | Excellent |
| Samsung/LG Smart TVs | TV browser | Fair |
## Jellyfin vs Plex
| Feature | Jellyfin | Plex |
|---------|----------|------|
| Cost | Free, no tiers | Free core; Plex Pass ($5/mo or $120 lifetime) |
| License | MIT | Proprietary |
| Account required | No | Yes |
| Hardware transcoding | Free | Plex Pass required |
| Mobile sync | Free | Plex Pass |
| Live TV / DVR | Free | Plex Pass |
| Client apps | Good, improving | Excellent, widest device support |
Run Jellyfin alongside Plex for an incremental migration. Point Jellyfin at the same media folders as Plex. Watch history and metadata will not transfer cleanly.
## Resource Requirements
- **Idle / Direct Play:** ~200–500 MB RAM, <5% CPU. Negligible.
- **1080p transcode (CPU):** 40–60% CPU on a modern multi-core CPU.
- **1080p transcode (with QSV):** 3–5% CPU.
- **4K HDR transcode (with QSV):** 5–15% CPU.
- **Minimum host RAM:** 4 GB. 8 GB comfortable for transcoding.
## Deployment Checklist
1. Verify GPU on host: `ls -l /dev/dri/`, `lspci | grep -i vga`, `getent group render`.
2. Install media drivers: `sudo apt install intel-media-va-driver-non-free vainfo intel-gpu-tools`.
3. Create directories: `/home/dank/Containers/jellyfin/config`, `/home/dank/Containers/jellyfin/cache`.
4. Write compose file, verify device and GID.
5. Deploy: `podman compose up -d`.
6. Test GPU passthrough: `podman exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo --display drm --device /dev/dri/renderD128`.
7. Configure Caddy reverse proxy. Set Known Proxies in Jellyfin.
8. Add media libraries in Dashboard.
9. Install plugins: Open Subtitles, fanart.tv, Intro Skipper.
10. Test playback on Android/Chrome.
## Gotchas
- **GPU passthrough silently failing:** `/dev/dri` must be passed and the correct render node used. Verify with `vainfo` inside the container.
- **Wrong render group GID:** Common GID is 992, but varies per host. Run `getent group render` to find it.
- **Subtitle burn-in triggers full transcode:** PGS (image-based) subtitles force full video transcoding. SRT/ASS (text-based) play fine.
- **Database on NFS:** SQLite on network storage causes slow startup and corruption risk. Keep `/config` on local storage.
- **Client bitrate limit too low:** Many clients default to 20 Mbps. Set Max Streaming Bitrate to Original or Maximum.
- **Naming convention mismatches:** Non-standard folder/file names cause missing metadata. Use `Movie Title (Year)` for movies and `Show Name/Season 01/Show Name S01E01.mkv` for TV.
## See Also
- [[Dankserver Stack]]
- [[ARR-Stack]] — automated download into Jellyfin libraries
- [[Copyparty]] — alternative file viewer for existing collections
- Plex — existing media server on this host