1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.zerotierone;
12
13 settingsFormat = pkgs.formats.json { };
14 localConfFile = settingsFormat.generate "zt-local.conf" cfg.localConf;
15 localConfFilePath = "/var/lib/zerotier-one/local.conf";
16in
17{
18 options.services.zerotierone.enable = mkEnableOption "ZeroTierOne";
19
20 options.services.zerotierone.joinNetworks = mkOption {
21 default = [ ];
22 example = [ "a8a2c3c10c1a68de" ];
23 type = types.listOf types.str;
24 description = ''
25 List of ZeroTier Network IDs to join on startup.
26 Note that networks are only ever joined, but not automatically left after removing them from the list.
27 To remove networks, use the ZeroTier CLI: `zerotier-cli leave <network-id>`
28 '';
29 };
30
31 options.services.zerotierone.port = mkOption {
32 default = 9993;
33 type = types.port;
34 description = ''
35 Network port used by ZeroTier.
36 '';
37 };
38
39 options.services.zerotierone.package = mkPackageOption pkgs "zerotierone" { };
40
41 options.services.zerotierone.localConf = mkOption {
42 default = { };
43 description = ''
44 Optional configuration to be written to the Zerotier JSON-based local.conf.
45 If set, the configuration will be symlinked to `/var/lib/zerotier-one/local.conf` at build time.
46 To understand the configuration format, refer to <https://docs.zerotier.com/config/#local-configuration-options>.
47 '';
48 example = {
49 settings.allowTcpFallbackRelay = false;
50 };
51 type = settingsFormat.type;
52 };
53
54 config = mkIf cfg.enable {
55 systemd.services.zerotierone = {
56 description = "ZeroTierOne";
57
58 wantedBy = [ "multi-user.target" ];
59 after = [ "network.target" ];
60 wants = [ "network-online.target" ];
61
62 path = [ cfg.package ];
63
64 preStart = ''
65 mkdir -p /var/lib/zerotier-one/networks.d
66 chmod 700 /var/lib/zerotier-one
67 chown -R root:root /var/lib/zerotier-one
68
69 # cleans up old symlinks also if we unset localConf
70 if [[ -L "${localConfFilePath}" && "$(readlink "${localConfFilePath}")" =~ ^${builtins.storeDir}.* ]]; then
71 rm ${localConfFilePath}
72 fi
73 ''
74 + (concatMapStrings (netId: ''
75 touch "/var/lib/zerotier-one/networks.d/${netId}.conf"
76 '') cfg.joinNetworks)
77 + lib.optionalString (cfg.localConf != { }) ''
78 # in case the user has applied manual changes to the local.conf, we backup the file
79 if [ -f "${localConfFilePath}" ]; then
80 mv ${localConfFilePath} ${localConfFilePath}.bak
81 fi
82 ln -s ${localConfFile} ${localConfFilePath}
83 '';
84
85 serviceConfig = {
86 ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}";
87 Restart = "always";
88 KillMode = "process";
89 TimeoutStopSec = 5;
90 };
91 };
92
93 # ZeroTier does not issue DHCP leases, but some strangers might...
94 networking.dhcpcd.denyInterfaces = [ "zt*" ];
95
96 # ZeroTier receives UDP transmissions
97 networking.firewall.allowedUDPPorts = [ cfg.port ];
98
99 environment.systemPackages = [ cfg.package ];
100
101 # Prevent systemd from potentially changing the MAC address
102 systemd.network.links."50-zerotier" = {
103 matchConfig = {
104 OriginalName = "zt*";
105 };
106 linkConfig = {
107 AutoNegotiation = false;
108 MACAddressPolicy = "none";
109 };
110 };
111 };
112}