1{
2 config,
3 lib,
4 options,
5 pkgs,
6 ...
7}:
8
9with lib;
10
11let
12
13 cfg = config.networking.wireguard;
14 opt = options.networking.wireguard;
15
16 kernel = config.boot.kernelPackages;
17
18 # interface options
19
20 interfaceOpts =
21 { ... }:
22 {
23
24 options = {
25
26 type = mkOption {
27 example = "amneziawg";
28 default = "wireguard";
29 type = types.enum [
30 "wireguard"
31 "amneziawg"
32 ];
33 description = ''
34 The type of the interface. Currently only "wireguard" and "amneziawg" are supported.
35 '';
36 };
37
38 ips = mkOption {
39 example = [ "192.168.2.1/24" ];
40 default = [ ];
41 type = with types; listOf str;
42 description = "The IP addresses of the interface.";
43 };
44
45 privateKey = mkOption {
46 example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
47 type = with types; nullOr str;
48 default = null;
49 description = ''
50 Base64 private key generated by {command}`wg genkey`.
51
52 Warning: Consider using privateKeyFile instead if you do not
53 want to store the key in the world-readable Nix store.
54 '';
55 };
56
57 generatePrivateKeyFile = mkOption {
58 default = false;
59 type = types.bool;
60 description = ''
61 Automatically generate a private key with
62 {command}`wg genkey`, at the privateKeyFile location.
63 '';
64 };
65
66 privateKeyFile = mkOption {
67 example = "/private/wireguard_key";
68 type = with types; nullOr str;
69 default = null;
70 description = ''
71 Private key file as generated by {command}`wg genkey`.
72 '';
73 };
74
75 listenPort = mkOption {
76 default = null;
77 type = with types; nullOr int;
78 example = 51820;
79 description = ''
80 16-bit port for listening. Optional; if not specified,
81 automatically generated based on interface name.
82 '';
83 };
84
85 preSetup = mkOption {
86 example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
87 default = "";
88 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
89 description = ''
90 Commands called at the start of the interface setup.
91 '';
92 };
93
94 postSetup = mkOption {
95 example = literalExpression ''
96 '''printf "nameserver 10.200.100.1" | ''${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0'''
97 '';
98 default = "";
99 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
100 description = "Commands called at the end of the interface setup.";
101 };
102
103 preShutdown = mkOption {
104 example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
105 default = "";
106 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
107 description = ''
108 Commands called before shutting down the interface.
109 '';
110 };
111
112 postShutdown = mkOption {
113 example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
114 default = "";
115 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
116 description = "Commands called after shutting down the interface.";
117 };
118
119 table = mkOption {
120 default = "main";
121 type = types.str;
122 description = ''
123 The kernel routing table to add this interface's
124 associated routes to. Setting this is useful for e.g. policy routing
125 ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
126 numeric table IDs and table names (/etc/rt_tables) can be used.
127 Defaults to "main".
128 '';
129 };
130
131 peers = mkOption {
132 default = [ ];
133 description = "Peers linked to the interface.";
134 type = with types; listOf (submodule peerOpts);
135 };
136
137 allowedIPsAsRoutes = mkOption {
138 example = false;
139 default = true;
140 type = types.bool;
141 description = ''
142 Determines whether to add allowed IPs as routes or not.
143 '';
144 };
145
146 socketNamespace = mkOption {
147 default = null;
148 type = with types; nullOr str;
149 example = "container";
150 description = ''
151 The pre-existing network namespace in which the
152 WireGuard interface is created, and which retains the socket even if the
153 interface is moved via {option}`interfaceNamespace`. When
154 `null`, the interface is created in the init namespace.
155 See [documentation](https://www.wireguard.com/netns/).
156 '';
157 };
158
159 interfaceNamespace = mkOption {
160 default = null;
161 type = with types; nullOr str;
162 example = "init";
163 description = ''
164 The pre-existing network namespace the WireGuard
165 interface is moved to. The special value `init` means
166 the init namespace. When `null`, the interface is not
167 moved.
168 See [documentation](https://www.wireguard.com/netns/).
169 '';
170 };
171
172 fwMark = mkOption {
173 default = null;
174 type = with types; nullOr str;
175 example = "0x6e6978";
176 description = ''
177 Mark all wireguard packets originating from
178 this interface with the given firewall mark. The firewall mark can be
179 used in firewalls or policy routing to filter the wireguard packets.
180 This can be useful for setup where all traffic goes through the
181 wireguard tunnel, because the wireguard packets need to be routed
182 differently.
183 '';
184 };
185
186 mtu = mkOption {
187 default = null;
188 type = with types; nullOr int;
189 example = 1280;
190 description = ''
191 Set the maximum transmission unit in bytes for the wireguard
192 interface. Beware that the wireguard packets have a header that may
193 add up to 80 bytes to the mtu. By default, the MTU is (1500 - 80) =
194 1420. However, if the MTU of the upstream network is lower, the MTU
195 of the wireguard network has to be adjusted as well.
196 '';
197 };
198
199 metric = mkOption {
200 default = null;
201 type = with types; nullOr int;
202 example = 700;
203 description = ''
204 Set the metric of routes related to this Wireguard interface.
205 '';
206 };
207
208 dynamicEndpointRefreshSeconds = mkOption {
209 default = 0;
210 example = 300;
211 type = with types; int;
212 description = ''
213 Periodically refresh the endpoint hostname or address for all peers.
214 Allows WireGuard to notice DNS and IPv4/IPv6 connectivity changes.
215 This option can be set or overridden for individual peers.
216
217 Setting this to `0` disables periodic refresh.
218
219 ::: {.warning}
220 When {option}`networking.wireguard.useNetworkd` is enabled, this
221 option deletes the Wireguard interface and brings it back up by
222 reconfiguring the network with `networkctl reload` on every refresh.
223 This could have adverse effects on your network and cause brief
224 connectivity blips. See [systemd/systemd#9911](https://github.com/systemd/systemd/issues/9911)
225 for an upstream feature request that can make this less hacky.
226 :::
227 '';
228 };
229
230 extraOptions = mkOption {
231 type =
232 with types;
233 attrsOf (oneOf [
234 str
235 int
236 ]);
237 default = { };
238 example = {
239 Jc = 5;
240 Jmin = 10;
241 Jmax = 42;
242 S1 = 60;
243 S2 = 90;
244 H4 = 12345;
245 };
246 description = ''
247 Extra options to append to the interface section. Can be used to define AmneziaWG-specific options.
248 '';
249 };
250 };
251
252 };
253
254 # peer options
255
256 peerOpts = self: {
257
258 options = {
259
260 name = mkOption {
261 default =
262 replaceStrings [ "/" "-" " " "+" "=" ] [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ]
263 self.config.publicKey;
264 defaultText = literalExpression "publicKey";
265 example = "bernd";
266 type = types.str;
267 description = "Name used to derive peer unit name.";
268 };
269
270 publicKey = mkOption {
271 example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
272 type = types.singleLineStr;
273 description = "The base64 public key of the peer.";
274 };
275
276 presharedKey = mkOption {
277 default = null;
278 example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
279 type = with types; nullOr str;
280 description = ''
281 Base64 preshared key generated by {command}`wg genpsk`.
282 Optional, and may be omitted. This option adds an additional layer of
283 symmetric-key cryptography to be mixed into the already existing
284 public-key cryptography, for post-quantum resistance.
285
286 Warning: Consider using presharedKeyFile instead if you do not
287 want to store the key in the world-readable Nix store.
288 '';
289 };
290
291 presharedKeyFile = mkOption {
292 default = null;
293 example = "/private/wireguard_psk";
294 type = with types; nullOr str;
295 description = ''
296 File pointing to preshared key as generated by {command}`wg genpsk`.
297 Optional, and may be omitted. This option adds an additional layer of
298 symmetric-key cryptography to be mixed into the already existing
299 public-key cryptography, for post-quantum resistance.
300 '';
301 };
302
303 allowedIPs = mkOption {
304 example = [
305 "10.192.122.3/32"
306 "10.192.124.1/24"
307 ];
308 type = with types; listOf str;
309 description = ''
310 List of IP (v4 or v6) addresses with CIDR masks from
311 which this peer is allowed to send incoming traffic and to which
312 outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
313 be specified for matching all IPv4 addresses, and ::/0 may be specified
314 for matching all IPv6 addresses.'';
315 };
316
317 endpoint = mkOption {
318 default = null;
319 example = "demo.wireguard.io:12913";
320 type = with types; nullOr str;
321 description = ''
322 Endpoint IP or hostname of the peer, followed by a colon,
323 and then a port number of the peer.
324
325 Warning for endpoints with changing IPs:
326 The WireGuard kernel side cannot perform DNS resolution.
327 Thus DNS resolution is done once by the `wg` userspace
328 utility, when setting up WireGuard. Consequently, if the IP address
329 behind the name changes, WireGuard will not notice.
330 This is especially common for dynamic-DNS setups, but also applies to
331 any other DNS-based setup.
332 If you do not use IP endpoints, you likely want to set
333 {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
334 to refresh the IPs periodically.
335 '';
336 };
337
338 dynamicEndpointRefreshSeconds = mkOption {
339 default = null;
340 defaultText = literalExpression "config.networking.wireguard.interfaces.<name>.dynamicEndpointRefreshSeconds";
341 example = 5;
342 type = with types; nullOr int;
343 description = ''
344 Periodically re-execute the `wg` utility every
345 this many seconds in order to let WireGuard notice DNS / hostname
346 changes.
347
348 Setting this to `0` disables periodic reexecution.
349
350 ::: {.note}
351 This peer-level setting is not available when {option}`networking.wireguard.useNetworkd`
352 is enabled. The interface-level setting may be used instead.
353 :::
354 '';
355 };
356
357 dynamicEndpointRefreshRestartSeconds = mkOption {
358 default = null;
359 example = 5;
360 type = with types; nullOr ints.unsigned;
361 description = ''
362 When the dynamic endpoint refresh that is configured via
363 dynamicEndpointRefreshSeconds exits (likely due to a failure),
364 restart that service after this many seconds.
365
366 If set to `null` the value of
367 {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
368 will be used as the default.
369 '';
370 };
371
372 persistentKeepalive = mkOption {
373 default = null;
374 type = with types; nullOr int;
375 example = 25;
376 description = ''
377 This is optional and is by default off, because most
378 users will not need it. It represents, in seconds, between 1 and 65535
379 inclusive, how often to send an authenticated empty packet to the peer,
380 for the purpose of keeping a stateful firewall or NAT mapping valid
381 persistently. For example, if the interface very rarely sends traffic,
382 but it might at anytime receive traffic from a peer, and it is behind
383 NAT, the interface might benefit from having a persistent keepalive
384 interval of 25 seconds; however, most users will not need this.'';
385 };
386
387 };
388
389 };
390
391 wgBins = {
392 wireguard = "wg";
393 amneziawg = "awg";
394 };
395
396 wgPackages = {
397 wireguard = pkgs.wireguard-tools;
398 amneziawg = pkgs.amneziawg-tools;
399 };
400
401 generateKeyServiceUnit =
402 name: values:
403 assert values.generatePrivateKeyFile;
404 nameValuePair "wireguard-${name}-key" {
405 description = "WireGuard Tunnel - ${name} - Key Generator";
406 wantedBy = [ "wireguard-${name}.service" ];
407 requiredBy = [ "wireguard-${name}.service" ];
408 before = [ "wireguard-${name}.service" ];
409 path = [ wgPackages.${values.type} ];
410
411 serviceConfig = {
412 Type = "oneshot";
413 RemainAfterExit = true;
414 };
415
416 script = ''
417 set -e
418
419 # If the parent dir does not already exist, create it.
420 # Otherwise, does nothing, keeping existing permissions intact.
421 mkdir -p --mode 0755 "${dirOf values.privateKeyFile}"
422
423 if [ ! -f "${values.privateKeyFile}" ]; then
424 # Write private key file with atomically-correct permissions.
425 (set -e; umask 077; ${wgBins.${values.type}} genkey > "${values.privateKeyFile}")
426 fi
427 '';
428 };
429
430 peerUnitServiceName =
431 interfaceName: peerName: dynamicRefreshEnabled:
432 let
433 refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
434 in
435 "wireguard-${interfaceName}-peer-${peerName}${refreshSuffix}";
436
437 dynamicRefreshSeconds =
438 interfaceCfg: peer:
439 if peer.dynamicEndpointRefreshSeconds != null then
440 peer.dynamicEndpointRefreshSeconds
441 else
442 interfaceCfg.dynamicEndpointRefreshSeconds;
443
444 generatePeerUnit =
445 {
446 interfaceName,
447 interfaceCfg,
448 peer,
449 }:
450 let
451 psk =
452 if peer.presharedKey != null then
453 pkgs.writeText "wg-psk" peer.presharedKey
454 else
455 peer.presharedKeyFile;
456 src = interfaceCfg.socketNamespace;
457 dst = interfaceCfg.interfaceNamespace;
458 ip = nsWrap "ip" src dst;
459 wg = nsWrap wgBins.${interfaceCfg.type} src dst;
460 dynamicEndpointRefreshSeconds = dynamicRefreshSeconds interfaceCfg peer;
461 dynamicRefreshEnabled = dynamicEndpointRefreshSeconds != 0;
462 # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds`
463 # to avoid that the same service switches `Type` (`oneshot` vs `simple`),
464 # with the intent to make scripting more obvious.
465 serviceName = peerUnitServiceName interfaceName peer.name dynamicRefreshEnabled;
466 in
467 nameValuePair serviceName {
468 description =
469 "WireGuard Peer - ${interfaceName} - ${peer.name}"
470 + optionalString (peer.name != peer.publicKey) " (${peer.publicKey})";
471 requires = [ "wireguard-${interfaceName}.service" ];
472 wants = [ "network-online.target" ];
473 after = [
474 "wireguard-${interfaceName}.service"
475 "network-online.target"
476 ];
477 wantedBy = [ "wireguard-${interfaceName}.service" ];
478 environment.DEVICE = interfaceName;
479 environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
480 path = with pkgs; [
481 iproute2
482 wgPackages.${interfaceCfg.type}
483 ];
484
485 serviceConfig =
486 if !dynamicRefreshEnabled then
487 {
488 Type = "oneshot";
489 RemainAfterExit = true;
490 }
491 else
492 {
493 Type = "simple"; # re-executes 'wg' indefinitely
494 # Note that `Type = "oneshot"` services with `RemainAfterExit = true`
495 # cannot be used with systemd timers (see `man systemd.timer`),
496 # which is why `simple` with a loop is the best choice here.
497 # It also makes starting and stopping easiest.
498 #
499 # Restart if the service exits (e.g. when wireguard gives up after "Name or service not known" dns failures):
500 Restart = "always";
501 RestartSec =
502 if null != peer.dynamicEndpointRefreshRestartSeconds then
503 peer.dynamicEndpointRefreshRestartSeconds
504 else
505 dynamicEndpointRefreshSeconds;
506 };
507 unitConfig = lib.optionalAttrs dynamicRefreshEnabled {
508 StartLimitIntervalSec = 0;
509 };
510
511 script =
512 let
513 wg_setup = concatStringsSep " " (
514 [ ''${wg} set ${interfaceName} peer "${peer.publicKey}"'' ]
515 ++ optional (psk != null) ''preshared-key "${psk}"''
516 ++ optional (peer.endpoint != null) ''endpoint "${peer.endpoint}"''
517 ++ optional (
518 peer.persistentKeepalive != null
519 ) ''persistent-keepalive "${toString peer.persistentKeepalive}"''
520 ++ optional (peer.allowedIPs != [ ]) ''allowed-ips "${concatStringsSep "," peer.allowedIPs}"''
521 );
522 route_setup = optionalString interfaceCfg.allowedIPsAsRoutes (
523 concatMapStringsSep "\n" (
524 allowedIP:
525 ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}" ${
526 optionalString (interfaceCfg.metric != null) "metric ${toString interfaceCfg.metric}"
527 }''
528 ) peer.allowedIPs
529 );
530 in
531 ''
532 ${wg_setup}
533 ${route_setup}
534
535 ${optionalString (dynamicEndpointRefreshSeconds != 0) ''
536 # Re-execute 'wg' periodically to notice DNS / hostname changes.
537 # Note this will not time out on transient DNS failures such as DNS names
538 # because we have set 'WG_ENDPOINT_RESOLUTION_RETRIES=infinity'.
539 # Also note that 'wg' limits its maximum retry delay to 20 seconds as of writing.
540 while ${wg_setup}; do
541 sleep "${toString dynamicEndpointRefreshSeconds}";
542 done
543 ''}
544 '';
545
546 postStop =
547 let
548 route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes (
549 concatMapStringsSep "\n" (
550 allowedIP:
551 ''${ip} route delete "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
552 ) peer.allowedIPs
553 );
554 in
555 ''
556 ${wg} set "${interfaceName}" peer "${peer.publicKey}" remove
557 ${route_destroy}
558 '';
559 };
560
561 # the target is required to start new peer units when they are added
562 generateInterfaceTarget =
563 name: values:
564 let
565 mkPeerUnit =
566 peer: (peerUnitServiceName name peer.name (dynamicRefreshSeconds values peer != 0)) + ".service";
567 in
568 nameValuePair "wireguard-${name}" rec {
569 description = "WireGuard Tunnel - ${name}";
570 wantedBy = [ "multi-user.target" ];
571 wants = [ "wireguard-${name}.service" ] ++ map mkPeerUnit values.peers;
572 after = wants;
573 };
574
575 generateInterfaceUnit =
576 name: values:
577 # exactly one way to specify the private key must be set
578 #assert (values.privateKey != null) != (values.privateKeyFile != null);
579 let
580 privKey =
581 if values.privateKeyFile != null then
582 values.privateKeyFile
583 else
584 pkgs.writeText "wg-key" values.privateKey;
585 src = values.socketNamespace;
586 dst = values.interfaceNamespace;
587 ipPreMove = nsWrap "ip" src null;
588 ipPostMove = nsWrap "ip" src dst;
589 wg = nsWrap wgBins.${values.type} src dst;
590 ns = if dst == "init" then "1" else dst;
591
592 in
593 nameValuePair "wireguard-${name}" {
594 description = "WireGuard Tunnel - ${name}";
595 after = [ "network-pre.target" ];
596 wants = [ "network.target" ];
597 before = [ "network.target" ];
598 environment.DEVICE = name;
599 path = with pkgs; [
600 kmod
601 iproute2
602 wgPackages.${values.type}
603 ];
604
605 serviceConfig = {
606 Type = "oneshot";
607 RemainAfterExit = true;
608 };
609
610 script = concatStringsSep "\n" (
611 optional (!config.boot.isContainer) "modprobe ${values.type} || true"
612 ++ [
613 values.preSetup
614 ''${ipPreMove} link add dev "${name}" type ${values.type}''
615 ]
616 ++ optional (
617 values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace
618 ) ''${ipPreMove} link set "${name}" netns "${ns}"''
619 ++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''
620 ++ (map (ip: ''${ipPostMove} address add "${ip}" dev "${name}"'') values.ips)
621 ++ [
622 (concatStringsSep " " (
623 [ ''${wg} set "${name}" private-key "${privKey}"'' ]
624 ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
625 ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
626 ++ mapAttrsToList (k: v: ''${toLower k} "${toString v}"'') values.extraOptions
627 ))
628 ''${ipPostMove} link set up dev "${name}"''
629 values.postSetup
630 ]
631 );
632
633 postStop = ''
634 ${values.preShutdown}
635 ${ipPostMove} link del dev "${name}"
636 ${values.postShutdown}
637 '';
638 };
639
640 nsWrap =
641 cmd: src: dst:
642 let
643 nsList = filter (ns: ns != null) [
644 src
645 dst
646 ];
647 ns = last nsList;
648 in
649 if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd;
650
651 usingWg = any (x: x.type == "wireguard") (attrValues cfg.interfaces);
652 usingAwg = any (x: x.type == "amneziawg") (attrValues cfg.interfaces);
653in
654
655{
656
657 ###### interface
658
659 options = {
660
661 networking.wireguard = {
662
663 enable = mkOption {
664 description = ''
665 Whether to enable WireGuard.
666
667 ::: {.note}
668 By default, this module is powered by a script-based backend. You can
669 enable the networkd backend with {option}`networking.wireguard.useNetworkd`.
670 :::
671 '';
672 type = types.bool;
673 # 2019-05-25: Backwards compatibility.
674 default = cfg.interfaces != { };
675 defaultText = literalExpression "config.${opt.interfaces} != { }";
676 example = true;
677 };
678
679 interfaces = mkOption {
680 description = ''
681 WireGuard interfaces.
682 '';
683 default = { };
684 example = {
685 wg0 = {
686 ips = [ "192.168.20.4/24" ];
687 privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
688 peers = [
689 {
690 allowedIPs = [ "192.168.20.1/32" ];
691 publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
692 endpoint = "demo.wireguard.io:12913";
693 }
694 ];
695 };
696 };
697 type = with types; attrsOf (submodule interfaceOpts);
698 };
699
700 };
701
702 };
703
704 ###### implementation
705
706 config = mkIf cfg.enable (
707 let
708 all_peers = flatten (
709 mapAttrsToList (
710 interfaceName: interfaceCfg:
711 map (peer: { inherit interfaceName interfaceCfg peer; }) interfaceCfg.peers
712 ) cfg.interfaces
713 );
714 in
715 {
716
717 assertions =
718 (attrValues (
719 mapAttrs (name: value: {
720 assertion = (value.privateKey != null) != (value.privateKeyFile != null);
721 message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
722 }) cfg.interfaces
723 ))
724 ++ (attrValues (
725 mapAttrs (name: value: {
726 assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
727 message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
728 }) cfg.interfaces
729 ))
730 ++ map (
731 { interfaceName, peer, ... }:
732 {
733 assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
734 message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
735 }
736 ) all_peers;
737
738 boot.extraModulePackages =
739 optional (usingWg && (versionOlder kernel.kernel.version "5.6")) kernel.wireguard
740 ++ optional usingAwg kernel.amneziawg;
741 boot.kernelModules = optional usingWg "wireguard" ++ optional usingAwg "amneziawg";
742 environment.systemPackages =
743 optional usingWg pkgs.wireguard-tools
744 ++ optional usingAwg pkgs.amneziawg-tools;
745
746 systemd.services = mkIf (!cfg.useNetworkd) (
747 (mapAttrs' generateInterfaceUnit cfg.interfaces)
748 // (listToAttrs (map generatePeerUnit all_peers))
749 // (mapAttrs' generateKeyServiceUnit (
750 filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces
751 ))
752 );
753
754 systemd.targets = mkIf (!cfg.useNetworkd) (mapAttrs' generateInterfaceTarget cfg.interfaces);
755 }
756 );
757
758}