{ config , pkgs , lib , ...}: with lib; let cfg = config.services.polaris; settingsFormat = pkgs.formats.toml {}; in { options = { services.polaris = { enable = mkEnableOption "Polaris Music Server"; package = mkPackageOption pkgs "polaris" { }; user = mkOption { type = types.str; default = "polaris"; description = "User account under which Polaris runs."; }; group = mkOption { type = types.str; default = "polaris"; description = "Group under which Polaris is run."; }; extraGroups = mkOption { type = types.listOf types.str; default = []; description = "Polaris' auxiliary groups."; example = literalExpression ''["media" "music"]''; }; port = mkOption { type = types.port; default = 5050; description = '' The port which the Polaris REST api and web UI should listen to. Note: polaris is hardcoded to listen to the hostname "0.0.0.0". ''; }; settings = mkOption { type = settingsFormat.type; default = {}; description = '' Contents for the TOML Polaris config, applied each start. Although poorly documented, an example may be found here: [test-config.toml](https://github.com/agersant/polaris/blob/374d0ca56fc0a466d797a4b252e2078607476797/test-data/config.toml) ''; example = literalExpression '' { settings.reindex_every_n_seconds = 7*24*60*60; # weekly, default is 1800 settings.album_art_pattern = "(cover|front|folder)\.(jpeg|jpg|png|bmp|gif)"; mount_dirs = [ { name = "NAS"; source = "/mnt/nas/music"; } { name = "Local"; source = "/home/my_user/Music"; } ]; } ''; }; openFirewall = mkOption { type = types.bool; default = false; description = '' Open the configured port in the firewall. ''; }; }; }; config = mkIf cfg.enable { systemd.services.polaris = { description = "Polaris Music Server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = rec { User = cfg.user; Group = cfg.group; DynamicUser = true; SupplementaryGroups = cfg.extraGroups; StateDirectory = "polaris"; CacheDirectory = "polaris"; ExecStart = escapeShellArgs ([ "${cfg.package}/bin/polaris" "--foreground" "--port" cfg.port "--database" "/var/lib/${StateDirectory}/db.sqlite" "--cache" "/var/cache/${CacheDirectory}" ] ++ optionals (cfg.settings != {}) [ "--config" (settingsFormat.generate "polaris-config.toml" cfg.settings) ]); Restart = "on-failure"; # Security options: #NoNewPrivileges = true; # implied by DynamicUser #RemoveIPC = true; # implied by DynamicUser AmbientCapabilities = ""; CapabilityBoundingSet = ""; DeviceAllow = ""; LockPersonality = true; #PrivateTmp = true; # implied by DynamicUser PrivateDevices = true; PrivateUsers = true; ProtectClock = true; ProtectControlGroups = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; RestrictNamespaces = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; RestrictRealtime = true; #RestrictSUIDSGID = true; # implied by DynamicUser SystemCallArchitectures = "native"; SystemCallErrorNumber = "EPERM"; SystemCallFilter = [ "@system-service" "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid" ]; }; }; networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; }; }; meta.maintainers = with maintainers; [ pbsds ]; }