187 lines
6.8 KiB
Nix
187 lines
6.8 KiB
Nix
{ 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
|
|
# baseline (e.g. admins group encryption for all its generated secrets).
|
|
fleet = {
|
|
control = { };
|
|
ns1 = { };
|
|
ns2 = { };
|
|
mx1 = { };
|
|
web01 = { };
|
|
# Site gateways (Topton 1U routers): dynamic PPPoE WAN, so they are NOT in
|
|
# modules/hosts.nix / the `internet` instance — clan reaches them over the
|
|
# zerotier mesh (or Tor) instead.
|
|
gw-cnx-1 = { };
|
|
};
|
|
in
|
|
{
|
|
# Ensure this is unique among all clans you want to use.
|
|
meta.name = "cnx-network-clan";
|
|
meta.domain = "cnx-network.internal";
|
|
|
|
inventory.machines = fleet;
|
|
|
|
# Services defined in this repo (module.input = "self"). See modules/clan/.
|
|
modules.router = ./modules/clan/router;
|
|
|
|
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";
|
|
};
|
|
};
|
|
|
|
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
|
|
};
|
|
};
|
|
|
|
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 = { };
|
|
};
|
|
|
|
# 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
|
|
# <site><n>.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.<vlanId>.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
|
|
# `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;
|
|
}
|