diff --git a/modules/vendored/jellyfin.nix b/modules/vendored/jellyfin.nix new file mode 100644 index 0000000..39794ca --- /dev/null +++ b/modules/vendored/jellyfin.nix @@ -0,0 +1,143 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.jellyfin; +in +{ + disabledModules = [ "services/misc/jellyfin.nix" ]; + + options = { + services.jellyfin = { + enable = mkEnableOption (lib.mdDoc "Jellyfin Media Server"); + + dataDir = mkOption { + type = types.str; + default = "/var/lib/jellyfin"; + }; + + cacheDir = mkOption { + type = types.str; + default = "/var/cache/jellyfin"; + }; + + logDir = mkOption { + type = types.str; + default = "/var/lib/jellyfin"; + }; + + user = mkOption { + type = types.str; + default = "jellyfin"; + description = lib.mdDoc "User account under which Jellyfin runs."; + }; + + package = mkPackageOption pkgs "jellyfin" { }; + + group = mkOption { + type = types.str; + default = "jellyfin"; + description = lib.mdDoc "Group under which jellyfin runs."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Open the default ports in the firewall for the media server. The + HTTP/HTTPS ports can be changed in the Web UI, so this option should + only be used if they are unchanged. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.settings."10-jellyfin" = { + ${cfg.dataDir}.d = { inherit (cfg) user group; mode = "0700"; }; + ${cfg.cacheDir}.d = { inherit (cfg) user group; mode = "0700"; }; + ${cfg.logDir}.d = { inherit (cfg) user group; mode = "0700"; }; + }; + + systemd.services.jellyfin = { + description = "Jellyfin Media Server"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + # This is mostly follows: https://github.com/jellyfin/jellyfin/blob/master/fedora/jellyfin.service + # Upstream also disable some hardenings when running in LXC, we do the same with the isContainer option + serviceConfig = rec { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + UMask = "0077"; + WorkingDirectory = cfg.dataDir; + ExecStart = "${cfg.package}/bin/jellyfin --datadir '${cfg.dataDir}' --cachedir '${cfg.cacheDir}' --logdir '${cfg.logDir}'"; + Restart = "on-failure"; + TimeoutSec = 15; + SuccessExitStatus = ["0" "143"]; + + # Security options: + NoNewPrivileges = true; + SystemCallArchitectures = "native"; + # AF_NETLINK needed because Jellyfin monitors the network connection + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; + RestrictNamespaces = !config.boot.isContainer; + RestrictRealtime = true; + RestrictSUIDSGID = true; + ProtectControlGroups = !config.boot.isContainer; + ProtectHostname = true; + ProtectKernelLogs = !config.boot.isContainer; + ProtectKernelModules = !config.boot.isContainer; + ProtectKernelTunables = !config.boot.isContainer; + LockPersonality = true; + PrivateTmp = !config.boot.isContainer; + # needed for hardware acceleration + PrivateDevices = false; + PrivateUsers = true; + RemoveIPC = true; + + SystemCallFilter = [ + "~@clock" + "~@aio" + "~@chown" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@memlock" + "~@module" + "~@mount" + "~@obsolete" + "~@privileged" + "~@raw-io" + "~@reboot" + "~@setuid" + "~@swap" + ]; + SystemCallErrorNumber = "EPERM"; + }; + }; + + users.users = mkIf (cfg.user == "jellyfin") { + jellyfin = { + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = mkIf (cfg.group == "jellyfin") { + jellyfin = {}; + }; + + networking.firewall = mkIf cfg.openFirewall { + # from https://jellyfin.org/docs/general/networking/index.html + allowedTCPPorts = [ 8096 8920 ]; + allowedUDPPorts = [ 1900 7359 ]; + }; + + }; + + meta.maintainers = with lib.maintainers; [ minijackson ]; +}