# Settings of the `router` service (inventory `roles.default.settings`). # Pure schema: no machine config is reachable here; the implementation files # get the evaluated result as `settings`. # # Fleet addressing convention: each site owns 10..0.0/16. A VLAN's # subnet defaults to 10...0/24 with the router at .1 and the # DHCP pool at .100-.199. VLANs that need more space (e.g. public-wifi guest) # override `subnet`/`address`/`dhcp.pool` and take a wider block from the # upper half (10..128.0/17), e.g. guest -> 10..128.0/22. # VLAN ids: 10 = mgmt, 20 = lan (mandatory); 30 = guest, 40 = iot (reserved). { config, lib, ... }: let site = toString config.siteId; vlanModule = { config, ... }: let octet = toString config.id; in { options = { id = lib.mkOption { type = lib.types.ints.between 1 4094; description = "802.1Q VLAN id (fleet convention: 10 mgmt, 20 lan, 30 guest, 40 iot)."; }; address = lib.mkOption { type = lib.types.str; default = "10.${site}.${octet}.1"; defaultText = lib.literalExpression ''"10...1"''; description = "Router address on this VLAN."; }; prefixLength = lib.mkOption { type = lib.types.ints.between 8 30; default = 24; }; subnet = lib.mkOption { type = lib.types.str; default = "10.${site}.${octet}.0/24"; defaultText = lib.literalExpression ''"10...0/24"''; description = "The VLAN's network in CIDR form (must contain `address`)."; }; dhcp = { enable = lib.mkOption { type = lib.types.bool; default = true; }; pool = { from = lib.mkOption { type = lib.types.str; default = "10.${site}.${octet}.100"; defaultText = lib.literalExpression ''"10...100"''; }; to = lib.mkOption { type = lib.types.str; default = "10.${site}.${octet}.199"; defaultText = lib.literalExpression ''"10...199"''; }; }; leaseTime = lib.mkOption { type = lib.types.ints.positive; default = 86400; description = '' Lease validity in seconds. Lower it for high-churn networks, e.g. public-WiFi guest VLANs (3600-7200), so the pool recycles. ''; }; reservations = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { hwAddress = lib.mkOption { type = lib.types.str; example = "aa:bb:cc:dd:ee:ff"; description = "Client MAC address."; }; ipAddress = lib.mkOption { type = lib.types.str; description = "Fixed address handed to this client (inside the VLAN's subnet, outside the pool)."; }; }; } ); default = { }; description = "Static DHCP leases; the attribute name becomes the client's hostname."; }; }; allowWan = lib.mkOption { type = lib.types.bool; default = true; description = "Whether clients on this VLAN may reach the internet."; }; }; }; proxyServiceModule = { options = { backend = lib.mkOption { type = lib.types.str; example = "https://127.0.0.1:8043"; description = "URL Caddy forwards to (internal/mesh address)."; }; insecureSkipVerify = lib.mkOption { type = lib.types.bool; default = false; description = "Skip TLS verification towards the backend (self-signed upstreams like Omada)."; }; }; }; in { options = { site = lib.mkOption { type = lib.types.str; description = "City code of the site, e.g. \"cnx\"."; }; siteId = lib.mkOption { type = lib.types.ints.between 1 254; description = "Site number; drives the 10...0/24 addressing."; }; wan.interface = lib.mkOption { type = lib.types.str; description = "Physical WAN port the PPPoE session runs on."; }; wan.vlanId = lib.mkOption { type = lib.types.nullOr (lib.types.ints.between 1 4094); default = null; description = '' 802.1Q tag the ISP requires for the PPPoE session (AIS Thailand: 10); null for untagged PPPoE directly on the port. Unrelated to the LAN VLANs — this tag exists only on the WAN port. ''; }; wan.macAddress = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "aa:bb:cc:dd:ee:ff"; description = '' Spoofed MAC for the WAN port, e.g. to keep the MAC the ISP has pinned (cloned from the old router). null keeps the hardware MAC. ''; }; trunkPorts = lib.mkOption { type = lib.types.listOf lib.types.str; description = "LAN ports carrying all VLANs tagged (incl. any 10G SFP+ ports)."; }; accessPorts = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { }; example = { enp4s0 = "mgmt"; }; description = '' Ports acting as untagged access ports on a single VLAN (port name -> VLAN name). Frames are untagged on the wire; the bridge tags them with the VLAN's PVID. Use for an always-available on-site mgmt port. ''; }; vlans = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule vlanModule); description = "VLANs served at this site; `mgmt` and `lan` are mandatory."; }; mesh.subnet = lib.mkOption { type = lib.types.str; example = "fd12:3456:789a:bcde:f000::/88"; description = '' IPv6 prefix of the admin mesh (the ZeroTier overlay; see modules/mesh-hosts.nix). Admin SSH, metrics scrapes, iperf3 and the Omada UI accept connections from it, and CrowdSec never bans it. ''; }; omada.enable = lib.mkEnableOption "TP-Link Omada SDN controller (podman container)"; proxy = { enable = lib.mkEnableOption "internal reverse proxy (Caddy, wildcard cert via DNS-01)"; domain = lib.mkOption { type = lib.types.str; example = "example.net"; description = '' Parent zone of the proxy names: services are served as .. under a wildcard certificate. ''; }; acme = { nameserver = lib.mkOption { type = lib.types.str; example = "203.0.113.53"; description = '' Authoritative nameserver of `domain` that accepts RFC 2136 updates for _acme-challenge. with this gateway's TSIG key (acme_, secret from the shared dns-acme--secret generator, see acme-secret.nix). ''; }; email = lib.mkOption { type = lib.types.str; example = "postmaster@example.net"; description = "ACME account contact."; }; }; services = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule proxyServiceModule); default = { }; description = "Proxied services; attr name becomes ..."; }; allowVlans = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "mgmt" "lan" ]; description = "VLANs whose clients may reach the proxy (443, plus 80 for the redirect)."; }; }; speedtest.interval = lib.mkOption { type = lib.types.str; default = "hourly"; description = "systemd OnCalendar spec for the WAN speed test."; }; }; }