1{ config
2, pkgs
3, lib
4, ...}:
5
6with lib;
7let
8 cfg = config.services.polaris;
9 settingsFormat = pkgs.formats.toml {};
10in
11{
12 options = {
13 services.polaris = {
14 enable = mkEnableOption (lib.mdDoc "Polaris Music Server");
15
16 package = mkPackageOptionMD pkgs "polaris" { };
17
18 user = mkOption {
19 type = types.str;
20 default = "polaris";
21 description = lib.mdDoc "User account under which Polaris runs.";
22 };
23
24 group = mkOption {
25 type = types.str;
26 default = "polaris";
27 description = lib.mdDoc "Group under which Polaris is run.";
28 };
29
30 extraGroups = mkOption {
31 type = types.listOf types.str;
32 default = [];
33 description = lib.mdDoc "Polaris' auxiliary groups.";
34 example = literalExpression ''["media" "music"]'';
35 };
36
37 port = mkOption {
38 type = types.port;
39 default = 5050;
40 description = lib.mdDoc ''
41 The port which the Polaris REST api and web UI should listen to.
42 Note: polaris is hardcoded to listen to the hostname "0.0.0.0".
43 '';
44 };
45
46 settings = mkOption {
47 type = settingsFormat.type;
48 default = {};
49 description = lib.mdDoc ''
50 Contents for the TOML Polaris config, applied each start.
51 Although poorly documented, an example may be found here:
52 [test-config.toml](https://github.com/agersant/polaris/blob/374d0ca56fc0a466d797a4b252e2078607476797/test-data/config.toml)
53 '';
54 example = literalExpression ''
55 {
56 settings.reindex_every_n_seconds = 7*24*60*60; # weekly, default is 1800
57 settings.album_art_pattern =
58 "(cover|front|folder)\.(jpeg|jpg|png|bmp|gif)";
59 mount_dirs = [
60 {
61 name = "NAS";
62 source = "/mnt/nas/music";
63 }
64 {
65 name = "Local";
66 source = "/home/my_user/Music";
67 }
68 ];
69 }
70 '';
71 };
72
73 openFirewall = mkOption {
74 type = types.bool;
75 default = false;
76 description = lib.mdDoc ''
77 Open the configured port in the firewall.
78 '';
79 };
80 };
81 };
82
83 config = mkIf cfg.enable {
84 systemd.services.polaris = {
85 description = "Polaris Music Server";
86 after = [ "network.target" ];
87 wantedBy = [ "multi-user.target" ];
88
89 serviceConfig = rec {
90 User = cfg.user;
91 Group = cfg.group;
92 DynamicUser = true;
93 SupplementaryGroups = cfg.extraGroups;
94 StateDirectory = "polaris";
95 CacheDirectory = "polaris";
96 ExecStart = escapeShellArgs ([
97 "${cfg.package}/bin/polaris"
98 "--foreground"
99 "--port" cfg.port
100 "--database" "/var/lib/${StateDirectory}/db.sqlite"
101 "--cache" "/var/cache/${CacheDirectory}"
102 ] ++ optionals (cfg.settings != {}) [
103 "--config" (settingsFormat.generate "polaris-config.toml" cfg.settings)
104 ]);
105 Restart = "on-failure";
106
107 # Security options:
108
109 #NoNewPrivileges = true; # implied by DynamicUser
110 #RemoveIPC = true; # implied by DynamicUser
111
112 AmbientCapabilities = "";
113 CapabilityBoundingSet = "";
114
115 DeviceAllow = "";
116
117 LockPersonality = true;
118
119 #PrivateTmp = true; # implied by DynamicUser
120 PrivateDevices = true;
121 PrivateUsers = true;
122
123 ProtectClock = true;
124 ProtectControlGroups = true;
125 ProtectHostname = true;
126 ProtectKernelLogs = true;
127 ProtectKernelModules = true;
128 ProtectKernelTunables = true;
129
130 RestrictNamespaces = true;
131 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
132 RestrictRealtime = true;
133 #RestrictSUIDSGID = true; # implied by DynamicUser
134
135 SystemCallArchitectures = "native";
136 SystemCallErrorNumber = "EPERM";
137 SystemCallFilter = [
138 "@system-service"
139 "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
140 ];
141 };
142 };
143
144 networking.firewall = mkIf cfg.openFirewall {
145 allowedTCPPorts = [ cfg.port ];
146 };
147
148 };
149
150 meta.maintainers = with maintainers; [ pbsds ];
151}