1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 name = "snapserver";
10
11 inherit (lib)
12 literalExpression
13 mkEnableOption
14 mkOption
15 mkPackageOption
16 mkRemovedOptionModule
17 mkRenamedOptionModule
18 types
19 ;
20
21 cfg = config.services.snapserver;
22
23 format = pkgs.formats.ini {
24 listsAsDuplicateKeys = true;
25 };
26
27 configFile = format.generate "snapserver.conf" cfg.settings;
28
29in
30{
31 imports = [
32 (mkRenamedOptionModule
33 [ "services" "snapserver" "controlPort" ]
34 [ "services" "snapserver" "tcp" "port" ]
35 )
36
37 (mkRenamedOptionModule
38 [ "services" "snapserver" "listenAddress" ]
39 [ "services" "snapserver" "settings" "stream" "bind_to_address" ]
40 )
41 (mkRenamedOptionModule
42 [ "services" "snapserver" "port" ]
43 [ "services" "snapserver" "settings" "stream" "port" ]
44 )
45 (mkRenamedOptionModule
46 [ "services" "snapserver" "sampleFormat" ]
47 [ "services" "snapserver" "settings" "stream" "sampleformat" ]
48 )
49 (mkRenamedOptionModule
50 [ "services" "snapserver" "codec" ]
51 [ "services" "snapserver" "settings" "stream" "codec" ]
52 )
53 (mkRenamedOptionModule
54 [ "services" "snapserver" "streamBuffer" ]
55 [ "services" "snapserver" "settings" "stream" "chunk_ms" ]
56 )
57 (mkRenamedOptionModule
58 [ "services" "snapserver" "buffer" ]
59 [ "services" "snapserver" "settings" "stream" "buffer" ]
60 )
61 (mkRenamedOptionModule
62 [ "services" "snapserver" "send" ]
63 [ "services" "snapserver" "settings" "stream" "chunk_ms" ]
64 )
65
66 (mkRenamedOptionModule
67 [ "services" "snapserver" "tcp" "enable" ]
68 [ "services" "snapserver" "settings" "tcp" "enabled" ]
69 )
70 (mkRenamedOptionModule
71 [ "services" "snapserver" "tcp" "listenAddress" ]
72 [ "services" "snapserver" "settings" "tcp" "bind_to_address" ]
73 )
74 (mkRenamedOptionModule
75 [ "services" "snapserver" "tcp" "port" ]
76 [ "services" "snapserver" "settings" "tcp" "port" ]
77 )
78
79 (mkRenamedOptionModule
80 [ "services" "snapserver" "http" "enable" ]
81 [ "services" "snapserver" "settings" "http" "enabled" ]
82 )
83 (mkRenamedOptionModule
84 [ "services" "snapserver" "http" "listenAddress" ]
85 [ "services" "snapserver" "settings" "http" "bind_to_address" ]
86 )
87 (mkRenamedOptionModule
88 [ "services" "snapserver" "http" "port" ]
89 [ "services" "snapserver" "settings" "http" "port" ]
90 )
91 (mkRenamedOptionModule
92 [ "services" "snapserver" "http" "docRoot" ]
93 [ "services" "snapserver" "settings" "http" "doc_root" ]
94 )
95
96 (mkRemovedOptionModule [
97 "services"
98 "snapserver"
99 "streams"
100 ] "Configure `services.snapserver.settings.stream.source` instead")
101 ];
102
103 ###### interface
104
105 options = {
106
107 services.snapserver = {
108
109 enable = mkEnableOption "snapserver";
110
111 package = mkPackageOption pkgs "snapcast" { };
112
113 settings = mkOption {
114 default = { };
115 description = ''
116 Snapserver configuration.
117
118 Refer to the [example configuration](https://github.com/badaix/snapcast/blob/develop/server/etc/snapserver.conf) for possible options.
119 '';
120 type = types.submodule {
121 freeformType = format.type;
122 options = {
123 stream = {
124 bind_to_address = mkOption {
125 default = "::";
126 description = ''
127 Address to listen on for snapclient connections.
128 '';
129 };
130
131 port = mkOption {
132 type = types.port;
133 default = 1704;
134 description = ''
135 Port to listen on for snapclient connections.
136 '';
137 };
138
139 source = mkOption {
140 type = with types; either str (listOf str);
141 example = "pipe:///tmp/snapfifo?name=default";
142 description = ''
143 One or multiple URIs to PCM inpuit streams.
144 '';
145 };
146 };
147
148 tcp = {
149 enabled = mkEnableOption "the TCP JSON-RPC";
150
151 bind_to_address = mkOption {
152 default = "::";
153 description = ''
154 Address to listen on for snapclient connections.
155 '';
156 };
157
158 port = mkOption {
159 type = types.port;
160 default = 1705;
161 description = ''
162 Port to listen on for snapclient connections.
163 '';
164 };
165 };
166
167 http = {
168 enabled = mkEnableOption "the HTTP JSON-RPC";
169
170 bind_to_address = mkOption {
171 default = "::";
172 description = ''
173 Address to listen on for snapclient connections.
174 '';
175 };
176
177 port = mkOption {
178 type = types.port;
179 default = 1780;
180 description = ''
181 Port to listen on for snapclient connections.
182 '';
183 };
184
185 doc_root = lib.mkOption {
186 type = with lib.types; nullOr path;
187 default = pkgs.snapweb;
188 defaultText = literalExpression "pkgs.snapweb";
189 description = ''
190 Path to serve from the HTTP servers root.
191 '';
192 };
193 };
194 };
195 };
196 };
197
198 openFirewall = lib.mkOption {
199 type = lib.types.bool;
200 default = false;
201 description = ''
202 Whether to automatically open the specified ports in the firewall.
203 '';
204 };
205 };
206 };
207
208 ###### implementation
209
210 config = lib.mkIf cfg.enable {
211 environment.etc."snapserver.conf".source = configFile;
212
213 systemd.services.snapserver = {
214 after = [
215 "network.target"
216 "nss-lookup.target"
217 ];
218 description = "Snapserver";
219 wantedBy = [ "multi-user.target" ];
220 before = [
221 "mpd.service"
222 "mopidy.service"
223 ];
224 restartTriggers = [ configFile ];
225 serviceConfig = {
226 DynamicUser = true;
227 ExecStart = toString [
228 (lib.getExe' cfg.package "snapserver")
229 "--daemon"
230 ];
231 Type = "forking";
232 LimitRTPRIO = 50;
233 LimitRTTIME = "infinity";
234 NoNewPrivileges = true;
235 PIDFile = "/run/${name}/pid";
236 ProtectKernelTunables = true;
237 ProtectControlGroups = true;
238 ProtectKernelModules = true;
239 Restart = "on-failure";
240 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
241 RestrictNamespaces = true;
242 RuntimeDirectory = name;
243 StateDirectory = name;
244 };
245 };
246
247 networking.firewall.allowedTCPPorts =
248 lib.optionals cfg.openFirewall [ cfg.settings.stream.port ]
249 ++ lib.optional (cfg.openFirewall && cfg.settings.tcp.enabled) cfg.settings.tcp.port
250 ++ lib.optional (cfg.openFirewall && cfg.settings.http.enabled) cfg.settings.http.port;
251 };
252
253 meta = {
254 maintainers = with lib.maintainers; [ tobim ];
255 };
256
257}