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