Network kits
The library ships kits for networks: a VPN with a hub, a virtual network with moons, and DNS records. A kit is a module, so a fleet writes only assemblies. See Kits batteries for kits, assemblies and roles. This is the fleet’s own words, and its assemblies:
# Network kits shipped with yen: write only assemblies. `root` is where public keys and ids are read from.{ yen, lib, inputs, ...}:let inherit (yen) kit machine; inherit (yen.lib) same; root = ../vars/per-machine;in{ imports = [ (inputs.yen.lib.kits.wireguard { inherit root; }) # (1) (inputs.yen.lib.kits.zerotier { inherit root; }) inputs.yen.lib.kits.dns ]; yen.machine = { lb.addr = "10.0.1.1"; web.addr = "10.0.1.10"; db.addr = "10.0.1.20"; };
# wireguard: lb is the hub, web and db are peers; addresses come from the prefix, in member order yen.assembly.vpn = { # (2) kit = kit.wireguard; roles = { controller = { at = machine.lb; settings.prefix = "10.8.0.0/24"; }; peer = { at = m: same m machine.web || same m machine.db; # (3) settings = { prefix = "10.8.0.0/24"; port = 51821; }; }; }; }; # zerotier: one controller, up to four moons, any machine as a peer yen.assembly.lan = { # (4) kit = kit.zerotier; roles = { controller.at = machine.lb; moon.at = machine.web; peer.at = machine.db; }; };
# a kit that gives endpoints; dns reads them yen.kit.site = { # (5) roles.default = { }; exports = [ "endpoints" ]; }; yen.assembly.site = { kit = kit.site; roles.default.at = machine.web; }; yen.aspects.site = { # (6) at = yen.lib.all [ yen.kinds.member (s: same (s.kit or null) kit.site) ]; exports = { endpoints = [ "www.example.org" ]; }; };}(1)imports a kit that reads public values. It takes{ root; }, the directory of the vars. The kit is a value in yen, not a file in your repo, so it cannot find your vars by a relative path.(2)is a wireguard assembly:lbis the hub, andwebanddbare peers. Addresses come from the prefix, in member order.(3)is a role taken by a predicate, not by a name.(4)is a zerotier assembly: one controller, one moon, and any other machine as a peer.(5)is a kit of the fleet that gives endpoints.(6)is the aspect that gives them. The kitdnsreads them.
Each wireguard member has an address in member order, and a peer sees only the controller:
{ db = { environment = { etc = { "zerotier/moons" = { text = "ztmoon1"; }; }; }; networking = { wireguard = { interfaces = { vpn = { ips = [ "10.8.0.1/32" ]; peers = [ { allowedIPs = [ "10.8.0.0/24" ]; endpoint = "10.0.1.1:51821"; publicKey = "wgLB"; } ]; privateKeyFile = "/run/secrets/vars/from-exports/wg-vpn.key"; }; }; }; }; services = { zerotierone = { enable = true; joinNetworks = [ "ztnet" ]; }; }; }; lb = { environment = { etc = { "zerotier/moons" = { text = "ztmoon1"; }; }; }; networking = { wireguard = { interfaces = { vpn = { ips = [ "10.8.0.2/32" ]; listenPort = 51820; peers = [ { allowedIPs = [ "10.8.0.1/32" ]; publicKey = "wgDB"; } { allowedIPs = [ "10.8.0.3/32" ]; publicKey = "wgWEB"; } ]; privateKeyFile = "/run/secrets/vars/from-exports/wg-vpn.key"; }; }; }; }; services = { zerotierone = { enable = true; joinNetworks = [ "ztnet" ]; }; }; systemd = { tmpfiles = { rules = [ "L+ /var/lib/zerotier-one/identity.secret - - - - /run/secrets/vars/from-exports/zt-lan.identity" ]; }; }; }; web = { environment = { etc = { "zerotier/moons" = { text = "ztmoon1"; }; }; }; networking = { wireguard = { interfaces = { vpn = { ips = [ "10.8.0.3/32" ]; peers = [ { allowedIPs = [ "10.8.0.0/24" ]; endpoint = "10.0.1.1:51821"; publicKey = "wgLB"; } ]; privateKeyFile = "/run/secrets/vars/from-exports/wg-vpn.key"; }; }; }; }; services = { zerotierone = { enable = true; joinNetworks = [ "ztnet" ]; }; }; systemd = { tmpfiles = { rules = [ "L+ /var/lib/zerotier-one/identity.secret - - - - /run/secrets/vars/from-exports/zt-lan-web.identity" ]; }; }; };}dns reads the endpoints that site exports:
[ { name = "www.example.org"; target = "web"; }]The kits
Section titled “The kits”| Kit | Roles | Interface | Does |
|---|---|---|---|
wireguard { root; } |
controller (exactly 1), peer |
prefix, port (51820) |
an address per member; keys by generators; the private key is a path, never in the store; a peer talks to the controller |
zerotier { root; } |
controller (1), moon (0 to 4), peer |
none | the controller’s network id and the moons’ ids are public generator files; peers join and list the moons |
dns |
default |
none | a record for every endpoints any kit exports |
Battery addresses gives each member an address of a prefix, in member order. A repeated address, a prefix that is no
IPv4 /16 to /24 and a prefix too small are error records.
The contract
Section titled “The contract”A wireguard or zerotier network needs exactly one controller, zerotier allows four moons at most, and a prefix must hold its members.
# zerotier allows four moons: five machines make five{ yen, ... }:{ yen.machine = { m1.addr = "10.0.2.1"; m2.addr = "10.0.2.2"; m3.addr = "10.0.2.3"; m4.addr = "10.0.2.4"; m5.addr = "10.0.2.5"; }; yen.assembly.crowd = { kit = yen.kit.zerotier; roles = { controller.at = yen.machine.lb; moon.at = yen.kinds.machine; }; };}[ "merge conflict at environment.etc.zerotier/moons.text" "merge conflict at services.zerotierone.joinNetworks" "merge conflict at systemd.tmpfiles.rules" "merge conflict at environment.etc.zerotier/moons.text" "merge conflict at services.zerotierone.joinNetworks" "merge conflict at systemd.tmpfiles.rules" "merge conflict at environment.etc.zerotier/moons.text" "merge conflict at services.zerotierone.joinNetworks" "merge conflict at systemd.tmpfiles.rules" "zerotier: db has no zt-crowd-db.moon public file" "zerotier: lb has no zt-crowd.nwid public file" "zerotier: lb has no zt-crowd-lb.moon public file" "zerotier: m1 has no zt-crowd-m1.moon public file" "zerotier: m2 has no zt-crowd-m2.moon public file" "zerotier: m3 has no zt-crowd-m3.moon public file" "zerotier: m4 has no zt-crowd-m4.moon public file" "zerotier: m5 has no zt-crowd-m5.moon public file" "zerotier: web has no zt-crowd-web.moon public file" "kits: assembly crowd role moon: 8 machines, at most 4"][ "kits: assembly headless role controller: 0 machines, at least 1"][ "merge conflict at environment.etc.zerotier/moons.text" "merge conflict at services.zerotierone.joinNetworks" "merge conflict at environment.etc.zerotier/moons.text" "merge conflict at services.zerotierone.joinNetworks" "merge conflict at systemd.tmpfiles.rules" "merge conflict at environment.etc.zerotier/moons.text" "merge conflict at services.zerotierone.joinNetworks" "merge conflict at systemd.tmpfiles.rules" "zerotier: db has no zt-twin.nwid public file" "zerotier: lb has no zt-twin.nwid public file" "zerotier: web has no zt-twin.nwid public file" "kits: assembly twin role controller: 3 machines, at most 1"][ "addresses: 10.8.0.0/30 is not an IPv4 prefix /16 to /24" "wireguard: lb has no wg-tiny.pub public key"]The members of one wireguard assembly must name the same prefix, and every member needs its public file in
vars. A missing file is an error record, never a text in the configuration:
# the members of one assembly name different prefixes{ yen, ... }:{ yen.assembly.split = { kit = yen.kit.wireguard; roles = { controller = { at = yen.machine.lb; settings.prefix = "10.9.0.0/24"; }; peer = { at = yen.machine.web; settings.prefix = "10.9.1.0/24"; }; }; };}[ "wireguard: assembly split members disagree on prefix" "wireguard: lb has no wg-split.pub public key" "wireguard: web has no wg-split.pub public key"]# a public key file that is not in vars is an error record, never the text "absent"{ yen, ... }:{ yen.assembly.lost = { kit = yen.kit.wireguard; roles.controller = { at = yen.machine.lb; settings.prefix = "10.9.0.0/24"; }; };}[ "wireguard: lb has no wg-lost.pub public key"]