1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.kea;
9
10 format = pkgs.formats.json { };
11
12 chooseNotNull = x: y: if x != null then x else y;
13
14 ctrlAgentConfig = chooseNotNull cfg.ctrl-agent.configFile (
15 format.generate "kea-ctrl-agent.conf" {
16 Control-agent = cfg.ctrl-agent.settings;
17 }
18 );
19
20 dhcp4Config = chooseNotNull cfg.dhcp4.configFile (
21 format.generate "kea-dhcp4.conf" {
22 Dhcp4 = cfg.dhcp4.settings;
23 }
24 );
25
26 dhcp6Config = chooseNotNull cfg.dhcp6.configFile (
27 format.generate "kea-dhcp6.conf" {
28 Dhcp6 = cfg.dhcp6.settings;
29 }
30 );
31
32 dhcpDdnsConfig = chooseNotNull cfg.dhcp-ddns.configFile (
33 format.generate "kea-dhcp-ddns.conf" {
34 DhcpDdns = cfg.dhcp-ddns.settings;
35 }
36 );
37
38 package = pkgs.kea;
39in
40{
41 options.services.kea = with lib.types; {
42 ctrl-agent = lib.mkOption {
43 description = ''
44 Kea Control Agent configuration
45 '';
46 default = { };
47 type = submodule {
48 options = {
49 enable = lib.mkEnableOption "Kea Control Agent";
50
51 extraArgs = lib.mkOption {
52 type = listOf str;
53 default = [ ];
54 description = ''
55 List of additional arguments to pass to the daemon.
56 '';
57 };
58
59 configFile = lib.mkOption {
60 type = nullOr path;
61 default = null;
62 description = ''
63 Kea Control Agent configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html>.
64
65 Takes preference over [settings](#opt-services.kea.ctrl-agent.settings).
66 Most users should prefer using [settings](#opt-services.kea.ctrl-agent.settings) instead.
67 '';
68 };
69
70 settings = lib.mkOption {
71 type = format.type;
72 default = null;
73 description = ''
74 Kea Control Agent configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html>.
75 '';
76 };
77 };
78 };
79 };
80
81 dhcp4 = lib.mkOption {
82 description = ''
83 DHCP4 Server configuration
84 '';
85 default = { };
86 type = submodule {
87 options = {
88 enable = lib.mkEnableOption "Kea DHCP4 server";
89
90 extraArgs = lib.mkOption {
91 type = listOf str;
92 default = [ ];
93 description = ''
94 List of additional arguments to pass to the daemon.
95 '';
96 };
97
98 configFile = lib.mkOption {
99 type = nullOr path;
100 default = null;
101 description = ''
102 Kea DHCP4 configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html>.
103
104 Takes preference over [settings](#opt-services.kea.dhcp4.settings).
105 Most users should prefer using [settings](#opt-services.kea.dhcp4.settings) instead.
106 '';
107 };
108
109 settings = lib.mkOption {
110 type = format.type;
111 default = null;
112 example = {
113 valid-lifetime = 4000;
114 renew-timer = 1000;
115 rebind-timer = 2000;
116 interfaces-config = {
117 interfaces = [
118 "eth0"
119 ];
120 };
121 lease-database = {
122 type = "memfile";
123 persist = true;
124 name = "/var/lib/kea/dhcp4.leases";
125 };
126 subnet4 = [
127 {
128 id = 1;
129 subnet = "192.0.2.0/24";
130 pools = [
131 {
132 pool = "192.0.2.100 - 192.0.2.240";
133 }
134 ];
135 }
136 ];
137 };
138 description = ''
139 Kea DHCP4 configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html>.
140 '';
141 };
142 };
143 };
144 };
145
146 dhcp6 = lib.mkOption {
147 description = ''
148 DHCP6 Server configuration
149 '';
150 default = { };
151 type = submodule {
152 options = {
153 enable = lib.mkEnableOption "Kea DHCP6 server";
154
155 extraArgs = lib.mkOption {
156 type = listOf str;
157 default = [ ];
158 description = ''
159 List of additional arguments to pass to the daemon.
160 '';
161 };
162
163 configFile = lib.mkOption {
164 type = nullOr path;
165 default = null;
166 description = ''
167 Kea DHCP6 configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html>.
168
169 Takes preference over [settings](#opt-services.kea.dhcp6.settings).
170 Most users should prefer using [settings](#opt-services.kea.dhcp6.settings) instead.
171 '';
172 };
173
174 settings = lib.mkOption {
175 type = format.type;
176 default = null;
177 example = {
178 valid-lifetime = 4000;
179 renew-timer = 1000;
180 rebind-timer = 2000;
181 preferred-lifetime = 3000;
182 interfaces-config = {
183 interfaces = [
184 "eth0"
185 ];
186 };
187 lease-database = {
188 type = "memfile";
189 persist = true;
190 name = "/var/lib/kea/dhcp6.leases";
191 };
192 subnet6 = [
193 {
194 id = 1;
195 subnet = "2001:db8:1::/64";
196 pools = [
197 {
198 pool = "2001:db8:1::1-2001:db8:1::ffff";
199 }
200 ];
201 }
202 ];
203 };
204 description = ''
205 Kea DHCP6 configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html>.
206 '';
207 };
208 };
209 };
210 };
211
212 dhcp-ddns = lib.mkOption {
213 description = ''
214 Kea DHCP-DDNS configuration
215 '';
216 default = { };
217 type = submodule {
218 options = {
219 enable = lib.mkEnableOption "Kea DDNS server";
220
221 extraArgs = lib.mkOption {
222 type = listOf str;
223 default = [ ];
224 description = ''
225 List of additional arguments to pass to the daemon.
226 '';
227 };
228
229 configFile = lib.mkOption {
230 type = nullOr path;
231 default = null;
232 description = ''
233 Kea DHCP-DDNS configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html>.
234
235 Takes preference over [settings](#opt-services.kea.dhcp-ddns.settings).
236 Most users should prefer using [settings](#opt-services.kea.dhcp-ddns.settings) instead.
237 '';
238 };
239
240 settings = lib.mkOption {
241 type = format.type;
242 default = null;
243 example = {
244 ip-address = "127.0.0.1";
245 port = 53001;
246 dns-server-timeout = 100;
247 ncr-protocol = "UDP";
248 ncr-format = "JSON";
249 tsig-keys = [ ];
250 forward-ddns = {
251 ddns-domains = [ ];
252 };
253 reverse-ddns = {
254 ddns-domains = [ ];
255 };
256 };
257 description = ''
258 Kea DHCP-DDNS configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html>.
259 '';
260 };
261 };
262 };
263 };
264 };
265
266 config =
267 let
268 commonServiceConfig = {
269 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
270 DynamicUser = true;
271 User = "kea";
272 ConfigurationDirectory = "kea";
273 RuntimeDirectory = "kea";
274 RuntimeDirectoryPreserve = true;
275 StateDirectory = "kea";
276 UMask = "0077";
277 };
278 in
279 lib.mkIf (cfg.ctrl-agent.enable || cfg.dhcp4.enable || cfg.dhcp6.enable || cfg.dhcp-ddns.enable) (
280 lib.mkMerge [
281 {
282 environment.systemPackages = [ package ];
283 }
284
285 (lib.mkIf cfg.ctrl-agent.enable {
286 assertions = [
287 {
288 assertion = lib.xor (cfg.ctrl-agent.settings == null) (cfg.ctrl-agent.configFile == null);
289 message = "Either services.kea.ctrl-agent.settings or services.kea.ctrl-agent.configFile must be set to a non-null value.";
290 }
291 ];
292
293 environment.etc."kea/ctrl-agent.conf".source = ctrlAgentConfig;
294
295 systemd.services.kea-ctrl-agent = {
296 description = "Kea Control Agent";
297 documentation = [
298 "man:kea-ctrl-agent(8)"
299 "https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"
300 ];
301
302 wants = [
303 "network-online.target"
304 ];
305 after = [
306 "network-online.target"
307 "time-sync.target"
308 ];
309 wantedBy = [
310 "kea-dhcp4-server.service"
311 "kea-dhcp6-server.service"
312 "kea-dhcp-ddns-server.service"
313 ];
314
315 environment = {
316 KEA_PIDFILE_DIR = "/run/kea";
317 KEA_LOCKFILE_DIR = "/run/kea";
318 };
319
320 restartTriggers = [
321 ctrlAgentConfig
322 ];
323
324 serviceConfig = {
325 ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.ctrl-agent.extraArgs}";
326 KillMode = "process";
327 Restart = "on-failure";
328 } // commonServiceConfig;
329 };
330 })
331
332 (lib.mkIf cfg.dhcp4.enable {
333 assertions = [
334 {
335 assertion = lib.xor (cfg.dhcp4.settings == null) (cfg.dhcp4.configFile == null);
336 message = "Either services.kea.dhcp4.settings or services.kea.dhcp4.configFile must be set to a non-null value.";
337 }
338 ];
339
340 environment.etc."kea/dhcp4-server.conf".source = dhcp4Config;
341
342 systemd.services.kea-dhcp4-server = {
343 description = "Kea DHCP4 Server";
344 documentation = [
345 "man:kea-dhcp4(8)"
346 "https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"
347 ];
348
349 after = [
350 "network-online.target"
351 "time-sync.target"
352 ];
353 wants = [
354 "network-online.target"
355 ];
356 wantedBy = [
357 "multi-user.target"
358 ];
359
360 environment = {
361 KEA_PIDFILE_DIR = "/run/kea";
362 KEA_LOCKFILE_DIR = "/run/kea";
363 };
364
365 restartTriggers = [
366 dhcp4Config
367 ];
368
369 serviceConfig = {
370 ExecStart = "${package}/bin/kea-dhcp4 -c /etc/kea/dhcp4-server.conf ${lib.escapeShellArgs cfg.dhcp4.extraArgs}";
371 # Kea does not request capabilities by itself
372 AmbientCapabilities = [
373 "CAP_NET_BIND_SERVICE"
374 "CAP_NET_RAW"
375 ];
376 CapabilityBoundingSet = [
377 "CAP_NET_BIND_SERVICE"
378 "CAP_NET_RAW"
379 ];
380 } // commonServiceConfig;
381 };
382 })
383
384 (lib.mkIf cfg.dhcp6.enable {
385 assertions = [
386 {
387 assertion = lib.xor (cfg.dhcp6.settings == null) (cfg.dhcp6.configFile == null);
388 message = "Either services.kea.dhcp6.settings or services.kea.dhcp6.configFile must be set to a non-null value.";
389 }
390 ];
391
392 environment.etc."kea/dhcp6-server.conf".source = dhcp6Config;
393
394 systemd.services.kea-dhcp6-server = {
395 description = "Kea DHCP6 Server";
396 documentation = [
397 "man:kea-dhcp6(8)"
398 "https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"
399 ];
400
401 after = [
402 "network-online.target"
403 "time-sync.target"
404 ];
405 wants = [
406 "network-online.target"
407 ];
408 wantedBy = [
409 "multi-user.target"
410 ];
411
412 environment = {
413 KEA_PIDFILE_DIR = "/run/kea";
414 KEA_LOCKFILE_DIR = "/run/kea";
415 };
416
417 restartTriggers = [
418 dhcp6Config
419 ];
420
421 serviceConfig = {
422 ExecStart = "${package}/bin/kea-dhcp6 -c /etc/kea/dhcp6-server.conf ${lib.escapeShellArgs cfg.dhcp6.extraArgs}";
423 # Kea does not request capabilities by itself
424 AmbientCapabilities = [
425 "CAP_NET_BIND_SERVICE"
426 ];
427 CapabilityBoundingSet = [
428 "CAP_NET_BIND_SERVICE"
429 ];
430 } // commonServiceConfig;
431 };
432 })
433
434 (lib.mkIf cfg.dhcp-ddns.enable {
435 assertions = [
436 {
437 assertion = lib.xor (cfg.dhcp-ddns.settings == null) (cfg.dhcp-ddns.configFile == null);
438 message = "Either services.kea.dhcp-ddns.settings or services.kea.dhcp-ddns.configFile must be set to a non-null value.";
439 }
440 ];
441
442 environment.etc."kea/dhcp-ddns.conf".source = dhcpDdnsConfig;
443
444 systemd.services.kea-dhcp-ddns-server = {
445 description = "Kea DHCP-DDNS Server";
446 documentation = [
447 "man:kea-dhcp-ddns(8)"
448 "https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"
449 ];
450
451 wants = [ "network-online.target" ];
452 after = [
453 "network-online.target"
454 "time-sync.target"
455 ];
456 wantedBy = [
457 "multi-user.target"
458 ];
459
460 environment = {
461 KEA_PIDFILE_DIR = "/run/kea";
462 KEA_LOCKFILE_DIR = "/run/kea";
463 };
464
465 restartTriggers = [
466 dhcpDdnsConfig
467 ];
468
469 serviceConfig = {
470 ExecStart = "${package}/bin/kea-dhcp-ddns -c /etc/kea/dhcp-ddns.conf ${lib.escapeShellArgs cfg.dhcp-ddns.extraArgs}";
471 AmbientCapabilities = [
472 "CAP_NET_BIND_SERVICE"
473 ];
474 CapabilityBoundingSet = [
475 "CAP_NET_BIND_SERVICE"
476 ];
477 } // commonServiceConfig;
478 };
479 })
480
481 ]
482 );
483
484 meta.maintainers = with lib.maintainers; [ hexa ];
485 # uses attributes of the linked package
486 meta.buildDocsInSandbox = false;
487}