1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9let
10 cfg = config.services.easytier;
11 settingsFormat = pkgs.formats.toml { };
12
13 genFinalSettings =
14 inst:
15 attrsets.filterAttrsRecursive (_: v: v != { }) (
16 attrsets.filterAttrsRecursive (_: v: v != null) (
17 {
18 inherit (inst.settings)
19 instance_name
20 hostname
21 ipv4
22 dhcp
23 listeners
24 ;
25 network_identity = {
26 inherit (inst.settings) network_name network_secret;
27 };
28 peer = map (p: { uri = p; }) inst.settings.peers;
29 }
30 // inst.extraSettings
31 )
32 );
33
34 genConfigFile =
35 name: inst:
36 if inst.configFile == null then
37 settingsFormat.generate "easytier-${name}.toml" (genFinalSettings inst)
38 else
39 inst.configFile;
40
41 activeInsts = filterAttrs (_: inst: inst.enable) cfg.instances;
42
43 settingsModule = name: {
44 options = {
45 instance_name = mkOption {
46 type = types.str;
47 default = name;
48 description = "Identify different instances on same host";
49 };
50
51 hostname = mkOption {
52 type = with types; nullOr str;
53 default = null;
54 description = "Hostname shown in peer list and web console.";
55 };
56
57 network_name = mkOption {
58 type = with types; nullOr str;
59 default = null;
60 description = "EasyTier network name.";
61 };
62
63 network_secret = mkOption {
64 type = with types; nullOr str;
65 default = null;
66 description = ''
67 EasyTier network credential used for verification and
68 encryption. It can also be set in environmentFile.
69 '';
70 };
71
72 ipv4 = mkOption {
73 type = with types; nullOr str;
74 default = null;
75 description = ''
76 IPv4 cidr address of this peer in the virtual network. If
77 empty, this peer will only forward packets and no TUN device
78 will be created.
79 '';
80 example = "10.144.144.1/24";
81 };
82
83 dhcp = mkOption {
84 type = types.bool;
85 default = false;
86 description = ''
87 Automatically determine the IPv4 address of this peer based on
88 existing peers on network.
89 '';
90 };
91
92 listeners = mkOption {
93 type = with types; listOf str;
94 default = [
95 "tcp://0.0.0.0:11010"
96 "udp://0.0.0.0:11010"
97 ];
98 description = ''
99 Listener addresses to accept connections from other peers.
100 Valid format is: `<proto>://<addr>:<port>`, where the protocol
101 can be `tcp`, `udp`, `ring`, `wg`, `ws`, `wss`.
102 '';
103 };
104
105 peers = mkOption {
106 type = with types; listOf str;
107 default = [ ];
108 description = ''
109 Peers to connect initially. Valid format is: `<proto>://<addr>:<port>`.
110 '';
111 example = [
112 "tcp://example.com:11010"
113 ];
114 };
115 };
116 };
117
118 instanceModule =
119 { name, ... }:
120 {
121 options = {
122 enable = mkOption {
123 type = types.bool;
124 default = true;
125 description = "Enable the instance.";
126 };
127
128 configServer = mkOption {
129 type = with types; nullOr str;
130 default = null;
131 description = ''
132 Configure the instance from config server. When this option
133 set, any other settings for configuring the instance manually
134 except `hostname` will be ignored. Valid formats are:
135
136 - full uri for custom server: `udp://example.com:22020/<token>`
137 - username only for official server: `<token>`
138 '';
139 example = "udp://example.com:22020/myusername";
140 };
141
142 configFile = mkOption {
143 type = with types; nullOr path;
144 default = null;
145 description = ''
146 Path to easytier config file. Setting this option will
147 override `settings` and `extraSettings` of this instance.
148 '';
149 };
150
151 environmentFiles = mkOption {
152 type = with types; listOf path;
153 default = [ ];
154 description = ''
155 Environment files for this instance. All command-line args
156 have corresponding environment variables.
157 '';
158 example = literalExpression ''
159 [
160 /path/to/.env
161 /path/to/.env.secret
162 ]
163 '';
164 };
165
166 settings = mkOption {
167 type = types.submodule (settingsModule name);
168 default = { };
169 description = ''
170 Settings to generate {file}`easytier-${name}.toml`
171 '';
172 };
173
174 extraSettings = mkOption {
175 type = settingsFormat.type;
176 default = { };
177 description = ''
178 Extra settings to add to {file}`easytier-${name}.toml`.
179 '';
180 };
181
182 extraArgs = mkOption {
183 type = with types; listOf str;
184 default = [ ];
185 description = ''
186 Extra args append to the easytier command-line.
187 '';
188 };
189 };
190 };
191
192in
193{
194 options.services.easytier = {
195 enable = mkEnableOption "EasyTier daemon";
196
197 package = mkPackageOption pkgs "easytier" { };
198
199 allowSystemForward = mkEnableOption ''
200 Allow the system to forward packets from easytier. Useful when
201 `proxy_forward_by_system` enabled.
202 '';
203
204 instances = mkOption {
205 description = ''
206 EasyTier instances.
207 '';
208 type = types.attrsOf (types.submodule instanceModule);
209 default = { };
210 example = {
211 settings = {
212 network_name = "easytier";
213 network_secret = "easytier";
214 ipv4 = "10.144.144.1/24";
215 peers = [
216 "tcp://public.easytier.cn:11010"
217 "wss://example.com:443"
218 ];
219 };
220 extraSettings = {
221 flags.dev_name = "tun1";
222 };
223 };
224 };
225 };
226
227 config = mkIf cfg.enable {
228 environment.systemPackages = [ cfg.package ];
229
230 systemd.services = mapAttrs' (
231 name: inst:
232 let
233 configFile = genConfigFile name inst;
234 in
235 nameValuePair "easytier-${name}" {
236 description = "EasyTier Daemon - ${name}";
237 wants = [
238 "network-online.target"
239 "nss-lookup.target"
240 ];
241 after = [
242 "network-online.target"
243 "nss-lookup.target"
244 ];
245 wantedBy = [ "multi-user.target" ];
246 path = with pkgs; [
247 cfg.package
248 iproute2
249 bash
250 ];
251 restartTriggers = inst.environmentFiles ++ (optionals (inst.configServer == null) [ configFile ]);
252 serviceConfig = {
253 Type = "simple";
254 Restart = "on-failure";
255 EnvironmentFile = inst.environmentFiles;
256 StateDirectory = "easytier/easytier-${name}";
257 StateDirectoryMode = "0700";
258 WorkingDirectory = "/var/lib/easytier/easytier-${name}";
259 ExecStart = escapeShellArgs (
260 [
261 "${cfg.package}/bin/easytier-core"
262 ]
263 ++ optionals (inst.configServer != null) (
264 [
265 "-w"
266 "${inst.configServer}"
267 ]
268 ++ (optionals (inst.settings.hostname != null) [
269 "--hostname"
270 "${inst.settings.hostname}"
271 ])
272 )
273 ++ optionals (inst.configServer == null) [
274 "-c"
275 "${configFile}"
276 ]
277 ++ inst.extraArgs
278 );
279 };
280 }
281 ) activeInsts;
282
283 boot.kernel.sysctl = mkIf cfg.allowSystemForward {
284 "net.ipv4.conf.all.forwarding" = mkOverride 97 true;
285 "net.ipv6.conf.all.forwarding" = mkOverride 97 true;
286 };
287 };
288
289 meta.maintainers = with maintainers; [
290 ltrump
291 ];
292}