{ config , pkgs , lib , ... }: let # Jellyfin SSO plugin built from upstream release zip. # Not in nixpkgs, so we package it here for use in a bind mount. ssoPlugin = pkgs.stdenvNoCC.mkDerivation { pname = "jellyfin-plugin-sso"; version = "4.0.0.4"; src = pkgs.fetchzip { url = "https://github.com/9p4/jellyfin-plugin-sso/releases/download/v4.0.0.4/sso-authentication_4.0.0.4.zip"; hash = "sha256-MJTyE6CeVLk7mlugauJ/F6bpi1kYwNtzNmQeH3+CFeQ="; stripRoot = false; }; dontBuild = true; installPhase = '' mkdir -p $out cp -r ./* $out/ ''; }; # Plugin directory name expected by Jellyfin (version-specific). pluginDirName = "SSO-Auth_4.0.0.4"; in { options.services.jellyfin-server = { enable = lib.mkEnableOption "Jellyfin media server"; port = lib.mkOption { type = lib.types.port; default = 8096; description = "Port for Jellyfin web interface"; }; mediaLocation = lib.mkOption { type = lib.types.path; default = "/data/jellyfin"; description = "Directory for storing media files"; }; }; config = lib.mkIf config.services.jellyfin-server.enable { services.jellyfin = { enable = true; openFirewall = true; group = "jellyfin"; # Hardware acceleration for NVIDIA NVENC transcoding (GTX 960 on homeserver-1). # Requires the legacy_580 driver branch for Maxwell GPUs. hardwareAcceleration = { enable = true; type = "nvenc"; device = "/dev/nvidia0"; }; # Force encoding config so NVENC settings are applied on every restart. # Without this, changes made in the web UI persist, but NixOS settings are ignored. forceEncodingConfig = true; transcoding = { # Enable hardware encoding for H.264 and H.265 (HEVC). enableHardwareEncoding = true; # Optional: enable tone mapping for HDR → SDR conversion. enableToneMapping = false; }; }; # Media dir: petere writes via Samba (forced to group jellyfin), Jellyfin # reads as group jellyfin. Not world-writable. # Also ensure the plugins directory exists for the SSO plugin bind mount. systemd.tmpfiles.rules = [ "d ${config.services.jellyfin-server.mediaLocation} 0775 root jellyfin -" "d /var/lib/jellyfin/plugins 0755 jellyfin jellyfin -" ]; # Samba share is authenticated and restricted to petere (the only writer). # No guest access; the Samba password is managed via SOPS (see # samba-petere-password secret and the samba-set-password service below). services.samba = { enable = true; openFirewall = true; settings = { global = { "server role" = "standalone server"; security = "user"; }; jellyfin = { path = "${config.services.jellyfin-server.mediaLocation}"; browseable = "yes"; "read only" = "no"; "guest ok" = "no"; "valid users" = "petere"; "force group" = "jellyfin"; "force create mode" = "0664"; "force directory mode" = "0775"; }; }; }; # Install the SSO plugin by copying from the Nix store. # Jellyfin needs to write plugin metadata (meta.json), so we can't use # a read-only bind mount from /nix/store. This oneshot service is # idempotent: it only copies if the plugin dir doesn't exist or differs. systemd.services.jellyfin-install-plugin-sso = { description = "Install Jellyfin SSO Auth plugin"; wantedBy = [ "multi-user.target" ]; before = [ "jellyfin.service" ]; serviceConfig.Type = "oneshot"; script = '' PLUGIN_DIR="/var/lib/jellyfin/plugins/${pluginDirName}" SOURCE="${ssoPlugin}" # Only copy if the plugin doesn't exist or source changed if [ ! -d "$PLUGIN_DIR" ] || ! diff -rq "$SOURCE" "$PLUGIN_DIR" >/dev/null 2>&1; then rm -rf "$PLUGIN_DIR" cp -r "$SOURCE" "$PLUGIN_DIR" chown -R jellyfin:jellyfin "$PLUGIN_DIR" chmod -R u+w "$PLUGIN_DIR" fi ''; }; systemd.services.jellyfin = { after = [ "jellyfin-install-plugin-sso.service" ]; wants = [ "jellyfin-install-plugin-sso.service" ]; }; }; }