Problem: node and cross-cutting narratives lived at the repo root
(containers/, vms/, infrastructure/, host .md files), interleaved with the
machine-readable substrate.
Change:
- Move containers/ -> knowledge/wiki/containers/, vms/ -> knowledge/wiki/vms/,
infrastructure/ -> knowledge/wiki/infrastructure/, hosts/{hubris,strong}.md ->
knowledge/wiki/hosts/, infrastructure/references/ -> knowledge/sources/references/,
GLOSSARY.md -> knowledge/GLOSSARY.md.
- Add knowledge/{index.md,log.md,sources/index.md} scaffolding.
- Rewrite all relative links repo-wide via a path-resolving mapper (inbound +
outbound + between-moved-files), including .hermes/, runbooks, operations,
investigations, plans, README, AGENTS.
- Repoint inventory.yaml doc_page fields and regenerate hosts/*.yaml (which
embed doc_page); update oikos/gen-topology.py output path, candidate doc
paths, and footer links; update code-comment doc paths.
Substrate untouched in place: inventory.yaml, hosts/*.yaml (regenerated,
idempotent), oikos/ code, mcp/, secrets/, bin/.
Verification:
- Logical broken-link set identical to pre-move baseline (net 128 -> 127; the
topology regen fixed one, introduced none). Remaining are pre-existing refs
to destroyed/archived nodes, out of scope for this move.
- gen-topology.py --check exit 0 (in sync); cards carry knowledge/wiki/ doc paths.
- build_host_files.py idempotent; all inventory doc_page targets resolve.
- MCP contract verified: get_page/search_docs/get_changelog resolve moved pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
161 lines
8.0 KiB
Markdown
161 lines
8.0 KiB
Markdown
# 101 — `jellyfin`
|
|
|
|
Media server: serves the movies / TV / anime / music libraries from `/mnt/media_local` to LAN clients. Hardware transcoding via AMD Radeon 680M + RX 7600 VAAPI. Authentik SSO via OIDC.
|
|
|
|
## At a glance
|
|
- **Host:** **strong** (migrated from hubris 2026-07-05)
|
|
- **IP:** `192.168.8.246`
|
|
- **Privilege:** privileged (recreated on strong as priv)
|
|
- **Resources:** 4 cores / 8 GiB RAM / 1 GiB swap / 16 GiB rootfs
|
|
- **GPU:** `/dev/dri/renderD128` + `/dev/dri/card0` (AMD Radeon 680M iGPU + RX 7600 dGPU) passed via `dev0` / `dev1` in LXC config
|
|
- **Mounts:** `/mnt/media_local` ↔ `/mnt/library`
|
|
- **Public hostname:** [`media.hubris.network`](../infrastructure/dns.md) → [caddy](121-caddy.md) → `:8096`
|
|
- **Version:** Jellyfin 10.11.11 (apt package, Ubuntu 24.04 noble repo)
|
|
- **FFmpeg:** jellyfin-ffmpeg7 7.1.4
|
|
|
|
## Service / port map
|
|
|
|
| Service | Listen | Notes |
|
|
| -------- | ------ | ----- |
|
|
| jellyfin | `:8096` | HTTP (caddy terminates TLS) |
|
|
|
|
## Hardware acceleration (VAAPI)
|
|
|
|
GPU is passed through to the LXC via `dev0: /dev/dri/renderD128,gid=993` and
|
|
`dev1: /dev/dri/card0,gid=44` in
|
|
`/etc/pve/lxc/101.conf` (strong). The `jellyfin` user is in the `render` (GID 993) and
|
|
`video` groups inside the container.
|
|
|
|
| GPU | Model | Role |
|
|
|-----|-------|------|
|
|
| Radeon 680M | iGPU (AMD Ryzen 7 PRO 6850U) | Primary VAAPI encoder/decoder |
|
|
| RX 7600 | dGPU (add-in) | Secondary transcode, HEVC/AV1 encoding |
|
|
|
|
Encoding settings (`/etc/jellyfin/encoding.xml`):
|
|
- `HardwareAccelerationType`: `vaapi`
|
|
- `VaapiDevice`: `/dev/dri/renderD128`
|
|
- `EnableHardwareEncoding`: `true`
|
|
- `AllowHevcEncoding`: `true`
|
|
- `AllowAv1Encoding`: `true`
|
|
- `EnableTonemapping`: `true`
|
|
- `HardwareDecodingCodecs`: h264, hevc, vc1, vp9, av1
|
|
- `EnableThrottling`: `true`
|
|
- `EnableSegmentDeletion`: `true`
|
|
|
|
Trickplay (`/etc/jellyfin/system.xml`):
|
|
- `EnableHwAcceleration`: `true`
|
|
- `EnableHwEncoding`: `true`
|
|
|
|
## Authentik SSO (OIDC)
|
|
|
|
Jellyfin uses the [SSO-Auth plugin](https://github.com/9p4/jellyfin-plugin-sso)
|
|
v4.0.0.4 for Authentik OIDC login. No Caddy forward-auth gate — the SSO plugin
|
|
handles auth directly via OIDC redirect flow.
|
|
|
|
### Architecture
|
|
|
|
```
|
|
User → media.hubris.network → Caddy (TLS, no forward-auth) → Jellyfin :8096
|
|
↓
|
|
Login page with "Sign in with Authentik" button
|
|
↓ (click)
|
|
/sso/OID/start/Authentik
|
|
↓ (302 redirect)
|
|
auth.hubris.network OIDC
|
|
↓ (login)
|
|
/sso/OID/redirect/Authentik?code=...&state=...
|
|
↓
|
|
Jellyfin SSO plugin validates token → logged in
|
|
```
|
|
|
|
### Components
|
|
|
|
1. **SSO-Auth plugin** — installed at `/var/lib/jellyfin/plugins/SSO-Auth_4.0.0.4/`
|
|
- Config: `/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml`
|
|
- Provider name: `Authentik`
|
|
- OIDC endpoint: `https://auth.hubris.network/application/o/jellyfin/`
|
|
- `SchemeOverride`: `https` (required — without it, plugin generates
|
|
`http://` redirect URIs that Authentik rejects)
|
|
- `EnableAuthorization`: `false` (prevents plugin from overwriting admin
|
|
permissions on each SSO login — see
|
|
[jellyfin-sso-plugin](../../../devops/homelab-authentik-admin/references/jellyfin-sso-plugin.md))
|
|
- `OidScopes`: `["email"]` (openid+profile added by default by the plugin;
|
|
must be non-null or `OidChallenge()` throws `ArgumentNullException`)
|
|
|
|
2. **Authentik OIDC provider** — `Provider for Jellyfin` (PK 6)
|
|
- Client ID: `vt61t5Y2ZVtN6l3QjitkBvwUJjFKvSyl4TDBXcJx`
|
|
- Redirect URI: `https://media.hubris.network/sso/OID/redirect/Authentik`
|
|
- Application slug: `jellyfin`
|
|
|
|
3. **SSO button injection** — `/usr/share/jellyfin/web/sso-inject.js`
|
|
- Injected via `<script defer src="sso-inject.js?v=3">` in `index.html`
|
|
- Polls for `.readOnlyContent` div on the login page, adds "Sign in with
|
|
Authentik" button linking to `/sso/OID/start/Authentik`
|
|
- Cache-busted with `?v=N` parameter (bump on changes)
|
|
- **Lost on apt upgrade** — re-inject the script tag and copy the JS file
|
|
after `apt-get upgrade jellyfin*`
|
|
|
|
4. **Caddy config** — no forward-auth gate for media.hubris.network:
|
|
```caddy
|
|
media.hubris.network {
|
|
tls { dns ionos {env.IONOS_AUTH_API_TOKEN} }
|
|
reverse_proxy 192.168.8.206:8096
|
|
}
|
|
```
|
|
|
|
### Known issues / pitfalls
|
|
|
|
- **`OidScopes` must be non-null** — if the field is missing from the plugin
|
|
config XML, `OidChallenge()` throws `System.ArgumentNullException`. Always
|
|
include `OidScopes` in the provider config (even if empty array).
|
|
- **`SchemeOverride: "https"` is required** — without it, the plugin generates
|
|
`http://` redirect URIs (from the internal HTTP listener). Authentik rejects
|
|
them with "Redirect URI Error".
|
|
- **SSO button JS is not served by the plugin** — the `__plugin/SSO-Auth.js`
|
|
endpoint returns 404 on Jellyfin 10.11.x when the plugin is installed
|
|
manually (not via Jellyfin's plugin manager). The `sso-inject.js` workaround
|
|
in `index.html` is the fallback.
|
|
- **No Caddy forward-auth gate** — the SSO plugin's OIDC redirect flow is
|
|
incompatible with Caddy's `import authentik` forward-auth. If both are
|
|
enabled, the forward-auth intercepts the OIDC callback and breaks the flow.
|
|
Use one or the other, not both. SSO plugin (OIDC redirect) is preferred.
|
|
- **API key for setup** — a temp API key can be inserted directly into the
|
|
`ApiKeys` SQLite table for automated configuration:
|
|
```sql
|
|
INSERT INTO ApiKeys VALUES (1, '2026-07-04', '2026-07-04', 'setup', 'jf-setup-key-...');
|
|
```
|
|
|
|
## Permissions
|
|
Member of the [media GID 10000](../infrastructure/media-permissions.md) standard. Service user `jellyfin` is in the `media` group inside the container; `/mnt/media_local` on strong's ludo-lvm is owned `root:media` with mode `2775`.
|
|
|
|
## Related
|
|
- [Caddy reverse proxy](121-caddy.md)
|
|
- [Media permissions](../infrastructure/media-permissions.md)
|
|
- [arriman](122-arriman.md) — \*arr stack writes the libraries jellyfin reads
|
|
- [DNS split-horizon](../infrastructure/dns.md)
|
|
- [Authentik admin](../../../devops/homelab-authentik-admin/SKILL.md) — OIDC provider creation, SSO plugin config
|
|
|
|
## Changelog
|
|
|
|
### 2026-07-06 — wiki: IP, host, GPU, mount path updated for strong migration
|
|
Updated At-a-glance: IP 206→246, host hubris→strong, mount /mnt/library→/mnt/media_local, GPU Radeon 760M→680M+RX7600, privilege unpriv→priv. Permissions section updated. Changelog entry in 122-arriman.md updated similarly.
|
|
|
|
### 2026-07-04 — VAAPI hardware acceleration + Authentik SSO + resource bump
|
|
- Upgraded Jellyfin 10.11.8 → 10.11.11 (purge + reinstall to fix DB migration bug)
|
|
- Enabled VAAPI hardware acceleration (Radeon 760M): h264/hevc/vc1/vp9/av1 decode + encode
|
|
- Bumped resources: 2→4 cores, 4→8 GiB RAM, 512→1024 MiB swap
|
|
- Enabled trickplay HW acceleration + throttling + segment deletion
|
|
- Installed SSO-Auth plugin v4.0.0.4 with Authentik OIDC provider
|
|
- Configured `SchemeOverride: https`, `OidScopes: ["email"]`, `EnableAuthorization: false`
|
|
- Injected SSO button via `sso-inject.js` in web `index.html`
|
|
- Removed Caddy forward-auth gate (incompatible with SSO plugin OIDC flow)
|
|
- **Database was wiped** during cache relocation attempt — no LVM snapshot
|
|
existed. All watch states, user accounts, and library configs lost.
|
|
Libraries re-added via setup wizard.
|
|
|
|
### 2026-04-28 — wiki entry created
|
|
Initial documentation. No config changes.
|
|
|
|
### 2026-04-20 — joined the `media` GID 10000 standard
|
|
Idmap block applied; in-container `media` group at GID 10000 mapped to host GID 10000. See [media permissions](../infrastructure/media-permissions.md). Config backup: `/root/101.conf.bak.*`.
|