router
Turns a machine with several NICs into a site gateway: PPPoE WAN (ISP credentials via vars prompts), a VLAN-filtering bridge over the LAN ports with one L3 interface per VLAN, Kea DHCP and Blocky DNS per VLAN, nftables firewall/NAT, DHCPv6-PD, CrowdSec, an iperf3 server and a WAN speed-test timer. Optional: the TP-Link Omada controller (podman) and an internal Caddy reverse proxy with a real wildcard certificate (ACME DNS-01).
Addressing convention: a site owns 10.<siteId>.0.0/16; VLAN <id> defaults
to 10.<siteId>.<id>.0/24, router at .1, DHCP pool .100-.199. The mgmt
and lan VLANs are mandatory. Trust model: mgmt reaches everything; other
VLANs get router DNS/DHCP and (with allowWan) the internet, no inter-VLAN;
WAN nothing inbound; the admin mesh (mesh.subnet) gets SSH, metrics, iperf3
and the Omada UI.
Usage from another clan
# flake.nix
inputs.cnx-network.url = "git+https://<host>/B4L/cnx-network-clan";
# clan.nix
inventory.instances.router = {
module = { name = "router"; input = "cnx-network"; };
roles.default.settings.mesh.subnet = "fd..::/88"; # your admin overlay
roles.default.machines.gw-1.settings = {
site = "ams";
siteId = 1;
wan.interface = "enp1s0";
wan.vlanId = 10; # or null for untagged PPPoE
trunkPorts = [ "enp2s0" ];
accessPorts.enp4s0 = "mgmt"; # untagged on-site recovery port
vlans = {
mgmt.id = 10;
lan.id = 20;
iot = { id = 40; allowWan = false; };
};
};
};
Then clan vars generate gw-1 prompts for the PPPoE username/password.
Internal proxy
proxy.enable serves <name>.<site><siteId>.<proxy.domain> under a wildcard
certificate obtained via RFC 2136 DNS-01 against proxy.acme.nameserver. The
gateway signs updates with TSIG key acme_<hostname with _>, whose secret is
the shared dns-acme-<hostname>-secret generator declared by this service.
The nameserver machine must declare the same generator so both sides hold one
secret — import acme-secret.nix from this directory with the gateway's name:
imports = [ (import "${inputs.cnx-network}/modules/clan/router/acme-secret.nix" "gw-1") ];
and load the key with an acl scoped to _acme-challenge.<site><siteId>.
The service does not open the WAN to anything; reach gateways over your mesh. One instance per machine.