1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.murmur;
7 forking = cfg.logFile != null;
8 configFile = pkgs.writeText "murmurd.ini" ''
9 database=/var/lib/murmur/murmur.sqlite
10 dbDriver=QSQLITE
11
12 autobanAttempts=${toString cfg.autobanAttempts}
13 autobanTimeframe=${toString cfg.autobanTimeframe}
14 autobanTime=${toString cfg.autobanTime}
15
16 logfile=${optionalString (cfg.logFile != null) cfg.logFile}
17 ${optionalString forking "pidfile=/run/murmur/murmurd.pid"}
18
19 welcometext="${cfg.welcometext}"
20 port=${toString cfg.port}
21
22 ${if cfg.hostName == "" then "" else "host="+cfg.hostName}
23 ${if cfg.password == "" then "" else "serverpassword="+cfg.password}
24
25 bandwidth=${toString cfg.bandwidth}
26 users=${toString cfg.users}
27
28 textmessagelength=${toString cfg.textMsgLength}
29 imagemessagelength=${toString cfg.imgMsgLength}
30 allowhtml=${boolToString cfg.allowHtml}
31 logdays=${toString cfg.logDays}
32 bonjour=${boolToString cfg.bonjour}
33 sendversion=${boolToString cfg.sendVersion}
34
35 ${if cfg.registerName == "" then "" else "registerName="+cfg.registerName}
36 ${if cfg.registerPassword == "" then "" else "registerPassword="+cfg.registerPassword}
37 ${if cfg.registerUrl == "" then "" else "registerUrl="+cfg.registerUrl}
38 ${if cfg.registerHostname == "" then "" else "registerHostname="+cfg.registerHostname}
39
40 certrequired=${boolToString cfg.clientCertRequired}
41 ${if cfg.sslCert == "" then "" else "sslCert="+cfg.sslCert}
42 ${if cfg.sslKey == "" then "" else "sslKey="+cfg.sslKey}
43 ${if cfg.sslCa == "" then "" else "sslCA="+cfg.sslCa}
44
45 ${lib.optionalString (cfg.dbus != null) "dbus=${cfg.dbus}"}
46
47 ${cfg.extraConfig}
48 '';
49in
50{
51 imports = [
52 (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ])
53 (mkRemovedOptionModule [ "services" "murmur" "pidfile" ] "Hardcoded to /run/murmur/murmurd.pid now")
54 ];
55
56 options = {
57 services.murmur = {
58 enable = mkOption {
59 type = types.bool;
60 default = false;
61 description = lib.mdDoc "If enabled, start the Murmur Mumble server.";
62 };
63
64 openFirewall = mkOption {
65 type = types.bool;
66 default = false;
67 description = lib.mdDoc ''
68 Open ports in the firewall for the Murmur Mumble server.
69 '';
70 };
71
72 autobanAttempts = mkOption {
73 type = types.int;
74 default = 10;
75 description = lib.mdDoc ''
76 Number of attempts a client is allowed to make in
77 `autobanTimeframe` seconds, before being
78 banned for `autobanTime`.
79 '';
80 };
81
82 autobanTimeframe = mkOption {
83 type = types.int;
84 default = 120;
85 description = lib.mdDoc ''
86 Timeframe in which a client can connect without being banned
87 for repeated attempts (in seconds).
88 '';
89 };
90
91 autobanTime = mkOption {
92 type = types.int;
93 default = 300;
94 description = lib.mdDoc "The amount of time an IP ban lasts (in seconds).";
95 };
96
97 logFile = mkOption {
98 type = types.nullOr types.path;
99 default = null;
100 example = "/var/log/murmur/murmurd.log";
101 description = lib.mdDoc "Path to the log file for Murmur daemon. Empty means log to journald.";
102 };
103
104 welcometext = mkOption {
105 type = types.str;
106 default = "";
107 description = lib.mdDoc "Welcome message for connected clients.";
108 };
109
110 port = mkOption {
111 type = types.port;
112 default = 64738;
113 description = lib.mdDoc "Ports to bind to (UDP and TCP).";
114 };
115
116 hostName = mkOption {
117 type = types.str;
118 default = "";
119 description = lib.mdDoc "Host to bind to. Defaults binding on all addresses.";
120 };
121
122 package = mkOption {
123 type = types.package;
124 default = pkgs.murmur;
125 defaultText = literalExpression "pkgs.murmur";
126 description = lib.mdDoc "Overridable attribute of the murmur package to use.";
127 };
128
129 password = mkOption {
130 type = types.str;
131 default = "";
132 description = lib.mdDoc "Required password to join server, if specified.";
133 };
134
135 bandwidth = mkOption {
136 type = types.int;
137 default = 72000;
138 description = lib.mdDoc ''
139 Maximum bandwidth (in bits per second) that clients may send
140 speech at.
141 '';
142 };
143
144 users = mkOption {
145 type = types.int;
146 default = 100;
147 description = lib.mdDoc "Maximum number of concurrent clients allowed.";
148 };
149
150 textMsgLength = mkOption {
151 type = types.int;
152 default = 5000;
153 description = lib.mdDoc "Max length of text messages. Set 0 for no limit.";
154 };
155
156 imgMsgLength = mkOption {
157 type = types.int;
158 default = 131072;
159 description = lib.mdDoc "Max length of image messages. Set 0 for no limit.";
160 };
161
162 allowHtml = mkOption {
163 type = types.bool;
164 default = true;
165 description = lib.mdDoc ''
166 Allow HTML in client messages, comments, and channel
167 descriptions.
168 '';
169 };
170
171 logDays = mkOption {
172 type = types.int;
173 default = 31;
174 description = lib.mdDoc ''
175 How long to store RPC logs for in the database. Set 0 to
176 keep logs forever, or -1 to disable DB logging.
177 '';
178 };
179
180 bonjour = mkOption {
181 type = types.bool;
182 default = false;
183 description = lib.mdDoc ''
184 Enable Bonjour auto-discovery, which allows clients over
185 your LAN to automatically discover Murmur servers.
186 '';
187 };
188
189 sendVersion = mkOption {
190 type = types.bool;
191 default = true;
192 description = lib.mdDoc "Send Murmur version in UDP response.";
193 };
194
195 registerName = mkOption {
196 type = types.str;
197 default = "";
198 description = lib.mdDoc ''
199 Public server registration name, and also the name of the
200 Root channel. Even if you don't publicly register your
201 server, you probably still want to set this.
202 '';
203 };
204
205 registerPassword = mkOption {
206 type = types.str;
207 default = "";
208 description = lib.mdDoc ''
209 Public server registry password, used authenticate your
210 server to the registry to prevent impersonation; required for
211 subsequent registry updates.
212 '';
213 };
214
215 registerUrl = mkOption {
216 type = types.str;
217 default = "";
218 description = lib.mdDoc "URL website for your server.";
219 };
220
221 registerHostname = mkOption {
222 type = types.str;
223 default = "";
224 description = lib.mdDoc ''
225 DNS hostname where your server can be reached. This is only
226 needed if you want your server to be accessed by its
227 hostname and not IP - but the name *must* resolve on the
228 internet properly.
229 '';
230 };
231
232 clientCertRequired = mkOption {
233 type = types.bool;
234 default = false;
235 description = lib.mdDoc "Require clients to authenticate via certificates.";
236 };
237
238 sslCert = mkOption {
239 type = types.str;
240 default = "";
241 description = lib.mdDoc "Path to your SSL certificate.";
242 };
243
244 sslKey = mkOption {
245 type = types.str;
246 default = "";
247 description = lib.mdDoc "Path to your SSL key.";
248 };
249
250 sslCa = mkOption {
251 type = types.str;
252 default = "";
253 description = lib.mdDoc "Path to your SSL CA certificate.";
254 };
255
256 extraConfig = mkOption {
257 type = types.lines;
258 default = "";
259 description = lib.mdDoc "Extra configuration to put into murmur.ini.";
260 };
261
262 environmentFile = mkOption {
263 type = types.nullOr types.path;
264 default = null;
265 example = "/var/lib/murmur/murmurd.env";
266 description = lib.mdDoc ''
267 Environment file as defined in {manpage}`systemd.exec(5)`.
268
269 Secrets may be passed to the service without adding them to the world-readable
270 Nix store, by specifying placeholder variables as the option value in Nix and
271 setting these variables accordingly in the environment file.
272
273 ```
274 # snippet of murmur-related config
275 services.murmur.password = "$MURMURD_PASSWORD";
276 ```
277
278 ```
279 # content of the environment file
280 MURMURD_PASSWORD=verysecretpassword
281 ```
282
283 Note that this file needs to be available on the host on which
284 `murmur` is running.
285 '';
286 };
287
288 dbus = mkOption {
289 type = types.enum [ null "session" "system" ];
290 default = null;
291 description = lib.mdDoc "Enable D-Bus remote control. Set to the bus you want Murmur to connect to.";
292 };
293 };
294 };
295
296 config = mkIf cfg.enable {
297 users.users.murmur = {
298 description = "Murmur Service user";
299 home = "/var/lib/murmur";
300 createHome = true;
301 uid = config.ids.uids.murmur;
302 group = "murmur";
303 };
304 users.groups.murmur = {
305 gid = config.ids.gids.murmur;
306 };
307
308 networking.firewall = mkIf cfg.openFirewall {
309 allowedTCPPorts = [ cfg.port ];
310 allowedUDPPorts = [ cfg.port ];
311 };
312
313 systemd.services.murmur = {
314 description = "Murmur Chat Service";
315 wantedBy = [ "multi-user.target" ];
316 after = [ "network-online.target" ];
317 preStart = ''
318 ${pkgs.envsubst}/bin/envsubst \
319 -o /run/murmur/murmurd.ini \
320 -i ${configFile}
321 '';
322
323 serviceConfig = {
324 # murmurd doesn't fork when logging to the console.
325 Type = if forking then "forking" else "simple";
326 PIDFile = mkIf forking "/run/murmur/murmurd.pid";
327 EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
328 ExecStart = "${cfg.package}/bin/mumble-server -ini /run/murmur/murmurd.ini";
329 Restart = "always";
330 RuntimeDirectory = "murmur";
331 RuntimeDirectoryMode = "0700";
332 User = "murmur";
333 Group = "murmur";
334 };
335 };
336
337 # currently not included in upstream package, addition requested at
338 # https://github.com/mumble-voip/mumble/issues/6078
339 services.dbus.packages = mkIf (cfg.dbus == "system") [(pkgs.writeTextFile {
340 name = "murmur-dbus-policy";
341 text = ''
342 <!DOCTYPE busconfig PUBLIC
343 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
344 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
345 <busconfig>
346 <policy user="murmur">
347 <allow own="net.sourceforge.mumble.murmur"/>
348 </policy>
349
350 <policy context="default">
351 <allow send_destination="net.sourceforge.mumble.murmur"/>
352 <allow receive_sender="net.sourceforge.mumble.murmur"/>
353 </policy>
354 </busconfig>
355 '';
356 destination = "/share/dbus-1/system.d/murmur.conf";
357 })];
358 };
359}