1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7let
8 cfg = config.services.polaris;
9 settingsFormat = pkgs.formats.toml { };
10in
11{
12 options = {
13 services.polaris = {
14 enable = lib.mkEnableOption "Polaris Music Server";
15
16 package = lib.mkPackageOption pkgs "polaris" { };
17
18 user = lib.mkOption {
19 type = lib.types.str;
20 default = "polaris";
21 description = "User account under which Polaris runs.";
22 };
23
24 group = lib.mkOption {
25 type = lib.types.str;
26 default = "polaris";
27 description = "Group under which Polaris is run.";
28 };
29
30 extraGroups = lib.mkOption {
31 type = lib.types.listOf lib.types.str;
32 default = [ ];
33 description = "Polaris' auxiliary groups.";
34 example = lib.literalExpression ''["media" "music"]'';
35 };
36
37 port = lib.mkOption {
38 type = lib.types.port;
39 default = 5050;
40 description = ''
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 = lib.mkOption {
47 type = settingsFormat.type;
48 default = { };
49 description = ''
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 = lib.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 = lib.mkOption {
74 type = lib.types.bool;
75 default = false;
76 description = ''
77 Open the configured port in the firewall.
78 '';
79 };
80 };
81 };
82
83 config = lib.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 = lib.escapeShellArgs (
97 [
98 "${cfg.package}/bin/polaris"
99 "--foreground"
100 "--port"
101 cfg.port
102 "--database"
103 "/var/lib/${StateDirectory}/db.sqlite"
104 "--cache"
105 "/var/cache/${CacheDirectory}"
106 ]
107 ++ lib.optionals (cfg.settings != { }) [
108 "--config"
109 (settingsFormat.generate "polaris-config.toml" cfg.settings)
110 ]
111 );
112 Restart = "on-failure";
113
114 # Security options:
115
116 #NoNewPrivileges = true; # implied by DynamicUser
117 #RemoveIPC = true; # implied by DynamicUser
118
119 AmbientCapabilities = "";
120 CapabilityBoundingSet = "";
121
122 DeviceAllow = "";
123
124 LockPersonality = true;
125
126 #PrivateTmp = true; # implied by DynamicUser
127 PrivateDevices = true;
128 PrivateUsers = true;
129
130 ProtectClock = true;
131 ProtectControlGroups = true;
132 ProtectHostname = true;
133 ProtectKernelLogs = true;
134 ProtectKernelModules = true;
135 ProtectKernelTunables = true;
136
137 RestrictNamespaces = true;
138 RestrictAddressFamilies = [
139 "AF_INET"
140 "AF_INET6"
141 "AF_UNIX"
142 ];
143 RestrictRealtime = true;
144 #RestrictSUIDSGID = true; # implied by DynamicUser
145
146 SystemCallArchitectures = "native";
147 SystemCallErrorNumber = "EPERM";
148 SystemCallFilter = [
149 "@system-service"
150 "~@cpu-emulation"
151 "~@debug"
152 "~@keyring"
153 "~@memlock"
154 "~@obsolete"
155 "~@privileged"
156 "~@setuid"
157 ];
158 };
159 };
160
161 networking.firewall = lib.mkIf cfg.openFirewall {
162 allowedTCPPorts = [ cfg.port ];
163 };
164
165 };
166
167 meta.maintainers = with lib.maintainers; [ pbsds ];
168}