slskd: init module (#233648)

* slskd: init module

* Update nixos/modules/services/web-apps/slskd.nix

* Update nixos/modules/services/web-apps/slskd.nix

* add description to slskd module options

---------

Co-authored-by: ppom <ppom@ppom.me>
Co-authored-by: Sandro <sandro.jaeckel@gmail.com>

authored by

ppom
ppom
Sandro
and committed by
GitHub
139259a3 5799b3d4

+212
+1
nixos/modules/module-list.nix
··· 1255 1255 ./services/web-apps/rss-bridge.nix 1256 1256 ./services/web-apps/selfoss.nix 1257 1257 ./services/web-apps/shiori.nix 1258 + ./services/web-apps/slskd.nix 1258 1259 ./services/web-apps/snipe-it.nix 1259 1260 ./services/web-apps/sogo.nix 1260 1261 ./services/web-apps/trilium.nix
+211
nixos/modules/services/web-apps/slskd.nix
··· 1 + { lib, pkgs, config, ... }: 2 + 3 + let 4 + settingsFormat = pkgs.formats.yaml {}; 5 + in { 6 + options.services.slskd = with lib; with types; { 7 + enable = mkEnableOption "enable slskd"; 8 + 9 + rotateLogs = mkEnableOption "enable an unit and timer that will rotate logs in /var/slskd/logs"; 10 + 11 + package = mkPackageOptionMD pkgs "slskd" { }; 12 + 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"; 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 + }); 38 + }; 39 + 40 + environmentFile = mkOption { 41 + type = path; 42 + description = '' 43 + Path to a file containing secrets. 44 + It must at least contain the variable `SLSKD_SLSK_PASSWORD` 45 + ''; 46 + }; 47 + 48 + openFirewall = mkOption { 49 + type = bool; 50 + description = '' 51 + Whether to open the firewall for services.slskd.settings.listen_port"; 52 + ''; 53 + default = false; 54 + }; 55 + 56 + 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. 62 + ''; 63 + default = {}; 64 + type = submodule { 65 + freeformType = settingsFormat.type; 66 + options = { 67 + 68 + soulseek = { 69 + username = mkOption { 70 + type = str; 71 + description = "Username on the Soulseek Network"; 72 + }; 73 + listen_port = mkOption { 74 + type = port; 75 + description = "Port to use for communication on the Soulseek Network"; 76 + default = 50000; 77 + }; 78 + }; 79 + 80 + web = { 81 + port = mkOption { 82 + type = port; 83 + default = 5001; 84 + description = "The HTTP listen port"; 85 + }; 86 + url_base = mkOption { 87 + 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 + ''; 93 + }; 94 + }; 95 + 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 + ''; 104 + }; 105 + }; 106 + 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; 119 + }; 120 + }; 121 + }; 122 + }; 123 + }; 124 + }; 125 + 126 + config = let 127 + cfg = config.services.slskd; 128 + 129 + confWithoutNullValues = (lib.filterAttrs (key: value: value != null) cfg.settings); 130 + 131 + configurationYaml = settingsFormat.generate "slskd.yml" confWithoutNullValues; 132 + 133 + in lib.mkIf cfg.enable { 134 + 135 + users = { 136 + users.slskd = { 137 + isSystemUser = true; 138 + group = "slskd"; 139 + }; 140 + groups.slskd = {}; 141 + }; 142 + 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 + }; 154 + }; 155 + 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 + systemd.services.slskd = { 163 + description = "A modern client-server application for the Soulseek file sharing network"; 164 + after = [ "network.target" ]; 165 + wantedBy = [ "multi-user.target" ]; 166 + serviceConfig = { 167 + Type = "simple"; 168 + User = "slskd"; 169 + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; 170 + StateDirectory = "slskd"; 171 + ExecStart = "${cfg.package}/bin/slskd --app-dir /var/lib/slskd --config ${configurationYaml}"; 172 + Restart = "on-failure"; 173 + ReadOnlyPaths = map (d: builtins.elemAt (builtins.split "[^/]*(/.+)" d) 1) cfg.settings.shares.directories; 174 + LockPersonality = true; 175 + NoNewPrivileges = true; 176 + PrivateDevices = true; 177 + PrivateMounts = true; 178 + PrivateTmp = true; 179 + PrivateUsers = true; 180 + ProtectClock = true; 181 + ProtectControlGroups = true; 182 + ProtectHome = true; 183 + ProtectHostname = true; 184 + ProtectKernelLogs = true; 185 + ProtectKernelModules = true; 186 + ProtectKernelTunables = true; 187 + ProtectProc = "invisible"; 188 + ProtectSystem = "strict"; 189 + RemoveIPC = true; 190 + RestrictNamespaces = true; 191 + RestrictSUIDSGID = true; 192 + }; 193 + }; 194 + 195 + networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.settings.soulseek.listen_port; 196 + 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"; 208 + }; 209 + 210 + }; 211 + }