{ config, lib, pkgs, homeyConfig, ... }: # Jellyfin — media server. # # Auth model: Jellyfin-native (no Authelia SSO — Jellyfin intercepts the # forward_auth flow in ways that break SSO). LDAP plugin can be installed # manually via the UI; the XML config is pre-seeded on first start so the # plugin settings are pre-populated when the user opens the config page. # # Manual step after first deploy: # 1. Dashboard → Plugins → Catalog → search "LDAP Authentication" → Install # 2. Restart Jellyfin (Dashboard → Administration → Restart) # 3. Dashboard → Plugins → LDAP Authentication → verify fields → Save # # Volume layout: # /jellyfin/config/ → /config # /media/movies/ → /data/movies # /media/tvshows/ → /data/tvshows # /media/general/ → /data/general # /media/complete/ → /data/complete let cfg = config.homey.jellyfin; dataDir = config.homey.storage.mountPoint; domain = homeyConfig.domain; ldapBaseDn = lib.concatStringsSep "," (map (p: "dc=${p}") (lib.splitString "." domain)); ldapConfigDir = "${dataDir}/jellyfin/config/plugins/configurations"; ldapConfigXml = "${ldapConfigDir}/LDAP-Auth.xml"; in { options.homey.jellyfin = { enable = lib.mkEnableOption "Jellyfin media server"; image = lib.mkOption { type = lib.types.str; default = "docker.io/jellyfin/jellyfin:latest"; }; port = lib.mkOption { type = lib.types.port; default = 8096; }; }; config = lib.mkIf cfg.enable { # ----------------------------------------------------------------------- # Secrets # ----------------------------------------------------------------------- sops.secrets."openldap/ro_password" = { owner = "root"; }; # ----------------------------------------------------------------------- # Container # ----------------------------------------------------------------------- virtualisation.oci-containers.containers.jellyfin = { image = cfg.image; ports = [ "127.0.0.1:${toString cfg.port}:8096" ]; environment = { JELLYFIN_PublishedServerUrl = "https://media.${domain}"; PUID = "1000"; PGID = "1000"; }; volumes = [ "${dataDir}/jellyfin/config:/config" "${dataDir}/media/movies:/data/movies:ro" "${dataDir}/media/tvshows:/data/tvshows:ro" "${dataDir}/media/general:/data/general:ro" "${dataDir}/media/complete:/data/complete:ro" ]; extraOptions = [ "--network=homey" ]; }; # ----------------------------------------------------------------------- # ExecStartPre: pre-seed LDAP plugin config on first start only # ----------------------------------------------------------------------- systemd.services."podman-jellyfin" = { serviceConfig.ExecStartPre = [ (pkgs.writeShellScript "jellyfin-seed-ldap-config" '' set -euo pipefail # Only write on first start; preserve any UI edits on subsequent starts. if [ -f "${ldapConfigXml}" ]; then exit 0 fi mkdir -p "${ldapConfigDir}" RO_PASSWORD=$(cat ${config.sops.secrets."openldap/ro_password".path}) cat > "${ldapConfigXml}" < openldap 389 false false false cn=readonly,${ldapBaseDn} $RO_PASSWORD ou=users,${ldapBaseDn} uid,cn,mail,displayName uid userPassword true cn=admins,ou=groups,${ldapBaseDn} false true false EOF chmod 600 "${ldapConfigXml}" '') ]; after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ]; }; # ----------------------------------------------------------------------- # Authelia access control — bypass; Jellyfin has its own login UI and # does not work correctly behind SSO forward_auth. # ----------------------------------------------------------------------- homey.authelia.accessControlRules = [{ priority = 60; domain = [ "media.${domain}" ]; policy = "bypass"; }]; # ----------------------------------------------------------------------- # Caddy virtual host — no forward_auth; Jellyfin manages its own auth # ----------------------------------------------------------------------- homey.caddy.virtualHosts = [{ subdomain = "media"; port = cfg.port; auth = false; }]; # ----------------------------------------------------------------------- # Storage directories # ----------------------------------------------------------------------- homey.storage.extraDirs = [ { path = "jellyfin"; } { path = "jellyfin/config"; } ]; # ----------------------------------------------------------------------- # Backup # ----------------------------------------------------------------------- homey.backup.extraPaths = [ "${dataDir}/jellyfin" ]; # ----------------------------------------------------------------------- # Uptime Kuma monitor # ----------------------------------------------------------------------- homey.monitoring.monitors = [{ name = "Jellyfin"; url = "https://media.${domain}"; interval = 60; }]; }; }