{ lib, pkgs, config, ... }: let jellyfinPort = 8096; jellyfin_data_directory = "/var/lib/jellyfin"; jellyfin_cache_directory = "/var/cache/jellyfin"; in { options.host.jellyfin = { enable = lib.mkEnableOption "should jellyfin be enabled on this computer"; subdomain = lib.mkOption { type = lib.types.str; description = "subdomain of base domain that jellyfin will be hosted at"; default = "jellyfin"; }; extraSubdomains = lib.mkOption { type = lib.types.listOf lib.types.str; description = "ex subdomain of base domain that jellyfin will be hosted at"; default = []; }; media_directory = lib.mkOption { type = lib.types.str; description = "directory jellyfin media will be hosted at"; default = "/srv/jellyfin/media"; }; }; config = lib.mkIf config.host.jellyfin.enable ( lib.mkMerge [ { services.jellyfin.enable = true; host.reverse_proxy.subdomains.jellyfin = { target = "http://localhost:${toString jellyfinPort}"; subdomain = config.host.jellyfin.subdomain; extraSubdomains = config.host.jellyfin.extraSubdomains; forwardHeaders.enable = true; extraConfig = '' client_max_body_size 20M; add_header X-Content-Type-Options "nosniff"; proxy_buffering off; ''; }; environment.systemPackages = [ pkgs.jellyfin pkgs.jellyfin-web pkgs.jellyfin-ffmpeg ]; } (lib.mkIf config.services.fail2ban.enable { environment.etc = { "fail2ban/filter.d/jellyfin.local".text = lib.mkIf config.services.jellyfin.enable ( pkgs.lib.mkDefault (pkgs.lib.mkAfter '' [Definition] failregex = "^.*Authentication request for .* has been denied \\\(IP: \"\"\\\)\\\." '') ); }; services.fail2ban = { jails = { jellyfin-iptables.settings = lib.mkIf config.services.jellyfin.enable { enabled = true; filter = "jellyfin"; action = ''iptables-multiport[name=HTTP, port="http,https"]''; logpath = "${config.services.jellyfin.dataDir}/log/*.log"; backend = "auto"; findtime = 600; bantime = 600; maxretry = 5; }; }; }; }) (lib.mkIf config.host.impermanence.enable { fileSystems."/persist/system/jellyfin".neededForBoot = true; host.storage.pool.extraDatasets = { # sops age key needs to be available to pre persist for user generation "persist/system/jellyfin" = { type = "zfs_fs"; mountpoint = "/persist/system/jellyfin"; options = { atime = "off"; relatime = "off"; canmount = "on"; }; }; }; assertions = [ { assertion = config.services.jellyfin.dataDir == jellyfin_data_directory; message = "jellyfin data directory does not match persistence"; } { assertion = config.services.jellyfin.cacheDir == jellyfin_cache_directory; message = "jellyfin cache directory does not match persistence"; } ]; environment.persistence = { "/persist/system/root" = { enable = true; hideMounts = true; directories = [ { directory = jellyfin_data_directory; user = "jellyfin"; group = "jellyfin"; } { directory = jellyfin_cache_directory; user = "jellyfin"; group = "jellyfin"; } ]; }; "/persist/system/jellyfin" = { enable = true; hideMounts = true; directories = [ { directory = config.host.jellyfin.media_directory; user = "jellyfin"; group = "jellyfin_media"; mode = "1770"; } ]; }; }; }) ] ); }