vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / audio / snapserver.nix
blob20186016afd70c5088d5cb09cff9efa0a72128ec
1 { config, options, lib, pkgs, ... }:
2 let
4   name = "snapserver";
6   cfg = config.services.snapserver;
8   # Using types.nullOr to inherit upstream defaults.
9   sampleFormat = lib.mkOption {
10     type = with lib.types; nullOr str;
11     default = null;
12     description = ''
13       Default sample format.
14     '';
15     example = "48000:16:2";
16   };
18   codec = lib.mkOption {
19     type = with lib.types; nullOr str;
20     default = null;
21     description = ''
22       Default audio compression method.
23     '';
24     example = "flac";
25   };
27   streamToOption = name: opt:
28     let
29       os = val:
30         lib.optionalString (val != null) "${val}";
31       os' = prefix: val:
32         lib.optionalString (val != null) (prefix + "${val}");
33       toQueryString = key: value:
34         "&${key}=${value}";
35     in
36       "--stream.stream=\"${opt.type}://" + os opt.location + "?" + os' "name=" name
37         + os' "&sampleformat=" opt.sampleFormat + os' "&codec=" opt.codec
38         + lib.concatStrings (lib.mapAttrsToList toQueryString opt.query) + "\"";
40   optionalNull = val: ret:
41     lib.optional (val != null) ret;
43   optionString = lib.concatStringsSep " " (lib.mapAttrsToList streamToOption cfg.streams
44     # global options
45     ++ [ "--stream.bind_to_address=${cfg.listenAddress}" ]
46     ++ [ "--stream.port=${toString cfg.port}" ]
47     ++ optionalNull cfg.sampleFormat "--stream.sampleformat=${cfg.sampleFormat}"
48     ++ optionalNull cfg.codec "--stream.codec=${cfg.codec}"
49     ++ optionalNull cfg.streamBuffer "--stream.stream_buffer=${toString cfg.streamBuffer}"
50     ++ optionalNull cfg.buffer "--stream.buffer=${toString cfg.buffer}"
51     ++ lib.optional cfg.sendToMuted "--stream.send_to_muted"
52     # tcp json rpc
53     ++ [ "--tcp.enabled=${toString cfg.tcp.enable}" ]
54     ++ lib.optionals cfg.tcp.enable [
55       "--tcp.bind_to_address=${cfg.tcp.listenAddress}"
56       "--tcp.port=${toString cfg.tcp.port}" ]
57      # http json rpc
58     ++ [ "--http.enabled=${toString cfg.http.enable}" ]
59     ++ lib.optionals cfg.http.enable [
60       "--http.bind_to_address=${cfg.http.listenAddress}"
61       "--http.port=${toString cfg.http.port}"
62     ] ++ lib.optional (cfg.http.docRoot != null) "--http.doc_root=\"${toString cfg.http.docRoot}\"");
64 in {
65   imports = [
66     (lib.mkRenamedOptionModule [ "services" "snapserver" "controlPort" ] [ "services" "snapserver" "tcp" "port" ])
67   ];
69   ###### interface
71   options = {
73     services.snapserver = {
75       enable = lib.mkOption {
76         type = lib.types.bool;
77         default = false;
78         description = ''
79           Whether to enable snapserver.
80         '';
81       };
83       listenAddress = lib.mkOption {
84         type = lib.types.str;
85         default = "::";
86         example = "0.0.0.0";
87         description = ''
88           The address where snapclients can connect.
89         '';
90       };
92       port = lib.mkOption {
93         type = lib.types.port;
94         default = 1704;
95         description = ''
96           The port that snapclients can connect to.
97         '';
98       };
100       openFirewall = lib.mkOption {
101         type = lib.types.bool;
102         default = false;
103         description = ''
104           Whether to automatically open the specified ports in the firewall.
105         '';
106       };
108       inherit sampleFormat;
109       inherit codec;
111       streamBuffer = lib.mkOption {
112         type = with lib.types; nullOr int;
113         default = null;
114         description = ''
115           Stream read (input) buffer in ms.
116         '';
117         example = 20;
118       };
120       buffer = lib.mkOption {
121         type = with lib.types; nullOr int;
122         default = null;
123         description = ''
124           Network buffer in ms.
125         '';
126         example = 1000;
127       };
129       sendToMuted = lib.mkOption {
130         type = lib.types.bool;
131         default = false;
132         description = ''
133           Send audio to muted clients.
134         '';
135       };
137       tcp.enable = lib.mkOption {
138         type = lib.types.bool;
139         default = true;
140         description = ''
141           Whether to enable the JSON-RPC via TCP.
142         '';
143       };
145       tcp.listenAddress = lib.mkOption {
146         type = lib.types.str;
147         default = "::";
148         example = "0.0.0.0";
149         description = ''
150           The address where the TCP JSON-RPC listens on.
151         '';
152       };
154       tcp.port = lib.mkOption {
155         type = lib.types.port;
156         default = 1705;
157         description = ''
158           The port where the TCP JSON-RPC listens on.
159         '';
160       };
162       http.enable = lib.mkOption {
163         type = lib.types.bool;
164         default = true;
165         description = ''
166           Whether to enable the JSON-RPC via HTTP.
167         '';
168       };
170       http.listenAddress = lib.mkOption {
171         type = lib.types.str;
172         default = "::";
173         example = "0.0.0.0";
174         description = ''
175           The address where the HTTP JSON-RPC listens on.
176         '';
177       };
179       http.port = lib.mkOption {
180         type = lib.types.port;
181         default = 1780;
182         description = ''
183           The port where the HTTP JSON-RPC listens on.
184         '';
185       };
187       http.docRoot = lib.mkOption {
188         type = with lib.types; nullOr path;
189         default = null;
190         description = ''
191           Path to serve from the HTTP servers root.
192         '';
193       };
195       streams = lib.mkOption {
196         type = with lib.types; attrsOf (submodule {
197           options = {
198             location = lib.mkOption {
199               type = lib.types.oneOf [ lib.types.path lib.types.str ];
200               description = ''
201                 For type `pipe` or `file`, the path to the pipe or file.
202                 For type `librespot`, `airplay` or `process`, the path to the corresponding binary.
203                 For type `tcp`, the `host:port` address to connect to or listen on.
204                 For type `meta`, a list of stream names in the form `/one/two/...`. Don't forget the leading slash.
205                 For type `alsa`, use an empty string.
206               '';
207               example = lib.literalExpression ''
208                 "/path/to/pipe"
209                 "/path/to/librespot"
210                 "192.168.1.2:4444"
211                 "/MyTCP/Spotify/MyPipe"
212               '';
213             };
214             type = lib.mkOption {
215               type = lib.types.enum [ "pipe" "librespot" "airplay" "file" "process" "tcp" "alsa" "spotify" "meta" ];
216               default = "pipe";
217               description = ''
218                 The type of input stream.
219               '';
220             };
221             query = lib.mkOption {
222               type = attrsOf str;
223               default = {};
224               description = ''
225                 Key-value pairs that convey additional parameters about a stream.
226               '';
227               example = lib.literalExpression ''
228                 # for type == "pipe":
229                 {
230                   mode = "create";
231                 };
232                 # for type == "process":
233                 {
234                   params = "--param1 --param2";
235                   logStderr = "true";
236                 };
237                 # for type == "tcp":
238                 {
239                   mode = "client";
240                 }
241                 # for type == "alsa":
242                 {
243                   device = "hw:0,0";
244                 }
245               '';
246             };
247             inherit sampleFormat;
248             inherit codec;
249           };
250         });
251         default = { default = {}; };
252         description = ''
253           The definition for an input source.
254         '';
255         example = lib.literalExpression ''
256           {
257             mpd = {
258               type = "pipe";
259               location = "/run/snapserver/mpd";
260               sampleFormat = "48000:16:2";
261               codec = "pcm";
262             };
263           };
264         '';
265       };
266     };
267   };
270   ###### implementation
272   config = lib.mkIf cfg.enable {
274     warnings =
275       # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
276       lib.filter (w: w != "") (lib.mapAttrsToList (k: v: lib.optionalString (v.type == "spotify") ''
277         services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
278       '') cfg.streams);
280     systemd.services.snapserver = {
281       after = [ "network.target" "nss-lookup.target" ];
282       description = "Snapserver";
283       wantedBy = [ "multi-user.target" ];
284       before = [ "mpd.service" "mopidy.service" ];
286       serviceConfig = {
287         DynamicUser = true;
288         ExecStart = "${pkgs.snapcast}/bin/snapserver --daemon ${optionString}";
289         Type = "forking";
290         LimitRTPRIO = 50;
291         LimitRTTIME = "infinity";
292         NoNewPrivileges = true;
293         PIDFile = "/run/${name}/pid";
294         ProtectKernelTunables = true;
295         ProtectControlGroups = true;
296         ProtectKernelModules = true;
297         RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
298         RestrictNamespaces = true;
299         RuntimeDirectory = name;
300         StateDirectory = name;
301       };
302     };
304     networking.firewall.allowedTCPPorts =
305       lib.optionals cfg.openFirewall [ cfg.port ]
306       ++ lib.optional (cfg.openFirewall && cfg.tcp.enable) cfg.tcp.port
307       ++ lib.optional (cfg.openFirewall && cfg.http.enable) cfg.http.port;
308   };
310   meta = {
311     maintainers = with lib.maintainers; [ tobim ];
312   };