1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.umurmur;
10 dumpAttrset =
11 x: top_level:
12 (lib.optionalString (!top_level) "{")
13 + (lib.concatLines (lib.mapAttrsToList (name: value: "${name} = ${toConfigValue value false};") x))
14 + (lib.optionalString (!top_level) "}");
15 dumpList = x: top_level: "(${lib.concatStringsSep ",\n" (map (y: "${toConfigValue y false}") x)})";
16
17 toConfigValue =
18 x: top_level:
19 if builtins.isList x then
20 dumpList x top_level
21 else if builtins.isAttrs x then
22 dumpAttrset x top_level
23 else
24 builtins.toJSON x;
25 dumpCfg = x: toConfigValue x true;
26 configAttrs = lib.filterAttrsRecursive (name: value: value != null) cfg.settings;
27 configFile = pkgs.writeTextFile {
28 name = "umurmur.conf";
29 checkPhase = ''
30 ${lib.getExe cfg.package} -t -c "$target"
31 '';
32 text = "\n" + (dumpCfg configAttrs) + "\n";
33 };
34in
35{
36 options = {
37 services.umurmur = {
38 enable = lib.mkEnableOption "uMurmur Mumble server";
39
40 package = lib.mkPackageOption pkgs "umurmur" { };
41
42 openFirewall = lib.mkOption {
43 type = lib.types.bool;
44 default = false;
45 description = ''
46 Open ports in the firewall for the uMurmur Mumble server.
47 '';
48 };
49
50 settings = lib.mkOption {
51 type = lib.types.submodule {
52 freeformType =
53 let
54 valueType =
55 with lib.types;
56 oneOf [
57 bool
58 int
59 float
60 str
61 path
62 (listOf (attrsOf valueType))
63 ]
64 // {
65 description = "uMurmur config value";
66 };
67 in
68 valueType;
69 options = {
70 welcometext = lib.mkOption {
71 type = lib.types.nullOr lib.types.str;
72 default = "Welcome to uMurmur!";
73 description = "Welcome message for connected clients.";
74 };
75
76 bindaddr = lib.mkOption {
77 type = lib.types.str;
78 default = "0.0.0.0";
79 description = "IPv4 address to bind to. Defaults binding on all addresses.";
80 };
81
82 bindaddr6 = lib.mkOption {
83 type = lib.types.str;
84 default = "::";
85 description = "IPv6 address to bind to. Defaults binding on all addresses.";
86 };
87
88 bindport = lib.mkOption {
89 type = lib.types.port;
90 default = 64739;
91 description = "Port to bind to (UDP and TCP).";
92 };
93
94 password = lib.mkOption {
95 type = lib.types.nullOr lib.types.str;
96 default = null;
97 description = "Required password to join server, if specified.";
98 };
99
100 max_bandwidth = lib.mkOption {
101 type = lib.types.int;
102 default = 48000;
103 description = ''
104 Maximum bandwidth (in bits per second) that clients may send
105 speech at.
106 '';
107 };
108
109 max_users = lib.mkOption {
110 type = lib.types.int;
111 default = 10;
112 description = "Maximum number of concurrent clients allowed.";
113 };
114
115 certificate = lib.mkOption {
116 type = lib.types.str;
117 default = "/var/lib/private/umurmur/cert.crt";
118 description = "Path to your SSL certificate. Generates self-signed automatically if not exists.";
119 };
120
121 private_key = lib.mkOption {
122 type = lib.types.str;
123 default = "/var/lib/private/umurmur/key.key";
124 description = "Path to your SSL key. Generates self-signed automatically if not exists.";
125 };
126
127 ca_path = lib.mkOption {
128 type = lib.types.nullOr lib.types.str;
129 default = null;
130 description = "Path to your SSL CA certificate.";
131 };
132
133 channels = lib.mkOption {
134 type = lib.types.listOf lib.types.attrs;
135 default = [
136 {
137 name = "root";
138 parent = "";
139 description = "Root channel.";
140 noenter = false;
141 }
142 ];
143 description = "Channel tree definitions.";
144 };
145
146 channel_links = lib.mkOption {
147 type = lib.types.listOf lib.types.attrs;
148 default = [ ];
149 example = [
150 {
151 source = "Lobby";
152 destination = "Red team";
153 }
154 ];
155 description = "Channel tree definitions.";
156 };
157
158 default_channel = lib.mkOption {
159 type = lib.types.str;
160 default = "root";
161 description = "The channel in which users will appear in when connecting.";
162 };
163
164 };
165 };
166 default = { };
167 description = "Settings of uMurmur. For reference see https://github.com/umurmur/umurmur/blob/master/umurmur.conf.example";
168 };
169
170 configFile = lib.mkOption rec {
171 type = lib.types.path;
172 default = configFile;
173 description = "Configuration file, default is generated from config.service.umurmur.settings";
174 defaultText = description;
175 };
176 };
177 };
178
179 config = lib.mkIf cfg.enable {
180 networking.firewall = lib.mkIf cfg.openFirewall {
181 allowedTCPPorts = [ cfg.settings.bindport ];
182 allowedUDPPorts = [ cfg.settings.bindport ];
183 };
184
185 systemd.services.umurmur = {
186 description = "uMurmur Mumble Server";
187 wants = [ "network.target" ];
188 after = [ "network.target" ];
189 wantedBy = [ "multi-user.target" ];
190 serviceConfig = {
191 Type = "exec";
192 ExecStart = "${lib.getExe cfg.package} -d -c ${cfg.configFile}";
193 Restart = "on-failure";
194 DynamicUser = true;
195 StateDirectory = "umurmur";
196 ReadWritePaths = "/dev/shm";
197
198 # hardening
199 UMask = 27;
200 MemoryDenyWriteExecute = true;
201 AmbientCapabilities = [ "" ];
202 CapabilityBoundingSet = [ "" ];
203 DevicePolicy = "closed";
204 LockPersonality = true;
205 NoNewPrivileges = true;
206 PrivateDevices = true;
207 PrivateTmp = true;
208 PrivateUsers = true;
209 ProtectSystem = "full";
210 ProtectClock = true;
211 ProtectControlGroups = true;
212 ProtectHome = true;
213 ProtectHostname = true;
214 ProtectKernelLogs = true;
215 ProtectKernelModules = true;
216 ProtectKernelTunables = true;
217 ProcSubset = "pid";
218 ProtectProc = "invisible";
219 RemoveIPC = true;
220 RestrictAddressFamilies = [
221 "AF_UNIX"
222 "AF_INET"
223 "AF_INET6"
224 ];
225 RestrictNamespaces = true;
226 RestrictRealtime = true;
227 RestrictSUIDSGID = true;
228 SystemCallArchitectures = "native";
229 SystemCallFilter = [
230 "@system-service"
231 "~@cpu-emulation"
232 "~@debug"
233 "~@mount"
234 "~@obsolete"
235 "~@privileged"
236 "~@resources"
237 ];
238 };
239 };
240 };
241
242 meta.maintainers = with lib.maintainers; [ _3JlOy-PYCCKUi ];
243 meta.doc = ./umurmur.md;
244}