at 24.11-pre 5.5 kB view raw
1{ config, pkgs, lib, ... }: 2 3let 4 inherit (lib) mkIf getExe maintainers mkEnableOption mkOption mkPackageOption; 5 inherit (lib.types) str path bool; 6 cfg = config.services.jellyfin; 7in 8{ 9 options = { 10 services.jellyfin = { 11 enable = mkEnableOption "Jellyfin Media Server"; 12 13 package = mkPackageOption pkgs "jellyfin" { }; 14 15 user = mkOption { 16 type = str; 17 default = "jellyfin"; 18 description = "User account under which Jellyfin runs."; 19 }; 20 21 group = mkOption { 22 type = str; 23 default = "jellyfin"; 24 description = "Group under which jellyfin runs."; 25 }; 26 27 dataDir = mkOption { 28 type = path; 29 default = "/var/lib/jellyfin"; 30 description = '' 31 Base data directory, 32 passed with `--datadir` see [#data-directory](https://jellyfin.org/docs/general/administration/configuration/#data-directory) 33 ''; 34 }; 35 36 configDir = mkOption { 37 type = path; 38 default = "${cfg.dataDir}/config"; 39 defaultText = "\${cfg.dataDir}/config"; 40 description = '' 41 Directory containing the server configuration files, 42 passed with `--configdir` see [configuration-directory](https://jellyfin.org/docs/general/administration/configuration/#configuration-directory) 43 ''; 44 }; 45 46 cacheDir = mkOption { 47 type = path; 48 default = "/var/cache/jellyfin"; 49 description = '' 50 Directory containing the jellyfin server cache, 51 passed with `--cachedir` see [#cache-directory](https://jellyfin.org/docs/general/administration/configuration/#cache-directory) 52 ''; 53 }; 54 55 logDir = mkOption { 56 type = path; 57 default = "${cfg.dataDir}/log"; 58 defaultText = "\${cfg.dataDir}/log"; 59 description = '' 60 Directory where the Jellyfin logs will be stored, 61 passed with `--logdir` see [#log-directory](https://jellyfin.org/docs/general/administration/configuration/#log-directory) 62 ''; 63 }; 64 65 openFirewall = mkOption { 66 type = bool; 67 default = false; 68 description = '' 69 Open the default ports in the firewall for the media server. The 70 HTTP/HTTPS ports can be changed in the Web UI, so this option should 71 only be used if they are unchanged, see [Port Bindings](https://jellyfin.org/docs/general/networking/#port-bindings). 72 ''; 73 }; 74 }; 75 }; 76 77 config = mkIf cfg.enable { 78 systemd = { 79 tmpfiles.settings.jellyfinDirs = { 80 "${cfg.dataDir}"."d" = { 81 mode = "700"; 82 inherit (cfg) user group; 83 }; 84 "${cfg.configDir}"."d" = { 85 mode = "700"; 86 inherit (cfg) user group; 87 }; 88 "${cfg.logDir}"."d" = { 89 mode = "700"; 90 inherit (cfg) user group; 91 }; 92 "${cfg.cacheDir}"."d" = { 93 mode = "700"; 94 inherit (cfg) user group; 95 }; 96 }; 97 services.jellyfin = { 98 description = "Jellyfin Media Server"; 99 after = [ "network-online.target" ]; 100 wants = [ "network-online.target" ]; 101 wantedBy = [ "multi-user.target" ]; 102 103 # This is mostly follows: https://github.com/jellyfin/jellyfin/blob/master/fedora/jellyfin.service 104 # Upstream also disable some hardenings when running in LXC, we do the same with the isContainer option 105 serviceConfig = { 106 Type = "simple"; 107 User = cfg.user; 108 Group = cfg.group; 109 UMask = "0077"; 110 WorkingDirectory = cfg.dataDir; 111 ExecStart = "${getExe cfg.package} --datadir '${cfg.dataDir}' --configdir '${cfg.configDir}' --cachedir '${cfg.cacheDir}' --logdir '${cfg.logDir}'"; 112 Restart = "on-failure"; 113 TimeoutSec = 15; 114 SuccessExitStatus = ["0" "143"]; 115 116 # Security options: 117 NoNewPrivileges = true; 118 SystemCallArchitectures = "native"; 119 # AF_NETLINK needed because Jellyfin monitors the network connection 120 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; 121 RestrictNamespaces = !config.boot.isContainer; 122 RestrictRealtime = true; 123 RestrictSUIDSGID = true; 124 ProtectControlGroups = !config.boot.isContainer; 125 ProtectHostname = true; 126 ProtectKernelLogs = !config.boot.isContainer; 127 ProtectKernelModules = !config.boot.isContainer; 128 ProtectKernelTunables = !config.boot.isContainer; 129 LockPersonality = true; 130 PrivateTmp = !config.boot.isContainer; 131 # needed for hardware acceleration 132 PrivateDevices = false; 133 PrivateUsers = true; 134 RemoveIPC = true; 135 136 SystemCallFilter = [ 137 "~@clock" "~@aio" "~@chown" "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@module" "~@mount" "~@obsolete" "~@privileged" "~@raw-io" "~@reboot" "~@setuid" "~@swap" 138 ]; 139 SystemCallErrorNumber = "EPERM"; 140 }; 141 }; 142 }; 143 144 users.users = mkIf (cfg.user == "jellyfin") { 145 jellyfin = { 146 inherit (cfg) group; 147 isSystemUser = true; 148 }; 149 }; 150 151 users.groups = mkIf (cfg.group == "jellyfin") { 152 jellyfin = {}; 153 }; 154 155 networking.firewall = mkIf cfg.openFirewall { 156 # from https://jellyfin.org/docs/general/networking/index.html 157 allowedTCPPorts = [ 8096 8920 ]; 158 allowedUDPPorts = [ 1900 7359 ]; 159 }; 160 161 }; 162 163 meta.maintainers = with maintainers; [ minijackson nu-nu-ko ]; 164}