Files
computer-playbook/docs/requirements/030-web-app-jellyfin.md
kevinveenbirkenbach 9ee4cd0fb9 Merge branch 'main' of github.com:infinito-nexus/core into feature/swarm-nfs
# Conflicts:
#	docs/agents/action/iteration/compose.md
#	roles/svc-db-openldap/vars/main.yml
#	roles/svc-runner/defaults/main.yml
#	roles/svc-runner/tasks/02_cleanup_instance.yml
#	roles/web-app-discourse/meta/server.yml
#	roles/web-app-mailu/tasks/01_core.yml
#	roles/web-app-odoo/tasks/main.yml
2026-07-04 18:16:12 +02:00

121 lines
7.2 KiB
Markdown

# 030 - Jellyfin Role with LDAP / OIDC SSO
## User Story
As a platform administrator of Infinito.Nexus, I want [Jellyfin](https://jellyfin.org) integrated as a `web-app-jellyfin` role with Keycloak-backed Single Sign-On so that users can stream the media server behind the platform's central identity, validated end-to-end with Playwright personas.
## Background
[Jellyfin](https://jellyfin.org) is a free, open-source media server. The official `jellyfin/jellyfin` container is Debian-based, stores everything in an internal SQLite DB (**no external database**), and persists three volumes: `/config`, `/cache`, `/media`.
### Upstream facts established by research
| Topic | Finding | Source |
|---|---|---|
| Image | `jellyfin/jellyfin` (Docker Hub + `ghcr.io`); tags `latest`, `X.Y`, `X.Y.Z`. Pin a concrete `10.x` patch. | [Container install](https://jellyfin.org/docs/general/installation/container/) |
| Config | `JELLYFIN_*` env (double-underscore nesting), `JELLYFIN_PublishedServerUrl`, `TZ`; runs as `PUID/PGID`. | [Configuration](https://jellyfin.org/docs/general/administration/configuration/) |
| Ports | `8096` (HTTP web), `8920` (optional HTTPS), `7359/udp` + `1900/udp` (client/service discovery). | [Container install](https://jellyfin.org/docs/general/installation/container/) |
| Database | None external — internal SQLite under `/config`. | [Configuration](https://jellyfin.org/docs/general/administration/configuration/) |
| Native auth | Local Jellyfin users only — **no native OIDC/LDAP**. | [Discussion #16470](https://github.com/orgs/jellyfin/discussions/16470) |
| LDAP plugin | [`jellyfin-plugin-ldapauth`](https://github.com/jellyfin/jellyfin-plugin-ldapauth) — official-catalog plugin; **works on ALL clients** (web + native apps). | [LDAP self-hosting](https://joeeey.com/blog/selfhosting-sso-ldap-part-3/) |
| OIDC/SSO plugin | [`9p4/jellyfin-plugin-sso`](https://github.com/9p4/jellyfin-plugin-sso) — OIDC/SAML one-click, **web UI only** (native apps cannot use it); third-party. | [plugin-sso](https://github.com/9p4/jellyfin-plugin-sso) |
| oauth2-proxy gate | Breaks native apps (they authenticate with API tokens, cannot run the browser OIDC flow). Not suitable as the sole gate for a media server. | [Discussion #16470](https://github.com/orgs/jellyfin/discussions/16470) |
### In-repo analogues
- Because Jellyfin has no native SSO and its apps use API tokens, the **LDAP plugin against the central `svc-db-openldap`** is the "works everywhere" path — closest in spirit to roles that consume central LDAP. The optional OIDC web-SSO plugin maps to the platform Keycloak.
- No external DB consumer (like Checkmk).
## Confirmed Decisions
These decisions were confirmed by the operator before implementation starts and are NOT subject to re-litigation during implementation.
| # | Decision | Rationale |
|---|---|---|
| 1 | Canonical hostname `media.{{ DOMAIN_PRIMARY }}`. | Standard single-host convention. |
| 2 | **Both** auth plugins: `jellyfin-plugin-ldapauth` (central OpenLDAP, all clients) **and** `9p4/jellyfin-plugin-sso` (Keycloak OIDC, web one-click). | Operator decision: LDAP gives every client (incl. native apps) a platform identity; the OIDC plugin adds one-click web sign-in. |
| 3 | Media library on a **named Docker volume** (`/media`), plus `/config` + `/cache`. | Operator decision: self-contained v1; libraries added in-app. |
| 4 | **CPU transcoding only** (no GPU passthrough). | Operator decision: smallest, host-portable surface. |
| 5 | Image `jellyfin/jellyfin` pinned to a concrete stable `10.x` patch (no `:latest`). | Standard upstream-pin policy. |
| 6 | No external DB; internal SQLite under `/config`. | Jellyfin bundles SQLite. |
## Target Schema
### Role layout
```
roles/web-app-jellyfin/
├── README.md
├── files/playwright/*.js
├── meta/{main,info,server,services,schema,users,rbac,variants,volumes}.yml
├── tasks/{main,01_core}.yml
├── templates/{compose.yml.j2,env.j2,playwright.env.j2}
└── vars/main.yml
```
## Acceptance Criteria
### Routing & TLS
- [ ] `media.{{ DOMAIN_PRIMARY }}` resolves through `sys-svc-proxy` to Jellyfin (`8096`); `GET /` returns the Jellyfin web UI; `JELLYFIN_PublishedServerUrl` is set to the canonical URL.
- [ ] The role emits a `Content-Security-Policy` header on the canonical surface.
### Role layout & image
- [ ] `roles/web-app-jellyfin/` exists per the [Target Schema](#role-layout); image pinned to a concrete `10.x` patch (no `:latest`).
- [ ] All `meta/*.yml` exist and pass the role-meta lint.
### Bootstrap
- [ ] First run reaches a steady running state with `/config` + `/cache` (+ media per Q2) persisted; the initial-setup wizard is bypassed or a first admin is seeded where supported.
### Authentication (Decision #2)
- [ ] The chosen auth integration is provisioned non-interactively: for LDAP, `jellyfin-plugin-ldapauth` is installed and configured against `svc-db-openldap` (server/bind/search/attributes); for the OIDC plugin, it is installed and pointed at Keycloak.
- [ ] An LDAP/SSO user can sign in and reach the Jellyfin home; a dedicated Playwright scenario (gated on the chosen service) covers it.
### Variants
- [ ] `meta/variants.yml` defines three variants (V1 all auth on, V2 all-false, V3 ldap-only); all deploy cleanly on a fresh box.
### Playwright
- [ ] Guest + authenticated persona(s) per the [playwright contract](../contributing/artefact/files/role/playwright.specs.js.md); `templates/playwright.env.j2` exposes every read env var; `make compose-playwright role=web-app-jellyfin` exits 0 with no stub tests and a clean logged-out final state.
### Health & quality
- [ ] Stack steady across all three variants; `make test` green tree-wide.
### Documentation
- [ ] `README.md` documents image + bump policy, the auth approach + its client-coverage caveat (web-only vs all-clients), the media-storage decision, transcoding, and the no-external-DB design.
- [ ] This requirement is cross-linked from the implementing PR.
## Validation Apps
```bash
INFINITO_APPS="web-app-jellyfin" make deploy-fresh-purged-apps INFINITO_FULL_CYCLE=true
```
Smoke: visit `https://media.{{ DOMAIN_PRIMARY }}/` → Jellyfin loads → sign in via the chosen integration → reach the home/library surface.
## Prerequisites
Before implementation, the agent MUST read [AGENTS.md](../../AGENTS.md), then [Role Loop](../agents/action/iteration/compose.md) and the [Playwright contract](../contributing/artefact/files/role/playwright.specs.js.md).
## Implementation Strategy
Execute autonomously after the Open Questions are resolved. Verify the plugin install + config mechanism (plugin DLL placement under `/config/plugins`, the plugin configuration XML schema, and the catalog/version) against the pinned upstream plugin release before claiming done.
## Commit Policy
- No git commit until every Acceptance Criterion is checked off.
- When met and `make test` is green, instruct the operator to run `git-sign-push` outside the sandbox. The agent MUST NOT push.
## Context
- Upstream site: <https://jellyfin.org>
- Container docs: <https://jellyfin.org/docs/general/installation/container/>
- LDAP plugin: <https://github.com/jellyfin/jellyfin-plugin-ldapauth>
- OIDC/SSO plugin: <https://github.com/9p4/jellyfin-plugin-sso>