nixos/slskd: refactor and add config file options

authored by Melvyn and committed by melvyn 4da76cc6 43fa9212

+231 -103
+6
nixos/doc/manual/release-notes/rl-2405.section.md
··· 446 446 447 447 - The module `services.github-runner` has been removed. To configure a single GitHub Actions Runner refer to `services.github-runners.*`. Note that this will trigger a new runner registration. 448 448 449 + - The `services.slskd` has been refactored to include more configuation options in 450 + the freeform `services.slskd.settings` option, and some defaults (including listen ports) 451 + have been changed to match the upstream defaults. Additionally, disk logging is now 452 + disabled by default, and the log rotation timer has been removed. 453 + The nginx virtualhost option is now of the `vhost-options` type. 454 + 449 455 - The `btrbk` module now automatically selects and provides required compression 450 456 program depending on the configured `stream_compress` option. Since this 451 457 replaces the need for the `extraPackages` option, this option will be
+224 -102
nixos/modules/services/web-apps/slskd.nix
··· 2 2 3 3 let 4 4 settingsFormat = pkgs.formats.yaml {}; 5 + defaultUser = "slskd"; 5 6 in { 6 7 options.services.slskd = with lib; with types; { 7 8 enable = mkEnableOption "enable slskd"; 8 9 9 - rotateLogs = mkEnableOption "enable an unit and timer that will rotate logs in /var/slskd/logs"; 10 + package = mkPackageOptionMD pkgs "slskd" { }; 10 11 11 - package = mkPackageOption pkgs "slskd" { }; 12 + user = mkOption { 13 + type = types.str; 14 + default = defaultUser; 15 + description = "User account under which slskd runs."; 16 + }; 12 17 13 - nginx = mkOption { 14 - description = lib.mdDoc "options for nginx"; 15 - example = { 16 - enable = true; 17 - domain = "example.com"; 18 - contextPath = "/slskd"; 19 - }; 20 - type = submodule ({name, config, ...}: { 21 - options = { 22 - enable = mkEnableOption "enable nginx as a reverse proxy"; 18 + group = mkOption { 19 + type = types.str; 20 + default = defaultUser; 21 + description = "Group under which slskd runs."; 22 + }; 23 23 24 - domainName = mkOption { 25 - type = str; 26 - description = "Domain you want to use"; 27 - }; 28 - contextPath = mkOption { 29 - type = types.path; 30 - default = "/"; 31 - description = lib.mdDoc '' 32 - The context path, i.e., the last part of the slskd 33 - URL. Typically '/' or '/slskd'. Default '/' 34 - ''; 35 - }; 36 - }; 37 - }); 24 + domain = mkOption { 25 + type = types.nullOr types.str; 26 + description = '' 27 + If non-null, enables an nginx reverse proxy virtual host at this FQDN, 28 + at the path configurated with `services.slskd.web.url_base`. 29 + ''; 30 + example = "slskd.example.com"; 31 + }; 32 + 33 + nginx = mkOption { 34 + type = types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }); 35 + default = {}; 36 + example = lib.literalExpression '' 37 + { 38 + enableACME = true; 39 + forceHttps = true; 40 + } 41 + ''; 42 + description = '' 43 + This option customizes the nginx virtual host set up for slskd. 44 + ''; 38 45 }; 39 46 40 47 environmentFile = mkOption { 41 48 type = path; 42 49 description = '' 43 - Path to a file containing secrets. 44 - It must at least contain the variable `SLSKD_SLSK_PASSWORD` 50 + Path to the environment file sourced on startup. 51 + It must at least contain the variables `SLSKD_SLSK_USERNAME` and `SLSKD_SLSK_PASSWORD`. 52 + Web interface credentials should also be set here in `SLSKD_USERNAME` and `SLSKD_PASSWORD`. 53 + Other, optional credentials like SOCKS5 with `SLSKD_SLSK_PROXY_USERNAME` and `SLSKD_SLSK_PROXY_PASSWORD` 54 + should all reside here instead of in the world-readable nix store. 55 + Variables are documented at https://github.com/slskd/slskd/blob/master/docs/config.md 45 56 ''; 46 57 }; 47 58 48 59 openFirewall = mkOption { 49 60 type = bool; 50 - description = '' 51 - Whether to open the firewall for services.slskd.settings.listen_port"; 52 - ''; 61 + description = "Whether to open the firewall for the soulseek network listen port (not the web interface port)."; 53 62 default = false; 54 63 }; 55 64 56 65 settings = mkOption { 57 - description = lib.mdDoc '' 58 - Configuration for slskd, see 59 - [available options](https://github.com/slskd/slskd/blob/master/docs/config.md) 60 - `APP_DIR` is set to /var/lib/slskd, where default download & incomplete directories, 61 - log and databases will be created. 66 + description = '' 67 + Application configuration for slskd. See 68 + [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md). 62 69 ''; 63 70 default = {}; 64 71 type = submodule { 65 72 freeformType = settingsFormat.type; 66 73 options = { 74 + remote_file_management = mkEnableOption "modification of share contents through the web ui"; 75 + 76 + flags = { 77 + force_share_scan = mkOption { 78 + type = bool; 79 + description = "Force a rescan of shares on every startup."; 80 + }; 81 + no_version_check = mkOption { 82 + type = bool; 83 + default = true; 84 + visible = false; 85 + description = "Don't perform a version check on startup."; 86 + }; 87 + }; 88 + 89 + directories = { 90 + incomplete = mkOption { 91 + type = nullOr path; 92 + description = "Directory where incomplete downloading files are stored."; 93 + defaultText = "/var/lib/slskd/incomplete"; 94 + default = null; 95 + }; 96 + downloads = mkOption { 97 + type = nullOr path; 98 + description = "Directory where downloaded files are stored."; 99 + defaultText = "/var/lib/slskd/downloads"; 100 + default = null; 101 + }; 102 + }; 103 + 104 + shares = { 105 + directories = mkOption { 106 + type = listOf str; 107 + description = '' 108 + Paths to shared directories. See 109 + [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories) 110 + for advanced usage. 111 + ''; 112 + example = lib.literalExpression ''[ "/home/John/Music" "!/home/John/Music/Recordings" "[Music Drive]/mnt" ]''; 113 + }; 114 + filters = mkOption { 115 + type = listOf str; 116 + example = lib.literalExpression ''[ "\.ini$" "Thumbs.db$" "\.DS_Store$" ]''; 117 + description = "Regular expressions of files to exclude from sharing."; 118 + }; 119 + }; 120 + 121 + rooms = mkOption { 122 + type = listOf str; 123 + description = "Chat rooms to join on startup."; 124 + }; 67 125 68 126 soulseek = { 69 - username = mkOption { 127 + description = mkOption { 70 128 type = str; 71 - description = "Username on the Soulseek Network"; 129 + description = "The user description for the Soulseek network."; 130 + defaultText = "A slskd user. https://github.com/slskd/slskd"; 72 131 }; 73 132 listen_port = mkOption { 74 133 type = port; 75 - description = "Port to use for communication on the Soulseek Network"; 76 - default = 50000; 134 + description = "The port on which to listen for incoming connections."; 135 + default = 50300; 136 + }; 137 + }; 138 + 139 + global = { 140 + # TODO speed units 141 + upload = { 142 + slots = mkOption { 143 + type = ints.unsigned; 144 + description = "Limit of the number of concurrent upload slots."; 145 + }; 146 + speed_limit = mkOption { 147 + type = ints.unsigned; 148 + description = "Total upload speed limit."; 149 + }; 150 + }; 151 + download = { 152 + slots = mkOption { 153 + type = ints.unsigned; 154 + description = "Limit of the number of concurrent download slots."; 155 + }; 156 + speed_limit = mkOption { 157 + type = ints.unsigned; 158 + description = "Total upload download limit"; 159 + }; 77 160 }; 78 161 }; 79 162 163 + filters.search.request = mkOption { 164 + type = listOf str; 165 + example = lib.literalExpression ''[ "^.{1,2}$" ]''; 166 + description = "Incoming search requests which match this filter are ignored."; 167 + }; 168 + 80 169 web = { 81 170 port = mkOption { 82 171 type = port; 83 - default = 5001; 84 - description = "The HTTP listen port"; 172 + default = 5030; 173 + description = "The HTTP listen port."; 85 174 }; 86 175 url_base = mkOption { 87 176 type = path; 88 - default = config.services.slskd.nginx.contextPath; 89 - defaultText = "config.services.slskd.nginx.contextPath"; 90 - description = lib.mdDoc '' 91 - The context path, i.e., the last part of the slskd URL 92 - ''; 177 + default = "/"; 178 + description = "The base path in the url for web requests."; 179 + }; 180 + # Users should use a reverse proxy instead for https 181 + https.disabled = mkOption { 182 + type = bool; 183 + default = true; 184 + description = "Disable the built-in HTTPS server"; 93 185 }; 94 186 }; 95 187 96 - shares = { 97 - directories = mkOption { 98 - type = listOf str; 99 - description = lib.mdDoc '' 100 - Paths to your shared directories. See 101 - [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories) 102 - for advanced usage 103 - ''; 188 + retention = { 189 + transfers = { 190 + upload = { 191 + succeeded = mkOption { 192 + type = ints.unsigned; 193 + description = "Lifespan of succeeded upload tasks."; 194 + defaultText = "(indefinite)"; 195 + }; 196 + errored = mkOption { 197 + type = ints.unsigned; 198 + description = "Lifespan of errored upload tasks."; 199 + defaultText = "(indefinite)"; 200 + }; 201 + cancelled = mkOption { 202 + type = ints.unsigned; 203 + description = "Lifespan of cancelled upload tasks."; 204 + defaultText = "(indefinite)"; 205 + }; 206 + }; 207 + download = { 208 + succeeded = mkOption { 209 + type = ints.unsigned; 210 + description = "Lifespan of succeeded download tasks."; 211 + defaultText = "(indefinite)"; 212 + }; 213 + errored = mkOption { 214 + type = ints.unsigned; 215 + description = "Lifespan of errored download tasks."; 216 + defaultText = "(indefinite)"; 217 + }; 218 + cancelled = mkOption { 219 + type = ints.unsigned; 220 + description = "Lifespan of cancelled download tasks."; 221 + defaultText = "(indefinite)"; 222 + }; 223 + }; 224 + }; 225 + files = { 226 + complete = mkOption { 227 + type = ints.unsigned; 228 + description = "Lifespan of completely downloaded files in minutes."; 229 + example = 20160; 230 + defaultText = "(indefinite)"; 231 + }; 232 + incomplete = mkOption { 233 + type = ints.unsigned; 234 + description = "Lifespan of incomplete downloading files in minutes."; 235 + defaultText = "(indefinite)"; 236 + }; 104 237 }; 105 238 }; 106 239 107 - directories = { 108 - incomplete = mkOption { 109 - type = nullOr path; 110 - description = "Directory where downloading files are stored"; 111 - defaultText = "<APP_DIR>/incomplete"; 112 - default = null; 113 - }; 114 - downloads = mkOption { 115 - type = nullOr path; 116 - description = "Directory where downloaded files are stored"; 117 - defaultText = "<APP_DIR>/downloads"; 118 - default = null; 240 + logger = { 241 + # Disable by default, journald already retains as needed 242 + disk = mkOption { 243 + type = bool; 244 + description = "Whether to log to the application directory."; 245 + default = false; 246 + visible = false; 119 247 }; 120 248 }; 121 249 }; ··· 126 254 config = let 127 255 cfg = config.services.slskd; 128 256 129 - confWithoutNullValues = (lib.filterAttrs (key: value: value != null) cfg.settings); 257 + confWithoutNullValues = (lib.filterAttrsRecursive (key: value: (builtins.tryEval value).success && value != null) cfg.settings); 130 258 131 259 configurationYaml = settingsFormat.generate "slskd.yml" confWithoutNullValues; 132 260 133 261 in lib.mkIf cfg.enable { 134 262 135 - users = { 136 - users.slskd = { 263 + # Force off, configuration file is in nix store and is immutable 264 + services.slskd.settings.remote_configuration = lib.mkForce false; 265 + 266 + users.users = lib.optionalAttrs (cfg.user == defaultUser) { 267 + "${defaultUser}" = { 268 + group = cfg.group; 137 269 isSystemUser = true; 138 - group = "slskd"; 139 270 }; 140 - groups.slskd = {}; 141 271 }; 142 272 143 - # Reverse proxy configuration 144 - services.nginx.enable = true; 145 - services.nginx.virtualHosts."${cfg.nginx.domainName}" = { 146 - forceSSL = true; 147 - enableACME = true; 148 - locations = { 149 - "${cfg.nginx.contextPath}" = { 150 - proxyPass = "http://localhost:${toString cfg.settings.web.port}"; 151 - proxyWebsockets = true; 152 - }; 153 - }; 273 + users.groups = lib.optionalAttrs (cfg.group == defaultUser) { 274 + "${defaultUser}" = {}; 154 275 }; 155 276 156 - # Hide state & logs 157 - systemd.tmpfiles.rules = [ 158 - "d /var/lib/slskd/data 0750 slskd slskd - -" 159 - "d /var/lib/slskd/logs 0750 slskd slskd - -" 160 - ]; 161 - 162 277 systemd.services.slskd = { 163 278 description = "A modern client-server application for the Soulseek file sharing network"; 164 279 after = [ "network.target" ]; 165 280 wantedBy = [ "multi-user.target" ]; 166 281 serviceConfig = { 167 282 Type = "simple"; 168 - User = "slskd"; 283 + User = cfg.user; 284 + Group = cfg.group; 169 285 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; 170 - StateDirectory = "slskd"; 286 + StateDirectory = "slskd"; # Creates /var/lib/slskd and manages permissions 171 287 ExecStart = "${cfg.package}/bin/slskd --app-dir /var/lib/slskd --config ${configurationYaml}"; 172 288 Restart = "on-failure"; 173 289 ReadOnlyPaths = map (d: builtins.elemAt (builtins.split "[^/]*(/.+)" d) 1) cfg.settings.shares.directories; 290 + ReadWritePaths = 291 + (lib.optional (cfg.settings.directories.incomplete != null) cfg.settings.directories.incomplete) ++ 292 + (lib.optional (cfg.settings.directories.downloads != null) cfg.settings.directories.downloads); 174 293 LockPersonality = true; 175 294 NoNewPrivileges = true; 176 295 PrivateDevices = true; ··· 194 313 195 314 networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.settings.soulseek.listen_port; 196 315 197 - systemd.services.slskd-rotatelogs = lib.mkIf cfg.rotateLogs { 198 - description = "Rotate slskd logs"; 199 - serviceConfig = { 200 - Type = "oneshot"; 201 - User = "slskd"; 202 - ExecStart = [ 203 - "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +10 -delete" 204 - "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +1 -exec ${pkgs.gzip}/bin/gzip -q {} ';'" 205 - ]; 206 - }; 207 - startAt = "daily"; 316 + services.nginx = lib.mkIf (cfg.domain != null) { 317 + enable = lib.mkDefault true; 318 + virtualHosts."${cfg.domain}" = lib.mkMerge [ 319 + cfg.nginx 320 + { 321 + locations."${cfg.settings.web.url_base}" = { 322 + proxyPass = "http://127.0.0.1:${toString cfg.settings.web.port}"; 323 + proxyWebsockets = true; 324 + }; 325 + } 326 + ]; 208 327 }; 328 + }; 209 329 330 + meta = { 331 + maintainers = with lib.maintainers; [ ppom melvyn2 ]; 210 332 }; 211 333 }
+1 -1
pkgs/servers/web-apps/slskd/default.nix
··· 21 21 description = "A modern client-server application for the Soulseek file sharing network"; 22 22 homepage = "https://github.com/slskd/slskd"; 23 23 license = licenses.agpl3Plus; 24 - maintainers = with maintainers; [ ppom ]; 24 + maintainers = with maintainers; [ ppom melvyn2 ]; 25 25 platforms = platforms.linux; 26 26 }; 27 27