Merge pull request 'zerotier: Migrate from clanModule to clanServices' (#3820) from kenji/clan-core:ke-migrate-clan-module-zerotier into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3820
This commit is contained in:
@@ -10,5 +10,6 @@
|
|||||||
./hello-world/flake-module.nix
|
./hello-world/flake-module.nix
|
||||||
./wifi/flake-module.nix
|
./wifi/flake-module.nix
|
||||||
./borgbackup/flake-module.nix
|
./borgbackup/flake-module.nix
|
||||||
|
./zerotier/flake-module.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
33
clanServices/zerotier/README.md
Normal file
33
clanServices/zerotier/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide explains how to set up and manage a [ZeroTier VPN](https://zerotier.com) for a clan network. Each VPN requires a single controller and can support multiple peers and optional moons for better connectivity.
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
### 1. Controller
|
||||||
|
|
||||||
|
The [Controller](https://docs.zerotier.com/controller/) manages network membership and is responsible for admitting new peers.
|
||||||
|
When a new node is added to the clan, the controller must be updated to ensure it has the latest member list.
|
||||||
|
|
||||||
|
- **Key Points:**
|
||||||
|
- Must be online to admit new machines to the VPN.
|
||||||
|
- Existing nodes can continue to communicate even when the controller is offline.
|
||||||
|
|
||||||
|
### 2. Moons
|
||||||
|
|
||||||
|
[Moons](https://docs.zerotier.com/roots) act as relay nodes,
|
||||||
|
providing direct connectivity to peers via their public IP addresses.
|
||||||
|
They enable devices that are not publicly reachable to join the VPN by routing through these nodes.
|
||||||
|
|
||||||
|
- **Configuration Notes:**
|
||||||
|
- Each moon must define its public IP address.
|
||||||
|
- Ensures connectivity for devices behind NAT or restrictive firewalls.
|
||||||
|
|
||||||
|
### 3. Peers
|
||||||
|
|
||||||
|
Peers are standard nodes in the VPN.
|
||||||
|
They connect to other peers, moons, and the controller as needed.
|
||||||
|
|
||||||
|
- **Purpose:**
|
||||||
|
- General role for all machines that are neither controllers nor moons.
|
||||||
|
- Ideal for most clan members devices.
|
||||||
151
clanServices/zerotier/default.nix
Normal file
151
clanServices/zerotier/default.nix
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest.name = "clan-core/zerotier";
|
||||||
|
manifest.description = "Configuration of the secure and efficient Zerotier VPN";
|
||||||
|
manifest.categories = [ "Utility" ];
|
||||||
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
|
roles.peer = {
|
||||||
|
perInstance =
|
||||||
|
{ instanceName, roles, ... }:
|
||||||
|
{
|
||||||
|
nixosModule =
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(import ./shared.nix {
|
||||||
|
inherit
|
||||||
|
instanceName
|
||||||
|
roles
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
roles.moon = {
|
||||||
|
interface =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
options.zerotier.moon.stableEndpoints = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Make this machine a moon.
|
||||||
|
Other machines can join this moon by adding this moon in their config.
|
||||||
|
It will be reachable under the given stable endpoints.
|
||||||
|
'';
|
||||||
|
example = ''
|
||||||
|
[ 1.2.3.4" "10.0.0.3/9993" "2001:abcd:abcd::3/9993" ]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
perInstance =
|
||||||
|
{ instanceName, roles, ... }:
|
||||||
|
{
|
||||||
|
nixosModule =
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
settings,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config.clan.core.networking.zerotier.moon.stableEndpoints = settings.zerotier.moon.stableEndpoints;
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
(import ./shared.nix {
|
||||||
|
inherit
|
||||||
|
instanceName
|
||||||
|
roles
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
roles.controller = {
|
||||||
|
perInstance =
|
||||||
|
{
|
||||||
|
instanceName,
|
||||||
|
roles,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
nixosModule =
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(import ./shared.nix {
|
||||||
|
inherit
|
||||||
|
instanceName
|
||||||
|
roles
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
config = {
|
||||||
|
systemd.services.zerotier-inventory-autoaccept =
|
||||||
|
let
|
||||||
|
machines = uniqueStrings (
|
||||||
|
(lib.attrNames roles.moon.machines)
|
||||||
|
++ (lib.attrNames roles.controller.machines)
|
||||||
|
++ (lib.attrNames roles.peer.machines)
|
||||||
|
);
|
||||||
|
networkIps = builtins.foldl' (
|
||||||
|
ips: name:
|
||||||
|
if
|
||||||
|
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
|
||||||
|
then
|
||||||
|
ips
|
||||||
|
++ [
|
||||||
|
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
|
||||||
|
]
|
||||||
|
else
|
||||||
|
ips
|
||||||
|
) [ ] machines;
|
||||||
|
allHostIPs = networkIps;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "zerotierone.service" ];
|
||||||
|
path = [ config.clan.core.clanPkgs.zerotierone ];
|
||||||
|
serviceConfig.ExecStart = pkgs.writeShellScript "zerotier-inventory-autoaccept" ''
|
||||||
|
${lib.concatMapStringsSep "\n" (host: ''
|
||||||
|
${config.clan.core.clanPkgs.zerotier-members}/bin/zerotier-members allow --member-ip ${host}
|
||||||
|
'') allHostIPs}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.networking.zerotier.controller.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
9
clanServices/zerotier/flake-module.nix
Normal file
9
clanServices/zerotier/flake-module.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
clan.modules = {
|
||||||
|
zerotier = lib.modules.importApply ./default.nix { };
|
||||||
|
};
|
||||||
|
}
|
||||||
72
clanServices/zerotier/shared.nix
Normal file
72
clanServices/zerotier/shared.nix
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
roles,
|
||||||
|
instanceName,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
controllerMachine = builtins.head (lib.attrNames roles.controller.machines or { });
|
||||||
|
networkIdPath = "${config.clan.core.settings.directory}/vars/per-machine/${controllerMachine}/zerotier/zerotier-network-id/value";
|
||||||
|
networkId =
|
||||||
|
if builtins.pathExists networkIdPath then
|
||||||
|
builtins.readFile networkIdPath
|
||||||
|
else
|
||||||
|
builtins.throw ''
|
||||||
|
No zerotier network id found for ${controllerMachine}.
|
||||||
|
Please run `clan vars generate ${controllerMachine}` first.
|
||||||
|
'';
|
||||||
|
moons = lib.attrNames (roles.moon.machines or { });
|
||||||
|
moonIps = builtins.foldl' (
|
||||||
|
ips: name:
|
||||||
|
if
|
||||||
|
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
|
||||||
|
then
|
||||||
|
ips
|
||||||
|
++ [
|
||||||
|
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
|
||||||
|
]
|
||||||
|
else
|
||||||
|
ips
|
||||||
|
) [ ] moons;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
clan.core.networking.zerotier.networkId = networkId;
|
||||||
|
clan.core.networking.zerotier.name = instanceName;
|
||||||
|
|
||||||
|
systemd.services.zerotierone.serviceConfig.ExecStartPost = lib.mkIf (moonIps != [ ]) (
|
||||||
|
lib.mkAfter [
|
||||||
|
"+${pkgs.writeScript "orbit-moons-by-ip" ''
|
||||||
|
#!${pkgs.python3.interpreter}
|
||||||
|
import json
|
||||||
|
import ipaddress
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def compute_member_id(ipv6_addr: str) -> str:
|
||||||
|
addr = ipaddress.IPv6Address(ipv6_addr)
|
||||||
|
addr_bytes = bytearray(addr.packed)
|
||||||
|
|
||||||
|
# Extract the bytes corresponding to the member_id (node_id)
|
||||||
|
node_id_bytes = addr_bytes[10:16]
|
||||||
|
node_id = int.from_bytes(node_id_bytes, byteorder="big")
|
||||||
|
|
||||||
|
member_id = format(node_id, "x").zfill(10)[-10:]
|
||||||
|
|
||||||
|
return member_id
|
||||||
|
def main() -> None:
|
||||||
|
ips = json.loads(${builtins.toJSON (builtins.toJSON moonIps)})
|
||||||
|
for ip in ips:
|
||||||
|
member_id = compute_member_id(ip)
|
||||||
|
res = subprocess.run(["zerotier-cli", "orbit", member_id, member_id])
|
||||||
|
if res.returncode != 0:
|
||||||
|
print(f"Failed to add {member_id} to orbit")
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
''}"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -91,6 +91,7 @@ nav:
|
|||||||
- reference/clanServices/heisenbridge.md
|
- reference/clanServices/heisenbridge.md
|
||||||
- reference/clanServices/hello-world.md
|
- reference/clanServices/hello-world.md
|
||||||
- reference/clanServices/wifi.md
|
- reference/clanServices/wifi.md
|
||||||
|
- reference/clanServices/zerotier.md
|
||||||
- Clan Modules:
|
- Clan Modules:
|
||||||
- Overview: reference/clanModules/index.md
|
- Overview: reference/clanModules/index.md
|
||||||
- reference/clanModules/frontmatter/index.md
|
- reference/clanModules/frontmatter/index.md
|
||||||
|
|||||||
Reference in New Issue
Block a user