1{ lib, config, pkgs, ... }:
2with lib;
3let
4 cfg = config.services.wgautomesh;
5 settingsFormat = pkgs.formats.toml { };
6 configFile =
7 # Have to remove nulls manually as TOML generator will not just skip key
8 # if value is null
9 settingsFormat.generate "wgautomesh-config.toml"
10 (filterAttrs (k: v: v != null)
11 (mapAttrs
12 (k: v:
13 if k == "peers"
14 then map (e: filterAttrs (k: v: v != null) e) v
15 else v)
16 cfg.settings));
17 runtimeConfigFile =
18 if cfg.enableGossipEncryption
19 then "/run/wgautomesh/wgautomesh.toml"
20 else configFile;
21in
22{
23 options.services.wgautomesh = {
24 enable = mkEnableOption (mdDoc "the wgautomesh daemon");
25 logLevel = mkOption {
26 type = types.enum [ "trace" "debug" "info" "warn" "error" ];
27 default = "info";
28 description = mdDoc "wgautomesh log level.";
29 };
30 enableGossipEncryption = mkOption {
31 type = types.bool;
32 default = true;
33 description = mdDoc "Enable encryption of gossip traffic.";
34 };
35 gossipSecretFile = mkOption {
36 type = types.path;
37 description = mdDoc ''
38 File containing the shared secret key to use for gossip encryption.
39 Required if `enableGossipEncryption` is set.
40 '';
41 };
42 enablePersistence = mkOption {
43 type = types.bool;
44 default = true;
45 description = mdDoc "Enable persistence of Wireguard peer info between restarts.";
46 };
47 openFirewall = mkOption {
48 type = types.bool;
49 default = true;
50 description = mdDoc "Automatically open gossip port in firewall (recommended).";
51 };
52 settings = mkOption {
53 type = types.submodule {
54 freeformType = settingsFormat.type;
55 options = {
56
57 interface = mkOption {
58 type = types.str;
59 description = mdDoc ''
60 Wireguard interface to manage (it is NOT created by wgautomesh, you
61 should use another NixOS option to create it such as
62 `networking.wireguard.interfaces.wg0 = {...};`).
63 '';
64 example = "wg0";
65 };
66 gossip_port = mkOption {
67 type = types.port;
68 description = mdDoc ''
69 wgautomesh gossip port, this MUST be the same number on all nodes in
70 the wgautomesh network.
71 '';
72 default = 1666;
73 };
74 lan_discovery = mkOption {
75 type = types.bool;
76 default = true;
77 description = mdDoc "Enable discovery of peers on the same LAN using UDP broadcast.";
78 };
79 upnp_forward_external_port = mkOption {
80 type = types.nullOr types.port;
81 default = null;
82 description = mdDoc ''
83 Public port number to try to redirect to this machine's Wireguard
84 daemon using UPnP IGD.
85 '';
86 };
87 peers = mkOption {
88 type = types.listOf (types.submodule {
89 options = {
90 pubkey = mkOption {
91 type = types.str;
92 description = mdDoc "Wireguard public key of this peer.";
93 };
94 address = mkOption {
95 type = types.str;
96 description = mdDoc ''
97 Wireguard address of this peer (a single IP address, multiple
98 addresses or address ranges are not supported).
99 '';
100 example = "10.0.0.42";
101 };
102 endpoint = mkOption {
103 type = types.nullOr types.str;
104 description = mdDoc ''
105 Bootstrap endpoint for connecting to this Wireguard peer if no
106 other address is known or none are working.
107 '';
108 default = null;
109 example = "wgnode.mydomain.example:51820";
110 };
111 };
112 });
113 default = [ ];
114 description = mdDoc "wgautomesh peer list.";
115 };
116 };
117
118 };
119 default = { };
120 description = mdDoc "Configuration for wgautomesh.";
121 };
122 };
123
124 config = mkIf cfg.enable {
125 services.wgautomesh.settings = {
126 gossip_secret_file = mkIf cfg.enableGossipEncryption "$CREDENTIALS_DIRECTORY/gossip_secret";
127 persist_file = mkIf cfg.enablePersistence "/var/lib/wgautomesh/state";
128 };
129
130 systemd.services.wgautomesh = {
131 path = [ pkgs.wireguard-tools ];
132 environment = { RUST_LOG = "wgautomesh=${cfg.logLevel}"; };
133 description = "wgautomesh";
134 serviceConfig = {
135 Type = "simple";
136
137 ExecStart = "${getExe pkgs.wgautomesh} ${runtimeConfigFile}";
138 Restart = "always";
139 RestartSec = "30";
140 LoadCredential = mkIf cfg.enableGossipEncryption [ "gossip_secret:${cfg.gossipSecretFile}" ];
141
142 ExecStartPre = mkIf cfg.enableGossipEncryption [
143 ''${pkgs.envsubst}/bin/envsubst \
144 -i ${configFile} \
145 -o ${runtimeConfigFile}''
146 ];
147
148 DynamicUser = true;
149 StateDirectory = "wgautomesh";
150 StateDirectoryMode = "0700";
151 RuntimeDirectory = "wgautomesh";
152 AmbientCapabilities = "CAP_NET_ADMIN";
153 CapabilityBoundingSet = "CAP_NET_ADMIN";
154 };
155 wantedBy = [ "multi-user.target" ];
156 };
157 networking.firewall.allowedUDPPorts =
158 mkIf cfg.openFirewall [ cfg.settings.gossip_port ];
159 };
160}
161