1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.dsnet;
10 settingsFormat = pkgs.formats.json { };
11 patchFile = settingsFormat.generate "dsnet-patch.json" cfg.settings;
12in
13{
14 options.services.dsnet = {
15 enable = lib.mkEnableOption "dsnet, a centralised Wireguard VPN manager";
16
17 package = lib.mkPackageOption pkgs "dsnet" { };
18
19 settings = lib.mkOption {
20 type = lib.types.submodule {
21
22 freeformType = settingsFormat.type;
23
24 options = {
25 ExternalHostname = lib.mkOption {
26 type = lib.types.nullOr lib.types.str;
27 default = null;
28 example = "vpn.example.com";
29 description = ''
30 The hostname that clients should use to connect to this server.
31 This is used to generate the client configuration files.
32
33 This is preferred over ExternalIP, as it allows for IPv4 and
34 IPv6, as well as enabling the ability tp change IP.
35 '';
36 };
37
38 ExternalIP = lib.mkOption {
39 type = lib.types.nullOr lib.types.str;
40 default = null;
41 example = "192.0.2.1";
42 description = ''
43 The external IP address of the server. This is used to generate
44 the client configuration files for when an ExternalHostname is not set.
45
46 Leaving this empty will cause dsnet to use the IP address of
47 what looks like the WAN interface.
48 '';
49 };
50
51 ExternalIP6 = lib.mkOption {
52 type = lib.types.nullOr lib.types.str;
53 default = null;
54 example = "2001:db8::1";
55 description = ''
56 The external IPv6 address of the server. This is used to generate
57 the client configuration files for when an ExternalHostname is
58 not set. Used in preference to ExternalIP.
59
60 Leaving this empty will cause dsnet to use the IP address of
61 what looks like the WAN interface.
62 '';
63 };
64
65 Network = lib.mkOption {
66 type = lib.types.nullOr lib.types.str;
67 default = null;
68 example = "172.18.0.0/24";
69 description = ''
70 The IPv4 network that the server will use to allocate IPs on the network.
71 Leave this empty to let dsnet choose a network.
72 '';
73 };
74
75 Network6 = lib.mkOption {
76 type = lib.types.nullOr lib.types.str;
77 default = null;
78 example = "2001:db8::1/64";
79 description = ''
80 The IPv6 network that the server will use to allocate IPs on the
81 network.
82 Leave this empty to let dsnet choose a network.
83 '';
84 };
85
86 IP = lib.mkOption {
87 type = lib.types.nullOr lib.types.str;
88 default = null;
89 example = "172.18.0.1";
90 description = ''
91 The IPv4 address that the server will use on the network.
92 Leave this empty to let dsnet choose an address.
93 '';
94 };
95
96 IP6 = lib.mkOption {
97 type = lib.types.nullOr lib.types.str;
98 default = null;
99 example = "2001:db8::1";
100 description = ''
101 The IPv6 address that the server will use on the network
102 Leave this empty to let dsnet choose an address.
103 '';
104 };
105
106 Networks = lib.mkOption {
107 type = lib.types.nullOr (lib.types.listOf lib.types.str);
108 default = null;
109 example = [
110 "0.0.0.0/0"
111 "192.168.0.0/24"
112 ];
113 description = ''
114 The CIDR networks that should route through this server. Clients
115 will be configured to route traffic for these networks through
116 the server peer.
117 '';
118 };
119 };
120 };
121
122 default = { };
123 description = ''
124 The settings to use for dsnet. This will be converted to a JSON
125 object that will be passed to dsnet as a patch, using the patch
126 command when the service is started. See the dsnet documentation for
127 more information on the additional options.
128
129 Note that the resulting /etc/dsnetconfg.json is more of a database
130 than it is a configuration file. It is therefore recommended that
131 system specific values are configured here, rather than the full
132 configuration including peers.
133
134 Peers may be managed via the dsnet add/remove commands, negating the
135 need to manage key material and cumbersom configuration with nix. If
136 you want peer configuration in nix, you may as well use the regular
137 wireguard module.
138 '';
139 example = {
140 ExternalHostname = "vpn.example.com";
141 ExternalIP = "127.0.0.1";
142 ExternalIP6 = "";
143 ListenPort = 51820;
144 Network = "10.3.148.0/22";
145 Network6 = "";
146 IP = "10.3.148.1";
147 IP6 = "";
148 DNS = "8.8.8.8";
149 Networks = [ "0.0.0.0/0" ];
150 };
151 };
152 };
153
154 config = lib.mkIf cfg.enable {
155 environment.systemPackages = [ cfg.package ];
156
157 systemd.services.dsnet = {
158 description = "dsnet VPN Management";
159 after = [ "network-online.target" ];
160 wants = [ "network-online.target" ];
161 wantedBy = [ "multi-user.target" ];
162 preStart = ''
163 test ! -f /etc/dsnetconfig.json && ${lib.getExe cfg.package} init
164 ${lib.getExe cfg.package} patch < ${patchFile}
165 '';
166 serviceConfig = {
167 ExecStart = "${lib.getExe cfg.package} up";
168 ExecStop = "${lib.getExe cfg.package} down";
169 Type = "oneshot";
170 # consider the service to be active after process exits, so it can be
171 # reloaded
172 RemainAfterExit = true;
173 };
174
175 reload = ''
176 ${lib.getExe cfg.package} patch < ${patchFile}
177 ${lib.getExe cfg.package} sync < ${patchFile}
178 '';
179
180 # reload _instead_ of restarting on change
181 reloadIfChanged = true;
182 };
183 };
184}