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