···446446447447- 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.
448448449449+- The `services.slskd` has been refactored to include more configuation options in
450450+ the freeform `services.slskd.settings` option, and some defaults (including listen ports)
451451+ have been changed to match the upstream defaults. Additionally, disk logging is now
452452+ disabled by default, and the log rotation timer has been removed.
453453+ The nginx virtualhost option is now of the `vhost-options` type.
454454+449455- The `btrbk` module now automatically selects and provides required compression
450456 program depending on the configured `stream_compress` option. Since this
451457 replaces the need for the `extraPackages` option, this option will be
+224-102
nixos/modules/services/web-apps/slskd.nix
···2233let
44 settingsFormat = pkgs.formats.yaml {};
55+ defaultUser = "slskd";
56in {
67 options.services.slskd = with lib; with types; {
78 enable = mkEnableOption "enable slskd";
8999- rotateLogs = mkEnableOption "enable an unit and timer that will rotate logs in /var/slskd/logs";
1010+ package = mkPackageOptionMD pkgs "slskd" { };
10111111- package = mkPackageOption pkgs "slskd" { };
1212+ user = mkOption {
1313+ type = types.str;
1414+ default = defaultUser;
1515+ description = "User account under which slskd runs.";
1616+ };
12171313- nginx = mkOption {
1414- description = lib.mdDoc "options for nginx";
1515- example = {
1616- enable = true;
1717- domain = "example.com";
1818- contextPath = "/slskd";
1919- };
2020- type = submodule ({name, config, ...}: {
2121- options = {
2222- enable = mkEnableOption "enable nginx as a reverse proxy";
1818+ group = mkOption {
1919+ type = types.str;
2020+ default = defaultUser;
2121+ description = "Group under which slskd runs.";
2222+ };
23232424- domainName = mkOption {
2525- type = str;
2626- description = "Domain you want to use";
2727- };
2828- contextPath = mkOption {
2929- type = types.path;
3030- default = "/";
3131- description = lib.mdDoc ''
3232- The context path, i.e., the last part of the slskd
3333- URL. Typically '/' or '/slskd'. Default '/'
3434- '';
3535- };
3636- };
3737- });
2424+ domain = mkOption {
2525+ type = types.nullOr types.str;
2626+ description = ''
2727+ If non-null, enables an nginx reverse proxy virtual host at this FQDN,
2828+ at the path configurated with `services.slskd.web.url_base`.
2929+ '';
3030+ example = "slskd.example.com";
3131+ };
3232+3333+ nginx = mkOption {
3434+ type = types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
3535+ default = {};
3636+ example = lib.literalExpression ''
3737+ {
3838+ enableACME = true;
3939+ forceHttps = true;
4040+ }
4141+ '';
4242+ description = ''
4343+ This option customizes the nginx virtual host set up for slskd.
4444+ '';
3845 };
39464047 environmentFile = mkOption {
4148 type = path;
4249 description = ''
4343- Path to a file containing secrets.
4444- It must at least contain the variable `SLSKD_SLSK_PASSWORD`
5050+ Path to the environment file sourced on startup.
5151+ It must at least contain the variables `SLSKD_SLSK_USERNAME` and `SLSKD_SLSK_PASSWORD`.
5252+ Web interface credentials should also be set here in `SLSKD_USERNAME` and `SLSKD_PASSWORD`.
5353+ Other, optional credentials like SOCKS5 with `SLSKD_SLSK_PROXY_USERNAME` and `SLSKD_SLSK_PROXY_PASSWORD`
5454+ should all reside here instead of in the world-readable nix store.
5555+ Variables are documented at https://github.com/slskd/slskd/blob/master/docs/config.md
4556 '';
4657 };
47584859 openFirewall = mkOption {
4960 type = bool;
5050- description = ''
5151- Whether to open the firewall for services.slskd.settings.listen_port";
5252- '';
6161+ description = "Whether to open the firewall for the soulseek network listen port (not the web interface port).";
5362 default = false;
5463 };
55645665 settings = mkOption {
5757- description = lib.mdDoc ''
5858- Configuration for slskd, see
5959- [available options](https://github.com/slskd/slskd/blob/master/docs/config.md)
6060- `APP_DIR` is set to /var/lib/slskd, where default download & incomplete directories,
6161- log and databases will be created.
6666+ description = ''
6767+ Application configuration for slskd. See
6868+ [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md).
6269 '';
6370 default = {};
6471 type = submodule {
6572 freeformType = settingsFormat.type;
6673 options = {
7474+ remote_file_management = mkEnableOption "modification of share contents through the web ui";
7575+7676+ flags = {
7777+ force_share_scan = mkOption {
7878+ type = bool;
7979+ description = "Force a rescan of shares on every startup.";
8080+ };
8181+ no_version_check = mkOption {
8282+ type = bool;
8383+ default = true;
8484+ visible = false;
8585+ description = "Don't perform a version check on startup.";
8686+ };
8787+ };
8888+8989+ directories = {
9090+ incomplete = mkOption {
9191+ type = nullOr path;
9292+ description = "Directory where incomplete downloading files are stored.";
9393+ defaultText = "/var/lib/slskd/incomplete";
9494+ default = null;
9595+ };
9696+ downloads = mkOption {
9797+ type = nullOr path;
9898+ description = "Directory where downloaded files are stored.";
9999+ defaultText = "/var/lib/slskd/downloads";
100100+ default = null;
101101+ };
102102+ };
103103+104104+ shares = {
105105+ directories = mkOption {
106106+ type = listOf str;
107107+ description = ''
108108+ Paths to shared directories. See
109109+ [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories)
110110+ for advanced usage.
111111+ '';
112112+ example = lib.literalExpression ''[ "/home/John/Music" "!/home/John/Music/Recordings" "[Music Drive]/mnt" ]'';
113113+ };
114114+ filters = mkOption {
115115+ type = listOf str;
116116+ example = lib.literalExpression ''[ "\.ini$" "Thumbs.db$" "\.DS_Store$" ]'';
117117+ description = "Regular expressions of files to exclude from sharing.";
118118+ };
119119+ };
120120+121121+ rooms = mkOption {
122122+ type = listOf str;
123123+ description = "Chat rooms to join on startup.";
124124+ };
6712568126 soulseek = {
6969- username = mkOption {
127127+ description = mkOption {
70128 type = str;
7171- description = "Username on the Soulseek Network";
129129+ description = "The user description for the Soulseek network.";
130130+ defaultText = "A slskd user. https://github.com/slskd/slskd";
72131 };
73132 listen_port = mkOption {
74133 type = port;
7575- description = "Port to use for communication on the Soulseek Network";
7676- default = 50000;
134134+ description = "The port on which to listen for incoming connections.";
135135+ default = 50300;
136136+ };
137137+ };
138138+139139+ global = {
140140+ # TODO speed units
141141+ upload = {
142142+ slots = mkOption {
143143+ type = ints.unsigned;
144144+ description = "Limit of the number of concurrent upload slots.";
145145+ };
146146+ speed_limit = mkOption {
147147+ type = ints.unsigned;
148148+ description = "Total upload speed limit.";
149149+ };
150150+ };
151151+ download = {
152152+ slots = mkOption {
153153+ type = ints.unsigned;
154154+ description = "Limit of the number of concurrent download slots.";
155155+ };
156156+ speed_limit = mkOption {
157157+ type = ints.unsigned;
158158+ description = "Total upload download limit";
159159+ };
77160 };
78161 };
79162163163+ filters.search.request = mkOption {
164164+ type = listOf str;
165165+ example = lib.literalExpression ''[ "^.{1,2}$" ]'';
166166+ description = "Incoming search requests which match this filter are ignored.";
167167+ };
168168+80169 web = {
81170 port = mkOption {
82171 type = port;
8383- default = 5001;
8484- description = "The HTTP listen port";
172172+ default = 5030;
173173+ description = "The HTTP listen port.";
85174 };
86175 url_base = mkOption {
87176 type = path;
8888- default = config.services.slskd.nginx.contextPath;
8989- defaultText = "config.services.slskd.nginx.contextPath";
9090- description = lib.mdDoc ''
9191- The context path, i.e., the last part of the slskd URL
9292- '';
177177+ default = "/";
178178+ description = "The base path in the url for web requests.";
179179+ };
180180+ # Users should use a reverse proxy instead for https
181181+ https.disabled = mkOption {
182182+ type = bool;
183183+ default = true;
184184+ description = "Disable the built-in HTTPS server";
93185 };
94186 };
951879696- shares = {
9797- directories = mkOption {
9898- type = listOf str;
9999- description = lib.mdDoc ''
100100- Paths to your shared directories. See
101101- [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories)
102102- for advanced usage
103103- '';
188188+ retention = {
189189+ transfers = {
190190+ upload = {
191191+ succeeded = mkOption {
192192+ type = ints.unsigned;
193193+ description = "Lifespan of succeeded upload tasks.";
194194+ defaultText = "(indefinite)";
195195+ };
196196+ errored = mkOption {
197197+ type = ints.unsigned;
198198+ description = "Lifespan of errored upload tasks.";
199199+ defaultText = "(indefinite)";
200200+ };
201201+ cancelled = mkOption {
202202+ type = ints.unsigned;
203203+ description = "Lifespan of cancelled upload tasks.";
204204+ defaultText = "(indefinite)";
205205+ };
206206+ };
207207+ download = {
208208+ succeeded = mkOption {
209209+ type = ints.unsigned;
210210+ description = "Lifespan of succeeded download tasks.";
211211+ defaultText = "(indefinite)";
212212+ };
213213+ errored = mkOption {
214214+ type = ints.unsigned;
215215+ description = "Lifespan of errored download tasks.";
216216+ defaultText = "(indefinite)";
217217+ };
218218+ cancelled = mkOption {
219219+ type = ints.unsigned;
220220+ description = "Lifespan of cancelled download tasks.";
221221+ defaultText = "(indefinite)";
222222+ };
223223+ };
224224+ };
225225+ files = {
226226+ complete = mkOption {
227227+ type = ints.unsigned;
228228+ description = "Lifespan of completely downloaded files in minutes.";
229229+ example = 20160;
230230+ defaultText = "(indefinite)";
231231+ };
232232+ incomplete = mkOption {
233233+ type = ints.unsigned;
234234+ description = "Lifespan of incomplete downloading files in minutes.";
235235+ defaultText = "(indefinite)";
236236+ };
104237 };
105238 };
106239107107- directories = {
108108- incomplete = mkOption {
109109- type = nullOr path;
110110- description = "Directory where downloading files are stored";
111111- defaultText = "<APP_DIR>/incomplete";
112112- default = null;
113113- };
114114- downloads = mkOption {
115115- type = nullOr path;
116116- description = "Directory where downloaded files are stored";
117117- defaultText = "<APP_DIR>/downloads";
118118- default = null;
240240+ logger = {
241241+ # Disable by default, journald already retains as needed
242242+ disk = mkOption {
243243+ type = bool;
244244+ description = "Whether to log to the application directory.";
245245+ default = false;
246246+ visible = false;
119247 };
120248 };
121249 };
···126254 config = let
127255 cfg = config.services.slskd;
128256129129- confWithoutNullValues = (lib.filterAttrs (key: value: value != null) cfg.settings);
257257+ confWithoutNullValues = (lib.filterAttrsRecursive (key: value: (builtins.tryEval value).success && value != null) cfg.settings);
130258131259 configurationYaml = settingsFormat.generate "slskd.yml" confWithoutNullValues;
132260133261 in lib.mkIf cfg.enable {
134262135135- users = {
136136- users.slskd = {
263263+ # Force off, configuration file is in nix store and is immutable
264264+ services.slskd.settings.remote_configuration = lib.mkForce false;
265265+266266+ users.users = lib.optionalAttrs (cfg.user == defaultUser) {
267267+ "${defaultUser}" = {
268268+ group = cfg.group;
137269 isSystemUser = true;
138138- group = "slskd";
139270 };
140140- groups.slskd = {};
141271 };
142272143143- # Reverse proxy configuration
144144- services.nginx.enable = true;
145145- services.nginx.virtualHosts."${cfg.nginx.domainName}" = {
146146- forceSSL = true;
147147- enableACME = true;
148148- locations = {
149149- "${cfg.nginx.contextPath}" = {
150150- proxyPass = "http://localhost:${toString cfg.settings.web.port}";
151151- proxyWebsockets = true;
152152- };
153153- };
273273+ users.groups = lib.optionalAttrs (cfg.group == defaultUser) {
274274+ "${defaultUser}" = {};
154275 };
155276156156- # Hide state & logs
157157- systemd.tmpfiles.rules = [
158158- "d /var/lib/slskd/data 0750 slskd slskd - -"
159159- "d /var/lib/slskd/logs 0750 slskd slskd - -"
160160- ];
161161-162277 systemd.services.slskd = {
163278 description = "A modern client-server application for the Soulseek file sharing network";
164279 after = [ "network.target" ];
165280 wantedBy = [ "multi-user.target" ];
166281 serviceConfig = {
167282 Type = "simple";
168168- User = "slskd";
283283+ User = cfg.user;
284284+ Group = cfg.group;
169285 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
170170- StateDirectory = "slskd";
286286+ StateDirectory = "slskd"; # Creates /var/lib/slskd and manages permissions
171287 ExecStart = "${cfg.package}/bin/slskd --app-dir /var/lib/slskd --config ${configurationYaml}";
172288 Restart = "on-failure";
173289 ReadOnlyPaths = map (d: builtins.elemAt (builtins.split "[^/]*(/.+)" d) 1) cfg.settings.shares.directories;
290290+ ReadWritePaths =
291291+ (lib.optional (cfg.settings.directories.incomplete != null) cfg.settings.directories.incomplete) ++
292292+ (lib.optional (cfg.settings.directories.downloads != null) cfg.settings.directories.downloads);
174293 LockPersonality = true;
175294 NoNewPrivileges = true;
176295 PrivateDevices = true;
···194313195314 networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.settings.soulseek.listen_port;
196315197197- systemd.services.slskd-rotatelogs = lib.mkIf cfg.rotateLogs {
198198- description = "Rotate slskd logs";
199199- serviceConfig = {
200200- Type = "oneshot";
201201- User = "slskd";
202202- ExecStart = [
203203- "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +10 -delete"
204204- "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +1 -exec ${pkgs.gzip}/bin/gzip -q {} ';'"
205205- ];
206206- };
207207- startAt = "daily";
316316+ services.nginx = lib.mkIf (cfg.domain != null) {
317317+ enable = lib.mkDefault true;
318318+ virtualHosts."${cfg.domain}" = lib.mkMerge [
319319+ cfg.nginx
320320+ {
321321+ locations."${cfg.settings.web.url_base}" = {
322322+ proxyPass = "http://127.0.0.1:${toString cfg.settings.web.port}";
323323+ proxyWebsockets = true;
324324+ };
325325+ }
326326+ ];
208327 };
328328+ };
209329330330+ meta = {
331331+ maintainers = with lib.maintainers; [ ppom melvyn2 ];
210332 };
211333}
+1-1
pkgs/servers/web-apps/slskd/default.nix
···2121 description = "A modern client-server application for the Soulseek file sharing network";
2222 homepage = "https://github.com/slskd/slskd";
2323 license = licenses.agpl3Plus;
2424- maintainers = with maintainers; [ ppom ];
2424+ maintainers = with maintainers; [ ppom melvyn2 ];
2525 platforms = platforms.linux;
2626 };
2727