030 - Jellyfin Role with LDAP / OIDC SSO¶
User Story¶
As a platform administrator of Infinito.Nexus, I want Jellyfin 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 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 |
|
|
Config |
|
|
Ports |
|
|
Database |
None external — internal SQLite under |
|
Native auth |
Local Jellyfin users only — no native OIDC/LDAP. |
|
LDAP plugin |
|
|
OIDC/SSO plugin |
|
|
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. |
In-repo analogues¶
Because Jellyfin has no native SSO and its apps use API tokens, the LDAP plugin against the central
svc-db-openldapis 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 |
Standard single-host convention. |
2 |
Both auth plugins: |
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 ( |
Operator decision: self-contained v1; libraries added in-app. |
4 |
CPU transcoding only (no GPU passthrough). |
Operator decision: smallest, host-portable surface. |
5 |
Image |
Standard upstream-pin policy. |
6 |
No external DB; internal SQLite under |
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 throughsys-svc-proxyto Jellyfin (8096);GET /returns the Jellyfin web UI;JELLYFIN_PublishedServerUrlis set to the canonical URL.[ ] The role emits a
Content-Security-Policyheader on the canonical surface.
Role layout & image¶
[ ]
roles/web-app-jellyfin/exists per the Target Schema; image pinned to a concrete10.xpatch (no:latest).[ ] All
meta/*.ymlexist 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-ldapauthis installed and configured againstsvc-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.ymldefines 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;
templates/playwright.env.j2exposes every read env var;make compose-playwright role=web-app-jellyfinexits 0 with no stub tests and a clean logged-out final state.
Health & quality¶
[ ] Stack steady across all three variants;
make testgreen tree-wide.
Documentation¶
[ ]
README.mddocuments 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¶
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, then Role Loop and the Playwright contract.
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 testis green, instruct the operator to rungit-sign-pushoutside 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