diff options
Diffstat (limited to 'nixos/modules/services/web-apps/slskd.nix')
-rw-r--r-- | nixos/modules/services/web-apps/slskd.nix | 326 |
1 files changed, 224 insertions, 102 deletions
diff --git a/nixos/modules/services/web-apps/slskd.nix b/nixos/modules/services/web-apps/slskd.nix index 580f66ec3ac90..15a5fd1177adf 100644 --- a/nixos/modules/services/web-apps/slskd.nix +++ b/nixos/modules/services/web-apps/slskd.nix @@ -2,120 +2,248 @@ let settingsFormat = pkgs.formats.yaml {}; + defaultUser = "slskd"; in { options.services.slskd = with lib; with types; { enable = mkEnableOption "enable slskd"; - rotateLogs = mkEnableOption "enable an unit and timer that will rotate logs in /var/slskd/logs"; + package = mkPackageOptionMD pkgs "slskd" { }; - package = mkPackageOption pkgs "slskd" { }; + user = mkOption { + type = types.str; + default = defaultUser; + description = "User account under which slskd runs."; + }; - nginx = mkOption { - description = lib.mdDoc "options for nginx"; - example = { - enable = true; - domain = "example.com"; - contextPath = "/slskd"; - }; - type = submodule ({name, config, ...}: { - options = { - enable = mkEnableOption "enable nginx as a reverse proxy"; + group = mkOption { + type = types.str; + default = defaultUser; + description = "Group under which slskd runs."; + }; - domainName = mkOption { - type = str; - description = "Domain you want to use"; - }; - contextPath = mkOption { - type = types.path; - default = "/"; - description = lib.mdDoc '' - The context path, i.e., the last part of the slskd - URL. Typically '/' or '/slskd'. Default '/' - ''; - }; - }; - }); + domain = mkOption { + type = types.nullOr types.str; + description = '' + If non-null, enables an nginx reverse proxy virtual host at this FQDN, + at the path configurated with `services.slskd.web.url_base`. + ''; + example = "slskd.example.com"; + }; + + nginx = mkOption { + type = types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }); + default = {}; + example = lib.literalExpression '' + { + enableACME = true; + forceHttps = true; + } + ''; + description = '' + This option customizes the nginx virtual host set up for slskd. + ''; }; environmentFile = mkOption { type = path; description = '' - Path to a file containing secrets. - It must at least contain the variable `SLSKD_SLSK_PASSWORD` + Path to the environment file sourced on startup. + It must at least contain the variables `SLSKD_SLSK_USERNAME` and `SLSKD_SLSK_PASSWORD`. + Web interface credentials should also be set here in `SLSKD_USERNAME` and `SLSKD_PASSWORD`. + Other, optional credentials like SOCKS5 with `SLSKD_SLSK_PROXY_USERNAME` and `SLSKD_SLSK_PROXY_PASSWORD` + should all reside here instead of in the world-readable nix store. + Variables are documented at https://github.com/slskd/slskd/blob/master/docs/config.md ''; }; openFirewall = mkOption { type = bool; - description = '' - Whether to open the firewall for services.slskd.settings.listen_port"; - ''; + description = "Whether to open the firewall for the soulseek network listen port (not the web interface port)."; default = false; }; settings = mkOption { - description = lib.mdDoc '' - Configuration for slskd, see - [available options](https://github.com/slskd/slskd/blob/master/docs/config.md) - `APP_DIR` is set to /var/lib/slskd, where default download & incomplete directories, - log and databases will be created. + description = '' + Application configuration for slskd. See + [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md). ''; default = {}; type = submodule { freeformType = settingsFormat.type; options = { + remote_file_management = mkEnableOption "modification of share contents through the web ui"; + + flags = { + force_share_scan = mkOption { + type = bool; + description = "Force a rescan of shares on every startup."; + }; + no_version_check = mkOption { + type = bool; + default = true; + visible = false; + description = "Don't perform a version check on startup."; + }; + }; + + directories = { + incomplete = mkOption { + type = nullOr path; + description = "Directory where incomplete downloading files are stored."; + defaultText = "/var/lib/slskd/incomplete"; + default = null; + }; + downloads = mkOption { + type = nullOr path; + description = "Directory where downloaded files are stored."; + defaultText = "/var/lib/slskd/downloads"; + default = null; + }; + }; + + shares = { + directories = mkOption { + type = listOf str; + description = '' + Paths to shared directories. See + [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories) + for advanced usage. + ''; + example = lib.literalExpression ''[ "/home/John/Music" "!/home/John/Music/Recordings" "[Music Drive]/mnt" ]''; + }; + filters = mkOption { + type = listOf str; + example = lib.literalExpression ''[ "\.ini$" "Thumbs.db$" "\.DS_Store$" ]''; + description = "Regular expressions of files to exclude from sharing."; + }; + }; + + rooms = mkOption { + type = listOf str; + description = "Chat rooms to join on startup."; + }; soulseek = { - username = mkOption { + description = mkOption { type = str; - description = "Username on the Soulseek Network"; + description = "The user description for the Soulseek network."; + defaultText = "A slskd user. https://github.com/slskd/slskd"; }; listen_port = mkOption { type = port; - description = "Port to use for communication on the Soulseek Network"; - default = 50000; + description = "The port on which to listen for incoming connections."; + default = 50300; }; }; + global = { + # TODO speed units + upload = { + slots = mkOption { + type = ints.unsigned; + description = "Limit of the number of concurrent upload slots."; + }; + speed_limit = mkOption { + type = ints.unsigned; + description = "Total upload speed limit."; + }; + }; + download = { + slots = mkOption { + type = ints.unsigned; + description = "Limit of the number of concurrent download slots."; + }; + speed_limit = mkOption { + type = ints.unsigned; + description = "Total upload download limit"; + }; + }; + }; + + filters.search.request = mkOption { + type = listOf str; + example = lib.literalExpression ''[ "^.{1,2}$" ]''; + description = "Incoming search requests which match this filter are ignored."; + }; + web = { port = mkOption { type = port; - default = 5001; - description = "The HTTP listen port"; + default = 5030; + description = "The HTTP listen port."; }; url_base = mkOption { type = path; - default = config.services.slskd.nginx.contextPath; - defaultText = "config.services.slskd.nginx.contextPath"; - description = lib.mdDoc '' - The context path, i.e., the last part of the slskd URL - ''; + default = "/"; + description = "The base path in the url for web requests."; + }; + # Users should use a reverse proxy instead for https + https.disabled = mkOption { + type = bool; + default = true; + description = "Disable the built-in HTTPS server"; }; }; - shares = { - directories = mkOption { - type = listOf str; - description = lib.mdDoc '' - Paths to your shared directories. See - [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories) - for advanced usage - ''; + retention = { + transfers = { + upload = { + succeeded = mkOption { + type = ints.unsigned; + description = "Lifespan of succeeded upload tasks."; + defaultText = "(indefinite)"; + }; + errored = mkOption { + type = ints.unsigned; + description = "Lifespan of errored upload tasks."; + defaultText = "(indefinite)"; + }; + cancelled = mkOption { + type = ints.unsigned; + description = "Lifespan of cancelled upload tasks."; + defaultText = "(indefinite)"; + }; + }; + download = { + succeeded = mkOption { + type = ints.unsigned; + description = "Lifespan of succeeded download tasks."; + defaultText = "(indefinite)"; + }; + errored = mkOption { + type = ints.unsigned; + description = "Lifespan of errored download tasks."; + defaultText = "(indefinite)"; + }; + cancelled = mkOption { + type = ints.unsigned; + description = "Lifespan of cancelled download tasks."; + defaultText = "(indefinite)"; + }; + }; + }; + files = { + complete = mkOption { + type = ints.unsigned; + description = "Lifespan of completely downloaded files in minutes."; + example = 20160; + defaultText = "(indefinite)"; + }; + incomplete = mkOption { + type = ints.unsigned; + description = "Lifespan of incomplete downloading files in minutes."; + defaultText = "(indefinite)"; + }; }; }; - directories = { - incomplete = mkOption { - type = nullOr path; - description = "Directory where downloading files are stored"; - defaultText = "<APP_DIR>/incomplete"; - default = null; - }; - downloads = mkOption { - type = nullOr path; - description = "Directory where downloaded files are stored"; - defaultText = "<APP_DIR>/downloads"; - default = null; + logger = { + # Disable by default, journald already retains as needed + disk = mkOption { + type = bool; + description = "Whether to log to the application directory."; + default = false; + visible = false; }; }; }; @@ -126,51 +254,42 @@ in { config = let cfg = config.services.slskd; - confWithoutNullValues = (lib.filterAttrs (key: value: value != null) cfg.settings); + confWithoutNullValues = (lib.filterAttrsRecursive (key: value: (builtins.tryEval value).success && value != null) cfg.settings); configurationYaml = settingsFormat.generate "slskd.yml" confWithoutNullValues; in lib.mkIf cfg.enable { - users = { - users.slskd = { + # Force off, configuration file is in nix store and is immutable + services.slskd.settings.remote_configuration = lib.mkForce false; + + users.users = lib.optionalAttrs (cfg.user == defaultUser) { + "${defaultUser}" = { + group = cfg.group; isSystemUser = true; - group = "slskd"; }; - groups.slskd = {}; }; - # Reverse proxy configuration - services.nginx.enable = true; - services.nginx.virtualHosts."${cfg.nginx.domainName}" = { - forceSSL = true; - enableACME = true; - locations = { - "${cfg.nginx.contextPath}" = { - proxyPass = "http://localhost:${toString cfg.settings.web.port}"; - proxyWebsockets = true; - }; - }; + users.groups = lib.optionalAttrs (cfg.group == defaultUser) { + "${defaultUser}" = {}; }; - # Hide state & logs - systemd.tmpfiles.rules = [ - "d /var/lib/slskd/data 0750 slskd slskd - -" - "d /var/lib/slskd/logs 0750 slskd slskd - -" - ]; - systemd.services.slskd = { description = "A modern client-server application for the Soulseek file sharing network"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; - User = "slskd"; + User = cfg.user; + Group = cfg.group; EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; - StateDirectory = "slskd"; + StateDirectory = "slskd"; # Creates /var/lib/slskd and manages permissions ExecStart = "${cfg.package}/bin/slskd --app-dir /var/lib/slskd --config ${configurationYaml}"; Restart = "on-failure"; ReadOnlyPaths = map (d: builtins.elemAt (builtins.split "[^/]*(/.+)" d) 1) cfg.settings.shares.directories; + ReadWritePaths = + (lib.optional (cfg.settings.directories.incomplete != null) cfg.settings.directories.incomplete) ++ + (lib.optional (cfg.settings.directories.downloads != null) cfg.settings.directories.downloads); LockPersonality = true; NoNewPrivileges = true; PrivateDevices = true; @@ -194,18 +313,21 @@ in { networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.settings.soulseek.listen_port; - systemd.services.slskd-rotatelogs = lib.mkIf cfg.rotateLogs { - description = "Rotate slskd logs"; - serviceConfig = { - Type = "oneshot"; - User = "slskd"; - ExecStart = [ - "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +10 -delete" - "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +1 -exec ${pkgs.gzip}/bin/gzip -q {} ';'" - ]; - }; - startAt = "daily"; + services.nginx = lib.mkIf (cfg.domain != null) { + enable = lib.mkDefault true; + virtualHosts."${cfg.domain}" = lib.mkMerge [ + cfg.nginx + { + locations."${cfg.settings.web.url_base}" = { + proxyPass = "http://127.0.0.1:${toString cfg.settings.web.port}"; + proxyWebsockets = true; + }; + } + ]; }; + }; + meta = { + maintainers = with lib.maintainers; [ ppom melvyn2 ]; }; } |