diff --git a/clan.nix b/clan.nix index 89983fa..eca3270 100644 --- a/clan.nix +++ b/clan.nix @@ -1,3 +1,4 @@ +{ inputs, self, ... }: let hosts = import ./modules/hosts.nix; @@ -17,173 +18,192 @@ let }; in { - # Ensure this is unique among all clans you want to use. - meta.name = "cnx-network-clan"; - meta.domain = "cnx-network.internal"; + clan = { + # Ensure this is unique among all clans you want to use. + meta.name = "cnx-network-clan"; + meta.domain = "cnx-network.internal"; - inventory.machines = fleet; + specialArgs = { inherit inputs self; }; - inventory.instances = { + # Customize nixpkgs + # pkgsForSystem = + # system: + # import nixpkgs { + # inherit system; + # config = { + # allowUnfree = true; + # }; + # overlays = []; + # }; + secrets.age.plugins = [ + "age-plugin-yubikey" + "age-plugin-fido2-hmac" + ]; - # Admin SSH keys + root password, split per the clan-core migration off - # the deprecated `admin` service (sshd handles keys, users the password). - sshd = { - roles.server.tags.all = { }; - roles.server.settings.authorizedKeys = { - "berwn" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIENAjhGQGraQoAjJzsomKP8GAmQPeGL1rNRNHgRcLqtT"; - "kurogeek" = - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEcZ/p1Ofa9liwIzPWzNtONhJ7+FUWd2lCz33r81t8+w kurogeek@kurogeek"; + inventory.machines = fleet; + + inventory.instances = { + + # Admin SSH keys + root password, split per the clan-core migration off + # the deprecated `admin` service (sshd handles keys, users the password). + sshd = { + roles.server.tags.all = { }; + roles.server.settings.authorizedKeys = { + "berwn" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIENAjhGQGraQoAjJzsomKP8GAmQPeGL1rNRNHgRcLqtT"; + "kurogeek" = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEcZ/p1Ofa9liwIzPWzNtONhJ7+FUWd2lCz33r81t8+w kurogeek@kurogeek"; + }; }; - }; - gw-router = { - module = { - name = "gw-router"; - input = "self"; - }; - roles.default.machines."gw-cnx-1" = { - settings = { - wan = { - interface = "enp1s0"; - vlanId = null; - pppInterface = "enp1s0"; - }; - vlans = { - mgmt = { - id = 10; - address = "10.1.10.1"; - prefixLength = 24; - subnet = "10.1.10.0/24"; - dhcp.fixIPs.storinator01 = { - hwAddress = "7c:c2:55:e0:d6:40"; - ipAddress = "10.1.10.53"; + gw-router = { + module = { + name = "gw-router"; + input = "self"; + }; + roles.default.machines."gw-cnx-1" = { + settings = { + wan = { + interface = "enp1s0"; + vlanId = null; + pppInterface = "enp1s0"; + }; + vlans = { + mgmt = { + id = 10; + address = "10.1.10.1"; + prefixLength = 24; + subnet = "10.1.10.0/24"; + dhcp.fixIPs.storinator01 = { + hwAddress = "7c:c2:55:e0:d6:40"; + ipAddress = "10.1.10.53"; + }; + }; + lan = { + id = 20; + address = "10.1.20.1"; + prefixLength = 24; + subnet = "10.1.20.0/24"; + }; + iot = { + id = 40; + address = "10.1.40.1"; + prefixLength = 24; + subnet = "10.1.40.0/24"; + }; + voip = { + id = 50; + address = "10.1.50.1"; + prefixLength = 24; + subnet = "10.1.50.0/24"; + }; + dmz = { + id = 60; + address = "10.1.60.1"; + prefixLength = 24; + subnet = "10.1.60.0/24"; + }; + unit1 = { + id = 110; + address = "10.1.110.1"; + prefixLength = 24; + subnet = "10.1.110.0/24"; + }; + unit2 = { + id = 120; + address = "10.1.120.1"; + prefixLength = 24; + subnet = "10.1.120.0/24"; + }; + unit3 = { + id = 130; + address = "10.1.130.1"; + prefixLength = 24; + subnet = "10.1.130.0/24"; + }; + unit4 = { + id = 140; + address = "10.1.140.1"; + prefixLength = 24; + subnet = "10.1.140.0/24"; + }; + unit5 = { + id = 150; + address = "10.1.150.1"; + prefixLength = 24; + subnet = "10.1.150.0/24"; + dhcp.fixIPs.newt = { + hwAddress = "7c:d3:0a:21:58:0b"; + ipAddress = "10.1.150.22"; + }; }; }; - lan = { - id = 20; - address = "10.1.20.1"; - prefixLength = 24; - subnet = "10.1.20.0/24"; + trunkPorts = [ "enp2s0" ]; + accessPorts = { + enp4s0.vlanId = 10; }; - iot = { - id = 40; - address = "10.1.40.1"; - prefixLength = 24; - subnet = "10.1.40.0/24"; - }; - voip = { - id = 50; - address = "10.1.50.1"; - prefixLength = 24; - subnet = "10.1.50.0/24"; - }; - dmz = { - id = 60; - address = "10.1.60.1"; - prefixLength = 24; - subnet = "10.1.60.0/24"; - }; - unit1 = { - id = 110; - address = "10.1.110.1"; - prefixLength = 24; - subnet = "10.1.110.0/24"; - }; - unit2 = { - id = 120; - address = "10.1.120.1"; - prefixLength = 24; - subnet = "10.1.120.0/24"; - }; - unit3 = { - id = 130; - address = "10.1.130.1"; - prefixLength = 24; - subnet = "10.1.130.0/24"; - }; - unit4 = { - id = 140; - address = "10.1.140.1"; - prefixLength = 24; - subnet = "10.1.140.0/24"; - }; - unit5 = { - id = 150; - address = "10.1.150.1"; - prefixLength = 24; - subnet = "10.1.150.0/24"; - dhcp.fixIPs.newt = { - hwAddress = "7c:d3:0a:21:58:0b"; - ipAddress = "10.1.150.22"; - }; - }; - }; - trunkPorts = [ "enp2s0" ]; - accessPorts = { - enp4s0.vlanId = 10; }; }; }; - }; - root-user = { - module = { - name = "users"; - input = "clan-core"; + root-user = { + module = { + name = "users"; + input = "clan-core"; + }; + roles.default.tags.all = { }; + roles.default.settings = { + user = "root"; + prompt = false; # auto-generate, like the old admin service + share = false; # per-machine password, not fleet-wide + }; }; - roles.default.tags.all = { }; - roles.default.settings = { - user = "root"; - prompt = false; # auto-generate, like the old admin service - share = false; # per-machine password, not fleet-wide + + zerotier = { + roles.controller.machines."control" = { }; + roles.peer.tags.all = { }; + # External members admitted by ZeroTier node id (stable per device). + # Inventory machines are auto-accepted; this is only for peers outside the + # clan. Node id comes from `zerotier-cli info` on the joining device. + roles.controller.settings.allowedIds = [ + "8802c8d7e0" # alex-nixos + "2bd36db8cc" # kurogeek-thinkpad + ]; + }; + + tor = { + roles.server.tags.nixos = { }; + }; + + # Direct SSH to public IPs — clan's priority-1 connection path, with the + # ZeroTier mesh and Tor kept as automatic fallbacks. Raw IPs (from + # modules/hosts.nix, not the ns1/ns2 DNS names) so reaching these hosts never + # depends on their own DNS being up. + internet.roles.default.machines = builtins.mapAttrs (_: h: { + settings.host = h.ipv4; + }) hosts; + + # Recovery root password for console access when a machine fails to boot. + emergency-access = { + roles.default.tags.nixos = { }; + }; + + # Encrypted, deduplicating backups. control hosts the repos; ns1 is the + # only client, backing up its declared clan.core.state (the Knot DNSSEC + # keystore) over the mesh. Repo lives at /var/lib/borgbackup/ns1 on control. + # Cross-host so an ns1 loss is recoverable; repokey encryption means control + # never holds plaintext. Run `clan vars generate ns1` (YubiKey) before deploy. + borgbackup = { + roles.server.machines.control = { }; + roles.client.machines.ns1 = { }; }; }; - zerotier = { - roles.controller.machines."control" = { }; - roles.peer.tags.all = { }; - # External members admitted by ZeroTier node id (stable per device). - # Inventory machines are auto-accepted; this is only for peers outside the - # clan. Node id comes from `zerotier-cli info` on the joining device. - roles.controller.settings.allowedIds = [ - "8802c8d7e0" # alex-nixos - "2bd36db8cc" # kurogeek-thinkpad - ]; - }; - - tor = { - roles.server.tags.nixos = { }; - }; - - # Direct SSH to public IPs — clan's priority-1 connection path, with the - # ZeroTier mesh and Tor kept as automatic fallbacks. Raw IPs (from - # modules/hosts.nix, not the ns1/ns2 DNS names) so reaching these hosts never - # depends on their own DNS being up. - internet.roles.default.machines = builtins.mapAttrs (_: h: { - settings.host = h.ipv4; - }) hosts; - - # Recovery root password for console access when a machine fails to boot. - emergency-access = { - roles.default.tags.nixos = { }; - }; - - # Encrypted, deduplicating backups. control hosts the repos; ns1 is the - # only client, backing up its declared clan.core.state (the Knot DNSSEC - # keystore) over the mesh. Repo lives at /var/lib/borgbackup/ns1 on control. - # Cross-host so an ns1 loss is recoverable; repokey encryption means control - # never holds plaintext. Run `clan vars generate ns1` (YubiKey) before deploy. - borgbackup = { - roles.server.machines.control = { }; - roles.client.machines.ns1 = { }; - }; + # Fleet-wide baseline applied to every machine. Secrets minted by + # `clan vars generate` are encrypted for the admins group from the very + # first run — generating before this took effect is what forced the + # re-encryption dance (`clan vars fix`) on gw-cnx-1. + machines = builtins.mapAttrs (_: _: { + clan.core.sops.defaultGroups = [ "admins" ]; + }) fleet; }; - - # Fleet-wide baseline applied to every machine. Secrets minted by - # `clan vars generate` are encrypted for the admins group from the very - # first run — generating before this took effect is what forced the - # re-encryption dance (`clan vars fix`) on gw-cnx-1. - machines = builtins.mapAttrs (_: _: { - clan.core.sops.defaultGroups = [ "admins" ]; - }) fleet; } diff --git a/flake.nix b/flake.nix index 48731af..c9f2a1c 100644 --- a/flake.nix +++ b/flake.nix @@ -13,34 +13,10 @@ inputs@{ self, clan-core, - nixpkgs, flake-parts, ... }: let - # Usage see: https://docs.clan.lol - clan = clan-core.lib.clan { - inherit self; - imports = [ - ./clan.nix - ]; - specialArgs = { inherit inputs self; }; - - # Customize nixpkgs - # pkgsForSystem = - # system: - # import nixpkgs { - # inherit system; - # config = { - # allowUnfree = true; - # }; - # overlays = []; - # }; - secrets.age.plugins = [ - "age-plugin-yubikey" - "age-plugin-fido2-hmac" - ]; - }; in flake-parts.lib.mkFlake { inherit inputs; } { systems = [ @@ -51,14 +27,12 @@ ]; imports = [ + inputs.clan-core.flakeModules.default + + ./clan.nix ./modules/clan/flake-module.nix ]; - # flake = { - # inherit (clan.config) nixosConfigurations nixosModules clanInternals; - # clan = clan.config; - # }; - perSystem = { system, ... }: let diff --git a/machines/gw-cnx-1/configuration.nix b/machines/gw-cnx-1/configuration.nix index 4320c31..c679df0 100644 --- a/machines/gw-cnx-1/configuration.nix +++ b/machines/gw-cnx-1/configuration.nix @@ -4,7 +4,7 @@ { config, lib, ... }: { imports = [ - ../../modules/router + # ../../modules/router ../../modules/monitoring/exporters.nix (import ../../modules/dns/acme-gw-secret.nix "gw-cnx-1") ]; @@ -20,60 +20,60 @@ builtins.hashString "sha256" config.networking.hostName ); - cnx.router = { - enable = true; - site = "cnx"; - siteId = 1; - wan.interface = "enp1s0"; - wan.vlanId = null; # this ISP runs PPPoE untagged on the port - trunkPorts = [ - "enp2s0" - # "enp3s0" # STAGING: serves as the uplink below until cutover - ]; - # 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; - }; - }; - }; + # cnx.router = { + # enable = true; + # site = "cnx"; + # siteId = 1; + # wan.interface = "enp1s0"; + # wan.vlanId = null; # this ISP runs PPPoE untagged on the port + # trunkPorts = [ + # "enp2s0" + # # "enp3s0" # STAGING: serves as the uplink below until cutover + # ]; + # # 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; + # }; + # }; + # }; # STAGING (remove at cutover, and restore enp3s0 to trunkPorts): DHCP-client # uplink into the existing OPNsense LAN so the box has internet + mesh while