diff --git a/clan.nix b/clan.nix index 9e4fb2a..58764a5 100644 --- a/clan.nix +++ b/clan.nix @@ -1,10 +1,6 @@ -{ config, lib, ... }: +{ ... }: let hosts = import ./modules/hosts.nix; - mesh = import ./modules/mesh-hosts.nix { - dir = config.directory; - inherit lib; - }; # Single source of the machine list: inventory AND the per-machine baseline # below are both derived from it, so no machine can be added without the @@ -22,6 +18,9 @@ let }; in { + # Site gateways: the router service instance lives in inventory.nix. + imports = [ ./inventory.nix ]; + # Ensure this is unique among all clans you want to use. meta.name = "cnx-network-clan"; meta.domain = "cnx-network.internal"; @@ -95,85 +94,6 @@ in roles.server.machines.control = { }; roles.client.machines.ns1 = { }; }; - - # Site gateways: one router instance per site, settings = the site's - # port/VLAN layout. Everything else (PPPoE, Kea/Blocky, nftables, IPv6-PD, - # CrowdSec, iperf3, speedtest) follows from modules/clan/router. - router = { - module = { - name = "router"; - input = "self"; - }; - # Fleet-wide settings: admin paths (SSH, scrapes, iperf3, Omada UI) are - # opened to the mesh; internal proxy names live under - # .cnx.network with wildcard certs issued via DNS-01 against - # ns1 (which must declare the gateway's acme-secret.nix generator). - roles.default.settings = { - mesh.subnet = mesh.subnet; - proxy = { - domain = "cnx.network"; - acme.nameserver = hosts.ns1.ipv4; - acme.email = "postmaster@cnx.email"; - }; - }; - # Chiang Mai (site 1): Topton 1U, Intel N300, 4x i226-V 2.5G. Port roles - # use the expected igc names — verify against facter.json after the - # first install and adjust if the box enumerates differently. - roles.default.machines.gw-cnx-1.settings = { - site = "cnx"; - siteId = 1; - wan.interface = "enp1s0"; - wan.vlanId = null; # this ISP runs PPPoE untagged on the port - wan.macAddress = "a8:b8:e0:01:06:87"; - trunkPorts = [ - "enp2s0" - # "enp3s0" # STAGING: serves as the uplink until cutover (see the machine config) - ]; - # Dedicated on-site recovery port: untagged mgmt, always available even - # if the switch config is broken. - accessPorts.enp4s0 = "mgmt"; - # Replaces the newedge.house OPNsense box; renumbered to the fleet - # convention (10.1..0/24, router .1, pool .100-.199). The old - # untagged LAN becomes tagged mgmt — infra switch ports get PVID 10. - vlans = { - mgmt = { - id = 10; # 10.1.10.0/24 — servers, APs, switches, Omada, admin - dhcp.reservations.storinator01 = { - hwAddress = "7c:c2:55:e0:d6:40"; - ipAddress = "10.1.10.53"; - }; - }; - lan.id = 20; # 10.1.20.0/24 — trusted clients - iot.id = 40; # 10.1.40.0/24 - voip.id = 50; # 10.1.50.0/24 - dmz.id = 60; # 10.1.60.0/24 - unit1.id = 110; # 10.1.110.0/24 - unit2.id = 120; # 10.1.120.0/24 - unit3.id = 130; # 10.1.130.0/24 - unit4.id = 140; # 10.1.140.0/24 - unit5 = { - id = 150; # 10.1.150.0/24 - dhcp.reservations.newt = { - hwAddress = "7c:d3:0a:21:58:0b"; - ipAddress = "10.1.150.22"; - }; - }; - }; - # This site runs the Omada controller for its APs/switches. - omada.enable = true; - - # Internal reverse proxy: real wildcard cert *.cnx1.cnx.network; Blocky - # resolves the names to the router's LAN address for mgmt+lan clients. - proxy = { - enable = true; - services.omada = { - # Omada's UI is HTTPS with a self-signed cert on the host network. - backend = "https://127.0.0.1:8043"; - insecureSkipVerify = true; - }; - }; - }; - }; }; # Fleet-wide baseline applied to every machine. Secrets minted by diff --git a/docs/src/gateways.md b/docs/src/gateways.md index 1568c83..6bfd885 100644 --- a/docs/src/gateways.md +++ b/docs/src/gateways.md @@ -5,8 +5,8 @@ NixOS routers (OPNsense replacements) on Topton 1U fanless boxes — Intel N300, Four sites are planned; the first is `gw-cnx-1` (Chiang Mai). Everything lives in the `router` clan service (`modules/clan/router/`, registered as `modules.router` in `clan.nix`); a site joins by adding its machine to the -`router` inventory instance with settings for port names, VLANs, and feature -flags (`inventory.instances.router.roles.default.machines..settings`). +`router` inventory instance in `inventory.nix` with settings for port names, +VLANs, and feature flags (`roles.default.machines..settings`). Naming: `gw--`, e.g. `gw-cnx-1`. @@ -70,7 +70,7 @@ Trust model: mgmt → everything; other VLANs → router DNS/DHCP + internet onl hostId, timezone). Fill the real disk id in `disko.nix` (from the installer: `ls -l /dev/disk/by-id/`). 2. Add the machine to `inventory.machines` in `clan.nix`, to the `router` - instance (`roles.default.machines.gw--.settings`: `site`, + instance in `inventory.nix` (`roles.default.machines.gw--.settings`: `site`, `siteId` (next free number), port names, VLANs, `omada.enable`; keep the `mgmt`/`lan` VLANs), and to the machine list in `modules/mesh-hosts.nix`. Do **not** add it to `modules/hosts.nix` (dynamic PPPoE IP; clan connects @@ -129,7 +129,7 @@ proxy = { PPPoE with zero inbound reachability, and browsers trust it without a CA install. Each gateway has its own TSIG key (`acme_gw__`) that ns1 scopes to `_acme-challenge.` TXT records only. The zone, ns1's - address and the ACME contact are fleet-wide role settings in `clan.nix` + address and the ACME contact are fleet-wide role settings in `inventory.nix` (`proxy.domain`, `proxy.acme.nameserver`, `proxy.acme.email`). - **Resolution**: the names exist only internally — Blocky answers `*..cnx.network` with the router's `lan` address; the public diff --git a/inventory.nix b/inventory.nix new file mode 100644 index 0000000..90d44cb --- /dev/null +++ b/inventory.nix @@ -0,0 +1,92 @@ +# Site gateways: the `router` service instance (modules/clan/router). One +# instance for the fleet; each site adds its machine with the site's port / +# VLAN layout under roles.default.machines..settings. Everything else +# (PPPoE, Kea/Blocky, nftables, IPv6-PD, CrowdSec, iperf3, speedtest) follows +# from the service. Imported by clan.nix. +{ config, lib, ... }: +let + hosts = import ./modules/hosts.nix; + mesh = import ./modules/mesh-hosts.nix { + dir = config.directory; + inherit lib; + }; +in +{ + inventory.instances.router = { + module = { + name = "router"; + input = "self"; + }; + + # Fleet-wide settings: admin paths (SSH, scrapes, iperf3, Omada UI) are + # opened to the mesh; internal proxy names live under + # .cnx.network with wildcard certs issued via DNS-01 against + # ns1 (which must declare the gateway's acme-secret.nix generator). + roles.default.settings = { + mesh.subnet = mesh.subnet; + proxy = { + domain = "cnx.network"; + acme.nameserver = hosts.ns1.ipv4; + acme.email = "postmaster@cnx.email"; + }; + }; + + # Chiang Mai (site 1): Topton 1U, Intel N300, 4x i226-V 2.5G. Port roles + # use the expected igc names — verify against facter.json after the + # first install and adjust if the box enumerates differently. + roles.default.machines.gw-cnx-1.settings = { + site = "cnx"; + siteId = 1; + wan.interface = "enp1s0"; + wan.vlanId = null; # this ISP runs PPPoE untagged on the port + wan.macAddress = "a8:b8:e0:01:06:87"; + trunkPorts = [ + "enp2s0" + # "enp3s0" # STAGING: serves as the uplink until cutover (see the machine config) + ]; + # Dedicated on-site recovery port: untagged mgmt, always available even + # if the switch config is broken. + accessPorts.enp4s0 = "mgmt"; + # Replaces the newedge.house OPNsense box; renumbered to the fleet + # convention (10.1..0/24, router .1, pool .100-.199). The old + # untagged LAN becomes tagged mgmt — infra switch ports get PVID 10. + vlans = { + mgmt = { + id = 10; # 10.1.10.0/24 — servers, APs, switches, Omada, admin + dhcp.reservations.storinator01 = { + hwAddress = "7c:c2:55:e0:d6:40"; + ipAddress = "10.1.10.53"; + }; + }; + lan.id = 20; # 10.1.20.0/24 — trusted clients + iot.id = 40; # 10.1.40.0/24 + voip.id = 50; # 10.1.50.0/24 + dmz.id = 60; # 10.1.60.0/24 + unit1.id = 110; # 10.1.110.0/24 + unit2.id = 120; # 10.1.120.0/24 + unit3.id = 130; # 10.1.130.0/24 + unit4.id = 140; # 10.1.140.0/24 + unit5 = { + id = 150; # 10.1.150.0/24 + dhcp.reservations.newt = { + hwAddress = "7c:d3:0a:21:58:0b"; + ipAddress = "10.1.150.22"; + }; + }; + }; + # This site runs the Omada controller for its APs/switches. + omada.enable = true; + + # Internal reverse proxy: real wildcard cert *.cnx1.cnx.network; Blocky + # resolves the names to the router's LAN address for mgmt+lan clients. + proxy = { + enable = true; + services.omada = { + # Omada's UI is HTTPS with a self-signed cert on the host network. + backend = "https://127.0.0.1:8043"; + insecureSkipVerify = true; + }; + }; + }; + }; +}