diff --git a/docs/mobile/README.md b/docs/mobile/README.md new file mode 100644 index 0000000..f886eb9 --- /dev/null +++ b/docs/mobile/README.md @@ -0,0 +1,9 @@ +# Mobile boxes + +Portable workstations that move between networks — currently just `tower`. Mobile boxes have +no static network assignments; they use DHCP/NetworkManager and reach other boxes over the +tailnet. + +| Box | What it is | +| --- | --- | +| [`tower`](tower.md) | Framework Laptop 13 workstation | diff --git a/docs/mobile/tower.md b/docs/mobile/tower.md new file mode 100644 index 0000000..ac65837 --- /dev/null +++ b/docs/mobile/tower.md @@ -0,0 +1,50 @@ +# tower + +Portable workstation — a Framework Laptop 13 (Intel), running the full GUI environment. + +- **Source:** [`nixos/boxes/tower/default.nix`](../../nixos/boxes/tower/default.nix) +- **Host:** physical (laptop) +- **nixpkgs:** `mine` + +## Role + +- Personal portable workstation: `my.gui.enable`, with Sway managed by home-manager. +- Joins the tailnet through the headscale on [`britway`](../remote/britway.md) (fish abbr + `tsup` = `doas tailscale up --login-server=https://hs.nul.ie --accept-routes`). + +## Network assignments + +`tower` has no static assignment; it uses DHCP through NetworkManager and reaches the other boxes +over Tailscale. + +## Hardware / platform + +| Component | Inventory | +|---|---| +| Platform | Framework Laptop 13 (12th Gen Intel Core) | +| CPU | Intel Core i5-1240P (12 cores / 16 threads) | +| Memory | 32 GiB | +| Storage | 2 TB WD Black SN850 NVMe SSD | +| Graphics | Integrated Intel Iris Xe | +| Connectivity | Intel AX210 Wi-Fi 6E, Bluetooth and Thunderbolt 4 | + +The configuration enables Intel microcode updates, `kvm-intel`, `intel_iommu=on`, +`intel-media-driver` and the latest kernel (`lib.my.c.kernel.latest`). Thunderbolt security +(`bolt`), the fingerprint reader (`fprintd`) and `tlp` power management are also enabled. + +## Storage + +- Two LUKS-encrypted partitions, `persist` and `home` (both `allowDiscards`); `/nix` is a + separate ext4 filesystem, `/boot` is vfat. +- Persistent `/home` (`my.user.tmphome = false`) with a size-limited tmpfs root (`my.tmproot.size`). + +## Networking / services + +- NetworkManager (`wpa_supplicant` backend) with `systemd-resolved`; networkd `wait-online` + is disabled. The Wi-Fi interface is renamed to `wifi` by MAC. +- Steam and Wireshark enabled; `fstrim`, LVM thin provisioning. +- `nix.gc.automatic = false` — GC is run manually on the laptop. + +## Notable config files + +- [`nixos/boxes/tower/default.nix`](../../nixos/boxes/tower/default.nix) — the whole box (single file). diff --git a/docs/networking.md b/docs/networking.md new file mode 100644 index 0000000..81b651d --- /dev/null +++ b/docs/networking.md @@ -0,0 +1,267 @@ +# Networking + +This page describes how addressing works across the boxes: the assignment mechanism, the +per-site domains and prefixes, the home router HA pair, and the overlays/tunnels that tie the +sites together. Switch-level home topology (jim/dave/brian, the ONT path) lives in +[sites/home/switches.md](sites/home/switches.md). + +## Assignments + +Every box declares `nixos.systems..assignments`, an attrset of *assignments* (one per +network the box is attached to). The option definition (`assignmentOpts` in +[`nixos/default.nix`](../nixos/default.nix)): + +- `name` (defaults to the attribute name) and `altNames` — DNS names for the assignment. +- `visible` (default `true`) — whether DNS helpers include it. +- `domain` — DNS suffix for this assignment. +- `mtu` — interface MTU (applied via the network's `linkConfig.MTUBytes`). +- `ipv4.address` / `ipv4.mask` (default 24) / `ipv4.gateway` (defaults to host 1 of the + prefix; set explicitly to `null` when there is no gateway) / `ipv4.genPTR`. +- `ipv6.address` (nullable — an assignment can be v4-only) / `ipv6.mask` (default 64) / + `ipv6.iid` (SLAAC static token instead of a full address) / `ipv6.gateway` / `ipv6.genPTR`. + +`extraAssignments` is a second, nested level for addresses that belong *to* a network but not +to any single box — the home routers use it for their floating VIP entries (`router-hi`, +`router-lo`, `router-ut`). + +All assignments are aggregated into `nixos.allAssignments` — every system's `assignments` +merged with every system's `extraAssignments` — and passed to every module as the +`allAssignments` argument, so any box can route to any other box's addresses without +hardcoding. A flake-wide assertion fails evaluation if any IPv4 or IPv6 address appears in +more than one assignment. Each box also receives its own assignments as the `assignments` +module argument. + +Two pieces of machinery consume assignments: + +- `lib.my.networkdAssignment` ([`lib/default.nix`](../lib/default.nix)) renders an assignment + as a `systemd.network` network: static `address`/`gateway`, MTU, LLDP, and IPv6 RA handling + (`IPv6AcceptRA` when there's no static gateway or a static `iid` is set, with + `Token = static:`). +- `mkSystem` defaults `networking.hostName` to `assignments.internal.name` (falling back to + the system name) and `networking.domain` to `assignments.internal.domain`. The shared + `network` module sets a fallback domain of `int.nul.ie` for boxes without one. + +## Box assignments + +Every box's `assignments` (plus the routers' floating VIP `extraAssignments`), grouped first by site +and then by assignment key. Generated from `nixos.allAssignments` by +`nix run .#update-docs-assignments`; CI keeps it current. Only the **Notes** column is hand-written — +edit prose there, never the other generated cells. + +### colony + + + + + +### home + + + + + +### remote + + + + + +## Domains + +The public domain is `nul.ie` (`lib.my.c.pubDomain`). Each site has its own internal domain +(constants in [`lib/constants.nix`](../lib/constants.nix)): + +| Site | Domain | +|---|---| +| colony | `ams1.int.nul.ie` | +| home | `h.nul.ie` | +| britway | `lon1.int.nul.ie` | +| britnet | `bhx1.int.nul.ie` | +| kelder | `hentai.engineer` | + +## colony + +The colony box is a hosted server in Amsterdam (`ams1`); its public edge is the `estuary` VM +(`94.142.240.44`, `2a02:898:0:20::329:1`), which NATs and filters for everything behind it. +The internal prefixes (`lib.my.c.colony.prefixes`) are carved from `10.100.0.0/16` and +`2a0e:97c0:4d2:10::/60`: + +| Network | IPv4 | IPv6 | Purpose | +|---|---|---|---| +| `base` | `10.100.0.0/24` | `2a0e:97c0:4d2:10::/64` | Base/management LAN (bridge on the host; estuary is `.1`) | +| `vms` | `10.100.1.0/24` | `2a0e:97c0:4d2:11::/64` | VM network (host is `.1`, hands out RAs) | +| `ctrs` | `10.100.2.0/24` | `2a0e:97c0:4d2:12::/64` | `systemd-nspawn` containers on the `shill` VM | +| `oci` | `10.100.3.0/24` | `2a0e:97c0:4d2:13::/64` | Podman/OCI workloads on the `whale2` VM | +| `qclk` | `10.100.4.0/24` | — | WireGuard endpoint instances in the `qclk` container | + +On top of that: `p2pTunnels` (`10.100.5.0/24`) holds point-to-point tunnel /30s (see +[WireGuard tunnels](#wireguard-point-to-point-tunnels)); the `as211024` mesh gets +`10.100.50.0/24` + `2a0e:97c0:4df::/64` (see [the L2 mesh](#the-as211024-l2-mesh)); and the +`cust` block (`10.100.100.0/24`, `2a0e:97c0:4d2:2000::/56`) plus the `vip1`/`vip2`/`vip3` +public blocks and the per-customer `mail` / `darts` / `jam` prefixes carry customer-facing +services with their own public addresses (announced by BGP, routed via the host). + +## home + +The home site prefixes (`lib.my.c.home.prefixes`) come from `192.168.64.0/18` and +`2a0e:97c0:4d0::/60`, with VLAN IDs from `lib.my.c.home.vlans`: + +| Network | VLAN | IPv4 | IPv6 | MTU | Purpose | +|---|---|---|---|---|---| +| `core` | — (macvlan) | `192.168.64.0/24` | — | 1500 | Router-to-router/core link | +| `hi` | 100 | `192.168.68.0/22` | `2a0e:97c0:4d0:1::/64` | 9000 | High-speed LAN (jumbo frames) | +| `lo` | 110 | `192.168.72.0/21` | `2a0e:97c0:4d0:2::/64` | 1500 | General LAN | +| `untrusted` | 120 | `192.168.80.0/24` | `2a0e:97c0:4d0:3::/64` | 1500 | Untrusted / IoT | +| `modem` | 130 (`wan`) | `192.168.0.0/24` | — | — | Virgin Media modem management (stream) | +| `ont` | 140 (`wan-pon-ont`) | `192.168.100.0/24` | — | — | Digiweb ONT management (river) | + +Two more WAN-side VLANs exist: `pon-isp` (10), the ISP VLAN Digiweb delivers at the ONT and +which is trunked untranslated to river, and `wan-pon-isp` (141), reserved for a future +multi-ONT translation scheme — see +[sites/home/switches.md](sites/home/switches.md) for the fabric side. + +The routers themselves (`river` = host 1, `stream` = host 2 in each prefix) are built from one +definition, [`nixos/boxes/home/routing-common`](../nixos/boxes/home/routing-common/default.nix), +parameterised by an index (`0` = river, `1` = stream) that derives per-box addresses, DHCP +pool splits, VRRP state/priority and DNS `ns` numbering. + +### Router VIPs + +Clients never use a router's real address: each client VLAN has a floating VIP +(`lib.my.c.home.vips`) that follows the VRRP master. The VIPs are also declared as +`extraAssignments` (`router-hi`/`router-lo`/`router-ut`) so they appear in `allAssignments` +and DNS: + +| Assignment | IPv4 | IPv6 | +|---|---|---| +| `router-hi` | `192.168.71.254/22` | `2a0e:97c0:4d0:1::ffff/64` | +| `router-lo` | `192.168.79.254/21` | `2a0e:97c0:4d0:2::ffff/64` | +| `router-ut` | `192.168.80.254/24` | `2a0e:97c0:4d0:3::ffff/64` | + +There is also a mesh-side VIP (`as211024`): `10.100.50.4` and `2a0e:97c0:4df:0:1::ffff`, +which the other sites use as their next-hop into the home prefixes. + +### Router HA + +#### VRRP + +[`routing-common/keepalived.nix`](../nixos/boxes/home/routing-common/keepalived.nix) defines separate +v4/v6 instances (router IDs 51/52) on `lan-core`. Index 0 (`river`) starts as `MASTER`, priorities +are `255 - index`, and track scripts demote a router whose WAN checks fail. All VIPs of an address +family move together. + +#### Client gateway and DNS + +`kea` hands out `vips..v4` as both `routers` and `domain-name-servers`, with the two routers +serving disjoint pool halves. `radvd` advertises the v6 VIP as RDNSS (`untrusted` gets Cloudflare) +and keepalived's `notify_master`/`notify_backup` hooks ensure that only the master sends RAs. + +#### DNS binding + +`pdns-recursor` binds the VIPs directly; see +[`routing-common/dns.nix`](../nixos/boxes/home/routing-common/dns.nix). The +`net.ipv4.ip_nonlocal_bind` / `net.ipv6.ip_nonlocal_bind` settings let the backup listen before it +owns the addresses, so failover does not depend on client resolver timeouts. The recursor forwards +the site's zones to authoritative PowerDNS on `127.0.0.1:5353`. + +#### `wan-online.target` + +This shared, initially inert systemd target means "the public IPv4 WAN route is up". +`routing-common` only declares it: `stream` gates it on a oneshot that waits for the DHCP default +route on `wan`, while `river`'s `pppd` hooks start and stop it. Consumers such as `ipsec` attach with +`wantedBy` + `partOf` + `after`, never `requires`/`wants`, so they cannot pull the target in early +and they reload on WAN flap. + +### WAN paths (summary) + +#### `river` + +The VM on `palace` runs Digiweb PPPoE directly on VLAN 10 (`vlans.pon-isp`), trunked untranslated +through the switches. The carrying interface is named `wan-pon-isp`; VLAN ID 141 with that name is +reserved for a future translation scheme. MTU 1508 preserves a 1500-byte PPP session, IPCP requests +the static address, and the `pppd` hooks own `wan-online.target`. `wan-pon-ont` (VLAN 140) reaches +the ONT management subnet at `192.168.100.0/24`, where `river` takes `.100`. + +#### `stream` + +The bare-metal backup uses DHCP from the Virgin Media modem on `wan` (VLAN 130), keeps a static +`192.168.0.100/24` management address beside the public lease, and applies CAKE shaping through +`wan-ifb`. + +The full fabric story — which switch port carries what, why VLAN 10 is trunked untranslated, +and the multi-ONT plan — is in [sites/home/switches.md](sites/home/switches.md); the +`my.homeRouter.*` options (`dns.wanSkipBroadcasts`, `firewall.untrustedRejectV4`) let each box +tell `routing-common` about subnets sharing its WAN interface. + +## The AS211024 L2 mesh + +The edge routers are joined by a layer-2 mesh, defined once as `nixos.vpns.l2.as211024` in +[`nixos/boxes/colony/vms/estuary/default.nix`](../nixos/boxes/colony/vms/estuary/default.nix) +and realised on each member by the [`l2mesh` module](../nixos/modules/l2mesh.nix): + +### Members + +`estuary`, `river`, `stream` and `britway` peer on their public addresses. + +### Transport + +VXLAN (VNI 211024, UDP port 4789) uses static per-peer FDB entries and UDP-encapsulated IPsec in +Libreswan transport mode. It authenticates without encryption by default; `security.encrypt` +switches ESP from `null-sha256` to AES-GCM. The shared `l2mesh/as211024.key` PSK is expanded into +`/run/l2mesh.secrets` when `ipsec` starts. + +### Overlay addressing + +The overlay uses `10.100.50.0/24` / `2a0e:97c0:4df::/64`. Each router holds `10.100.50.`: +`estuary` `.1`, `river` `.2`, `stream` `.3`, and `britway` `.5`. Interface MTU is calculated from +the physical MTU minus VXLAN/UDP/IPsec overhead. + +### Routing + +The home routers route colony prefixes via `estuary` and Tailscale prefixes via `britway`; +`estuary` and `britway` route home prefixes through the `10.100.50.4` VIP. The home IPv6 default +route also crosses the mesh through `britway`, which is why the recursor pins its upstreams to IPv4 +as noted in [`routing-common/dns.nix`](../nixos/boxes/home/routing-common/dns.nix). The `nftTrust` +snippet in `lib.my.c.as211024` admits trusted colony, home, mesh and Tailscale prefixes at the edge +firewalls. + +## BGP + +Both edge routers run `bird2` as AS211024. + +### `estuary` + +The colony edge takes a full table from ColoClue, IPv6 transit from iFog and Hurricane Electric, +and peers through the Frys-IX, NL-ix and FogIXP route servers. It also has direct and monitoring +sessions; the complete peer table and originated routes live in [estuary.md](sites/colony/estuary.md#bgp). + +### `britway` + +The London edge uses secret-backed MD5 authentication for Vultr transit (AS64515), connects to +`bgp.tools`, and originates the internal, colony and home IPv6 prefixes. See +[britway.md](remote/britway.md) for its box-specific routing detail. + +## WireGuard point-to-point tunnels + +Separate from the mesh, a few boxes run their own WireGuard (private keys in per-box secrets): + +- **estuary** terminates point-to-point tunnels to `kelder`, `hillcrest` and `john-valorant`, + addressed out of `p2pTunnels` — see + [estuary.md](sites/colony/estuary.md#wireguard-tunnels) for the per-tunnel ports and prefixes. +- **`qclk`** (container on `shill`) runs its own WireGuard on port 51821 out of `10.100.4.0/24` + — see [qclk.md](sites/colony/shill/containers/qclk.md). +- **`britnet`** hosts a road-warrior WireGuard VPN on port 51820 serving `10.200.0.0/24` / + `fdfb:5ebf:6e84::/64` — see [britnet.md](remote/britnet.md). + +## Tailscale / headscale + +Tailscale runs against a self-hosted **headscale** control plane on britway at `https://hs.nul.ie` +(OIDC, MagicDNS, split DNS — see [britway.md](remote/britway.md)). The tailnet prefixes are +`100.64.0.0/10` / `fd7a:115c:a1e0::/48`. + +Notable nodes: + +- **waffletail** (container on `shill`) — the colony subnet router: advertises the colony + prefixes, acts as an exit node, and SNATs tailnet traffic into the colony networks. +- **britway** — advertises the home prefixes (routed via the mesh) and is also an exit node. +- Other boxes join with the shared `tailscale-auth.key` auth-key secret. diff --git a/docs/remote/README.md b/docs/remote/README.md new file mode 100644 index 0000000..4892edf --- /dev/null +++ b/docs/remote/README.md @@ -0,0 +1,12 @@ +# Remote boxes + +The "remote" group covers the boxes that live outside the `colony` and `home` sites: the two +edge VPSes (`britway` in London, `britnet` in Birmingham) and the `kelder` site — a secondary +server at a remote location, linked back to colony over WireGuard and acting as a NixOS +container host. + +| Box | What it is | +| --- | --- | +| [`britway`](britway.md) | Vultr VPS (London, `lon1`): Headscale control plane, Tailscale exit node, BGP edge, nginx | +| [`britnet`](britnet.md) | VPS (Birmingham, `bhx1`): Tailscale exit node / WireGuard hub | +| [`kelder`](kelder/README.md) | Secondary home server (`hentai.engineer`): container host, Samba, DDNS (containers on its page) | diff --git a/docs/remote/britnet.md b/docs/remote/britnet.md new file mode 100644 index 0000000..87dc200 --- /dev/null +++ b/docs/remote/britnet.md @@ -0,0 +1,41 @@ +# britnet + +A small VPS in Birmingham (`bhx1`) acting as a second Tailscale/WireGuard egress point — a +narrower gateway role than [`britway`](britway.md) (no control plane, no BGP). + +- **Source:** [`nixos/boxes/britnet.nix`](../../nixos/boxes/britnet.nix) +- **Host:** VPS (Birmingham, `bhx1`; provider uplink assignment `allhost`) +- **nixpkgs:** `mine` + +## Role + +- **Tailscale exit node** — logs into the headscale on [`britway`](britway.md) + (`--login-server=https://hs.nul.ie`) with `--advertise-exit-node`. +- **WireGuard hub** — `wg0` listens on UDP 51820 on the `vpn` assignment, with a single static + peer. +- **NAT gateway** — traffic arriving on `tailscale0`/`wg0` is forwarded out `veth0` and SNATed + to the `allhost` v4/v6 addresses. + +## Network assignments + +See the consolidated [network assignments](../networking.md#box-assignments) table (this box: `britnet`). + +## Platform + +| Component | Allocation | +|---|---| +| Virtualisation | KVM/QEMU guest | +| Compute | 2 vCPUs and 2 GiB RAM | +| Storage | 32 GiB virtio disk with separate ext4 filesystems for `/boot`, `/nix` and `/persist`; root is tmpfs | + +## Networking + +- The provider interface is renamed to `veth0` by MAC. Its IPv6 default gateway sits off-subnet, so + a link-scope route is added to reach it. +- `wg0` is a networkd WireGuard netdev keyed from `britnet/wg.key`; RA is disabled on it. +- Upstream DNS is hardcoded to Cloudflare (`1.1.1.1` / `1.0.0.1`). +- `iperf3` runs with an open port for bandwidth testing. + +## Notable config files + +- [`nixos/boxes/britnet.nix`](../../nixos/boxes/britnet.nix) — the whole box (single file). diff --git a/docs/remote/britway.md b/docs/remote/britway.md new file mode 100644 index 0000000..f55ffe6 --- /dev/null +++ b/docs/remote/britway.md @@ -0,0 +1,54 @@ +# britway + +A Vultr VPS in London (`lon1`) acting as the network edge node: the self-hosted Tailscale +control plane, a tailnet exit node, and the BGP speaker for AS211024. + +- **Source:** [`nixos/boxes/britway/`](../../nixos/boxes/britway) +- **Host:** VPS at Vultr (London, `lon1`) +- **nixpkgs:** `mine` + +## Role + +- **Headscale** — the self-hosted Tailscale control plane at `hs.nul.ie`; every other box's + `tailscaled` logs in here (`--login-server=https://hs.nul.ie`). Google OIDC for auth, + SQLite state, MagicDNS under `ts.nul.ie`, and split DNS pointing the colony/home domains + at their internal resolvers. +- **Tailscale exit node** — advertises `--advertise-exit-node` plus routes to the home v4/v6 + prefixes; tailnet traffic is SNATed out `veth0` (v4 to the Vultr public IP, v6 to the + `as211024` mesh address). +- **BGP edge** — `bird2` speaks BGP as AS211024 to Vultr transit (AS64515, separate v4/v6 + sessions authenticated with a password from `britway/bgp-password-vultr.conf`) and exports + everything to a `bgp.tools` monitoring session. It originates the internal, colony and home + IPv6 prefixes documented in [networking](../networking.md#domains). +- **nginx** — reverse proxy fronting headscale (`hs.nul.ie` → `localhost` headscale port), + with a wildcard ACME cert for `nul.ie` issued via Cloudflare DNS. + +## Network assignments + +See the consolidated [network assignments](../networking.md#box-assignments) table (this box: `britway`). + +## Platform + +| Component | Allocation | +|---|---| +| Virtualisation | Vultr VC2 virtual guest on a QEMU-compatible platform | +| Compute | 2 vCPUs and 2 GiB RAM | +| Storage | 65 GiB virtio disk with separate ext4 filesystems for `/boot`, `/nix` and `/persist`; root is tmpfs | + +## Networking + +- Two assignments: `vultr` on the provider interface `veth0` (renamed by MAC), and `as211024` + on the `l2mesh` VXLAN interface (`my.vpns.l2`) — member of the shared mesh (see + [The AS211024 L2 mesh](../networking.md#the-as211024-l2-mesh)). +- Static routes steer colony/home v4 traffic over the `as211024` mesh. A separate `ts-extra` + routing table with a policy rule on `tailscale0` ingress sends Tailscale-sourced v6 + traffic for colony via `estuary`, while the box's own v6 uses WAN. +- The firewall trusts the `as211024` prefixes (`lib.my.c.as211024.nftTrust`) and + `tailscale0`; `iperf3` runs with an open port for bandwidth testing. + +## Notable config files + +- [`nixos/boxes/britway/default.nix`](../../nixos/boxes/britway/default.nix) — system, assignments, networkd, firewall/SNAT. +- [`nixos/boxes/britway/bgp.nix`](../../nixos/boxes/britway/bgp.nix) — `bird2` config (Vultr transit, `bgp.tools`). +- [`nixos/boxes/britway/nginx.nix`](../../nixos/boxes/britway/nginx.nix) — nginx vhosts + ACME. +- [`nixos/boxes/britway/tailscale.nix`](../../nixos/boxes/britway/tailscale.nix) — headscale + the tailnet node itself. diff --git a/docs/remote/kelder/README.md b/docs/remote/kelder/README.md new file mode 100644 index 0000000..a40cd19 --- /dev/null +++ b/docs/remote/kelder/README.md @@ -0,0 +1,61 @@ +# kelder + +Secondary home server at a remote site, domain `hentai.engineer`. Linked back to colony over +WireGuard and acting as a NixOS container host (like `shill`/`sfh`). + +- **Source:** [`nixos/boxes/kelder/`](../../../nixos/boxes/kelder) +- **Host:** physical (Intel; LTS kernel, `kvm-intel`, IOMMU on) +- **nixpkgs:** `mine` + +## Role + +- **Container host** — runs two NixOS containers on the `ctrs` bridge + (`my.containers.instances`): `kelder-acquisition` and `kelder-spoder` (below). +- **Public services via colony** — a WireGuard tunnel (`estuary` netdev) connects to colony's + `estuary` box, which DNATs public traffic to kelder's tunneled assignment; connection-mark-based + policy routing sends replies back through the tunnel while ordinary traffic uses the LAN. + kelder's own NAT forwards `http`/`https` on to `kelder-spoder`. +- **Nextcloud host** — served from the `kelder-spoder` container. +- **Samba** — the `storage` share backed by `/mnt/storage`, with `nmbd` and `samba-wsdd` for + Windows discovery. +- **DDNS** — a `ddns-update` timer runs `dns_update.py` periodically to sync the + `hentai.engineer` and `kelder-local.hentai.engineer` Cloudflare records with the address on + `et1g0`. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `kelder`). + +## Containers + +| Container | Role | +| --- | --- | +| [`kelder-acquisition`](containers/kelder-acquisition.md) | Media stack (Transmission over AirVPN, Jackett/Radarr/Sonarr, Jellyfin) | +| [`kelder-spoder`](containers/kelder-spoder.md) | Nextcloud + nginx reverse proxy | + +The containers are not deploy targets (`my.deploy.enable = false`); they're managed through +the host. + +## Networking + +- LAN on `et1g0` (renamed by MAC) with DHCP and MTU 1460 (`lib.my.c.kelder.ipv4MTU`); the + kelder v4 prefixes are masqueraded out of it. +- The `estuary` WireGuard peer is combined with rules that keep LAN traffic on the main table and + only route tunnel-marked or owned traffic through the tunnel's dedicated table. + +## Services + +- `netdata` (proxied as `monitor.hentai.engineer` by `kelder-spoder`), `smartd`, `fstrim`, + LVM thin provisioning. +- `minecraft-server` is present but **disabled** (`enable = false`); the firewall still opens + 25565 tcp/udp. +- Primary user `kontent` (in the `storage`/`media` groups). +- Sets `system.nixos.distroName = "KelderOS"`, a custom Plymouth theme and an `amogus-beep` + boot jingle ([`boot.nix`](../../../nixos/boxes/kelder/boot.nix)). + +## Notable config files + +- [`nixos/boxes/kelder/default.nix`](../../../nixos/boxes/kelder/default.nix) — system, assignments, tunnel, NAT, containers. +- [`nixos/boxes/kelder/boot.nix`](../../../nixos/boxes/kelder/boot.nix) — Plymouth theme + boot beep. +- [`nixos/boxes/kelder/containers/`](../../../nixos/boxes/kelder/containers) — the two container definitions. +- [`nixos/boxes/kelder/dns_update.py`](../../../nixos/boxes/kelder/dns_update.py) — Cloudflare DDNS script. diff --git a/docs/remote/kelder/containers/kelder-acquisition.md b/docs/remote/kelder/containers/kelder-acquisition.md new file mode 100644 index 0000000..1cab49e --- /dev/null +++ b/docs/remote/kelder/containers/kelder-acquisition.md @@ -0,0 +1,45 @@ +# kelder-acquisition + +The media acquisition stack for the kelder site — Transmission (forced over VPN), the *arrs +and Jellyfin in one NixOS container. + +- **Source:** [`nixos/boxes/kelder/containers/acquisition/`](../../../../nixos/boxes/kelder/containers/acquisition) +- **Host:** NixOS container on [`kelder`](../README.md) +- **nixpkgs:** `mine` + +## Role + +### Transmission + +`transmission_4` is bound to the VPN wait-online unit and uses AirVPN's forwarded peer port. Upload, +download and seed-ratio limits are configured in the service source. Downloads use +`/mnt/media/downloads/torrents`, backed by the host's `/mnt/storage/media`. + +### Media services + +Jackett, Radarr and Sonarr share the `media` group with a group-writable umask. Jellyfin uses the +host's bind-mounted `/dev/dri` with `intel-vaapi-driver` / `intel-ocl`; its user belongs to +`render`. + +## Network assignments + +See the consolidated [network assignments](../../../networking.md#box-assignments) table (this box: `kelder-acquisition`). + +## Networking + +- `internal` assignment (name `acquisition-ctr`) on the host's `ctrs` bridge, MTU 1460 to + match the site WAN. +- All non-site traffic goes over an AirVPN WireGuard tunnel (`vpn` netdev, AirVPN IE endpoint). + Policy rules keep traffic to and from the kelder prefixes on the main table and push everything + else through the VPN's dedicated table. +- An nftables input chain drops new TCP connections from the VPN interface except the + Transmission peer port; the web UI ports (9091 Transmission, 9117 Jackett, 7878 Radarr, + 8989 Sonarr, 8096 Jellyfin) are accepted from the site. When built as a dev VM, those ports + are forwarded to the host. +- Sonarr still needs the EOL .NET 6 runtime, allowed via + `nixpkgs.config.permittedInsecurePackages`. + +## Notable config files + +- [`nixos/boxes/kelder/containers/acquisition/default.nix`](../../../../nixos/boxes/kelder/containers/acquisition/default.nix) — services and users. +- [`nixos/boxes/kelder/containers/acquisition/networking.nix`](../../../../nixos/boxes/kelder/containers/acquisition/networking.nix) — AirVPN tunnel + firewall. diff --git a/docs/remote/kelder/containers/kelder-spoder.md b/docs/remote/kelder/containers/kelder-spoder.md new file mode 100644 index 0000000..c8d7d5a --- /dev/null +++ b/docs/remote/kelder/containers/kelder-spoder.md @@ -0,0 +1,50 @@ +# kelder-spoder + +The web container for the kelder site: Nextcloud plus an nginx (OpenResty) reverse proxy for +the site's services. + +- **Source:** [`nixos/boxes/kelder/containers/spoder/`](../../../../nixos/boxes/kelder/containers/spoder) +- **Host:** NixOS container on [`kelder`](../README.md) +- **nixpkgs:** `mine` + +## Role + +- **Nextcloud** (`nextcloud32`) at `cloud.hentai.engineer` (trusted alias + `cloud-local.hentai.engineer`), SQLite backend, data in `/mnt/storage/nextcloud` + (`/mnt/storage` is bind-mounted from the host). +- **nginx reverse proxy** (`openresty`) terminating TLS for the site's public vhosts, with a + wildcard ACME cert for `hentai.engineer` via Cloudflare DNS. The kelder host forwards + `http`/`https` to this container; uploads are unlimited (`clientMaxBodySize = 0`) for + Nextcloud's sake. + +## Network assignments + +See the consolidated [network assignments](../../../networking.md#box-assignments) table (this box: `kelder-spoder`). + +## Proxy vhosts + +All under `hentai.engineer`, each with a `*-local` alias: + +| vhost | Target | Auth | +| --- | --- | --- | +| `monitor` | `netdata` on the kelder host (:19999) | `htpasswd` | +| `kontent` | Jellyfin on `kelder-acquisition` (:8096, incl. websocket) | — | +| `torrents` | Transmission on `kelder-acquisition` (:9091) | `htpasswd` | +| `jackett` | Jackett on `kelder-acquisition` (:9117) | `htpasswd` | +| `radarr` | Radarr on `kelder-acquisition` (:7878) | `htpasswd` | +| `sonarr` | Sonarr on `kelder-acquisition` (:8989) | `htpasswd` | +| `cloud` | Nextcloud (local) | — | + +An `init_worker_by_lua` timer polls `v4.ident.me` periodically to track the site's public IP; it +feeds `localRedirect` rewrites (bounce public-IP clients to the `*-local` name) that +are currently **disabled** (commented out — Virgin Media filters DNS answers containing local +IPs, so the split doesn't work as intended). + +## Networking + +- `internal` assignment (name `spoder-ctr`) on the host's `ctrs` bridge, MTU 1420. + +## Notable config files + +- [`nixos/boxes/kelder/containers/spoder/default.nix`](../../../../nixos/boxes/kelder/containers/spoder/default.nix) — Nextcloud + ACME. +- [`nixos/boxes/kelder/containers/spoder/nginx.nix`](../../../../nixos/boxes/kelder/containers/spoder/nginx.nix) — reverse proxy vhosts. diff --git a/docs/sites/colony/README.md b/docs/sites/colony/README.md new file mode 100644 index 0000000..82de5bc --- /dev/null +++ b/docs/sites/colony/README.md @@ -0,0 +1,32 @@ +# colony + +The hosted dedicated server in Amsterdam (`ams1`) and the public-facing half of +the boxes: almost everything reachable from the internet lives here. + +- **Internal domain:** `ams1.int.nul.ie` (`lib.my.c.colony.domain`) +- **Public domain:** `nul.ie` — public services are published as `*.nul.ie` +- **Source:** [`nixos/boxes/colony/`](../../../nixos/boxes/colony) + +## Networking + +`colony` separates the host, VMs, `shill` containers and `whale2` OCI workloads onto dedicated +networks behind [`estuary`](estuary.md), which terminates the public addressing. The canonical +prefixes and routing overview are in the [`colony` section of networking.md](../../networking.md#colony). + +## Boxes + +| Box | Role | +|---|---| +| [`colony`](colony.md) | Physical VM host (AMD, KVM, LVM-thin, `borgthin` backups → rsync.net) | +| [`estuary`](estuary.md) | Edge router: WAN, firewall/NAT, DNS, BGP (AS211024), WireGuard | +| [`shill`](shill/README.md) | NixOS container host (most applications; per-container pages under `shill/`) | +| [`whale2`](whale2.md) | podman/OCI game-server host | +| [`git`](git.md) | Gitea + Gitea Actions runner | +| [`mail`](mail.md) | Debian VM running mailcow (not NixOS) | +| [`darts`](darts.md) | Third-party/customer VM (not NixOS) | + +The applications running on `shill` are listed on its own page — see +[shill/README.md](shill/README.md#containers). + +`mail` and `darts` are host-defined VMs whose guest operating systems are managed out of band; their +pages document only what this repository controls. diff --git a/docs/sites/colony/colony.md b/docs/sites/colony/colony.md new file mode 100644 index 0000000..291fe3e --- /dev/null +++ b/docs/sites/colony/colony.md @@ -0,0 +1,110 @@ +# colony + +The physical dedicated server in Amsterdam (`ams1`) and the VM host for +everything at the colony site. + +- **Source:** [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix) + (VM instances in [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix)) +- **Host:** bare metal (this *is* the physical box) +- **nixpkgs:** `mine-stable` + +## Role + +Bare-metal AMD host. It does little application work itself — its job is to run +the VMs and provide them with storage, networking and backups. + +### Virtualisation + +The `my.vms` module drives QEMU/KVM with `kvm-amd` and IOMMU enabled. Each `my.vms.instances` entry +becomes a `vm@` systemd service with UEFI, QMP/monitor sockets under `/run/vms//`, TAP +networking and optional `hostDevices` passthrough through `vfio-pci`. `estuary` receives the WAN NIC +this way. + +### Storage + +All three NVMe SSDs and all four SATA HDDs are physical volumes in one `main` VG. Separate pools +and standalone RAID LVs keep workloads on the appropriate media: + +| Layer | Physical layout | Main consumers | +|---|---|---| +| `nvme-tpool` | Thin-pool data is RAID 0 across the three NVMe SSDs; thin metadata is RAID 1 on two of them. The data itself has no redundancy. | Host `/nix` and `/persist`; VM system and persistence disks; fast data LVs such as `minio`, `oci`, `git`, `gitea-actions-cache`, `nix-cache` and `jam` | +| `hdds-tpool` | Thin-pool data is RAID 5 across the three 12 TB HDDs; thin metadata is RAID 1 on two NVMe SSDs. | `media`, passed to `shill`, and `backup`, mounted by the host at `/mnt/backup` | +| `darts-media` | Standalone RAID 5 LV spanning all four HDDs. | Bulk storage passed to `darts` | +| `darts-ext` | Standalone linear LV using the remaining capacity of the 18 TB HDD. | Expansion storage passed to `darts` | + +`media` and `backup` are thin-provisioned, so their virtual capacities are not additive physical +capacity and overcommit `hdds-tpool`. VM disks built with `lib.my.vm.disk` are named +`vm--` in `main`; `lib.my.vm.lvmDisk` attaches the named data LVs. + +The initrd activates only `colony-nix` and `colony-persist`. This avoids checking and activating all +of the storage before switching root; `lvm-activate-main.service` activates the remaining LVs before +local filesystems and VMs need them. + +### Backups + +`my.borgthin` job `main` snapshots host and VM persistence/data LVs into `/mnt/backup/main`. +`borgthin-rsync.service` copies the repository to rsync.net and `rsync-lvm-meta.service` sends the +LVM metadata; both run at idle priority after the Borg job. + +### Monitoring + +Netdata uses FreeIPMI while ignoring the VCCM sensor. The box also runs `smartd` with logs under +`/var/log/smartd`, `rasdaemon`, and `fstrim` before the backup job. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `colony`). + +## Hardware + +| Component | Inventory | +|---|---| +| Platform | ASRock Rack X570D4U server board | +| CPU | AMD Ryzen 9 5950X (16 cores / 32 threads) | +| Memory | 128 GiB | +| NVMe storage | Three 2 TB Samsung SSD 980 PRO devices providing the NVMe-backed LVM thin pool and data LVs | +| Bulk storage | Three 12 TB WD120EDBZ disks and one 18 TB WD180EDGZ disk for the bulk LVM volumes | +| Boot | SanDisk USB device holding the EFI system partition | +| Network / management | Two Intel I210 Gigabit Ethernet controllers, one passed through to `estuary`; ASPEED BMC graphics and console | + +## Networking + +- Two bridges: `base` (the colony base network, shared with `estuary`) and + `vms` (the VM network). Dummy interfaces (`base0`, `vms0`) keep the bridges + up in networkd's eyes so dependent VMs can start. +- `colony` sends RAs on `vms` (DNS = `estuary`'s base address) and carries + static routes for the downstream prefixes: `ctrs` via `shill`, `oci` via + `whale2`, plus the Tailscale, `qclk` and `jam` prefixes via `shill`. +- `estuary` is the default gateway (via the `base` bridge); `colony`'s own public-facing address is + its `internal` assignment (alt name `vm`). +- The customer VMs attach to dedicated TAP devices (`vm-mail`, `vm-darts`) + which are **not** bridged: networkd puts the point-to-point /32 + (`lib.my.c.colony.custRouting`) and the customer's IPv6 /64 on each, sends + RAs, and link-routes the customer's public /32 down the tap. +- `my.firewall` trusts the `vms` bridge, DNATs the shared + `lib.my.c.colony.firewallForwards` list for traffic addressed to `estuary`'s + public IP (so the port forwards also work from inside), and forwards the + customer prefixes through with minimal filtering ("trust for now"). + +## VMs + +Declared in `my.vms.instances` (`cpus`/`threads` are QEMU `smp` values): + +| VM | Cores | Threads | Memory | Network | Disks | +|---|---|---|---|---|---| +| `estuary` | 2 | 2 | 3 GiB | `base` | `esp` / `nix` / `persist` LVs + WAN NIC passthrough | +| `shill` | 12 | 2 | 40 GiB | `vms` | `esp` / `nix` / `persist` + `media` / `minio` / `nix-cache` / `jam` LVs | +| `whale2` | 8 | 2 | 16 GiB | `vms` | `esp` / `nix` / `persist` + `oci` LV | +| `git` | 12 | 2 | 40 GiB | `vms` | `esp` / `nix` / `persist` / `oci` + `git` / `gitea-actions-cache` LVs | +| `mail` | 3 | 2 | 6 GiB | `vm-mail` tap | `root` / `data` LVs | +| `darts` | 4 | 2 | 16 GiB | `vm-darts` tap | `root` LV + `darts-media` / `darts-ext` LVs | + +`estuary`, `shill`, `whale2` and `git` are NixOS systems with their own pages +(see the [README](README.md#boxes)); [`mail`](mail.md) and [`darts`](darts.md) have host-side +definitions here, but their guest operating systems are managed out of band. + +## Notable config files + +- [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix) — host hardware, networkd, firewall, backups. +- [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix) — `my.vms.instances` for all six VMs. +- [`nixos/modules/vms.nix`](../../../nixos/modules/vms.nix) — the `my.vms` module itself. diff --git a/docs/sites/colony/darts.md b/docs/sites/colony/darts.md new file mode 100644 index 0000000..9759968 --- /dev/null +++ b/docs/sites/colony/darts.md @@ -0,0 +1,37 @@ +# darts + +An opaque third-party/customer VM. Declared in `colony`'s `my.vms.instances` +but **not a NixOS system**: this repo knows nothing about what runs inside it +and doesn't manage it. + +- **Source (host-side only):** the `darts` instance in + [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix) + and the `90-vm-darts` network in + [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix) +- **Host:** VM on `colony` +- **nixpkgs:** not applicable (unmanaged guest; host-side definition uses `colony`'s `mine-stable`) + +## Role + +- Customer/dedicated VM, left alone beyond hosting and connectivity. +- A thin-provisioned `root` LV on the NVMe pool, plus `darts-media` (RAID 5 across the four HDDs) + and `darts-ext` (linear storage on the 18 TB HDD) in the `main` VG. See + [`colony`'s storage layout](colony.md#storage). + +## Network assignments + +This guest is not a NixOS system, so its host-routed addresses are not rows in the generated +[network assignments](../../networking.md#box-assignments) table. + +- Same customer-VM pattern as [`mail`](mail.md): dedicated unbridged TAP + (`vm-darts`), point-to-point address + (`custRouting.darts-vm`) on the host side, link-routed public /32 + `94.142.242.255`, and the IPv6 /64 `2a0e:97c0:4d2:2001::/64` with RAs. +- DNS: `darts-cust.ams1.int.nul.ie`. Like the other customer prefixes, its + inbound traffic is accepted by `estuary` without per-port filtering and + forwarded on by `colony`. + +## Notable config files + +- [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix) — VM definition. +- [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix) — host-side network and routing. diff --git a/docs/sites/colony/estuary.md b/docs/sites/colony/estuary.md new file mode 100644 index 0000000..7acdfd7 --- /dev/null +++ b/docs/sites/colony/estuary.md @@ -0,0 +1,164 @@ +# estuary + +The colony edge router and firewall — the box that holds colony's public IPs +and connects everything else at the site to the internet. + +- **Source:** [`nixos/boxes/colony/vms/estuary/`](../../../nixos/boxes/colony/vms/estuary) + (`default.nix`, `bgp.nix`, `dns.nix`, `bandwidth.nix`) +- **Host:** VM on `colony` (gets the WAN NIC by PCI passthrough) +- **nixpkgs:** `mine` + +## Role + +- **Edge routing / firewall / NAT** — owns the colony public IPv4/IPv6 assignments, NATs outbound + traffic, and port-forwards inbound services (see [Firewall and NAT](#firewall-and-nat)). +- **DNS** — PowerDNS authoritative server *and* recursor (see [DNS](#dns)). +- **BGP** — BIRD2 speaking AS211024 with upstreams, IXP route servers and + direct peers (see [BGP](#bgp)). +- **VPNs** — the `as211024` L2 VXLAN mesh plus three point-to-point WireGuard + tunnels (see [VPNs](#vpns)). +- **Misc** — `iperf3` server, netdata. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `estuary`). + +## WAN and IXP VLANs + +- `wan` — the passed-through `igb` NIC (9000 MTU, enlarged rings). It carries + the plain upstream uplink (static v4/v6 with gateways from the `internal` + assignment) plus the tagged `ifog` VLAN. +- `ifog` (VLAN 409) is an iFog QinQ transport that carries the IXP VLANs as + nested tags: + +| Interface | VLAN | IPv4 | IPv6 | Purpose | +|---|---|---|---|---| +| `frys-ix` | 701 | `185.1.160.196/23` | `2001:7f8:10f::3:3850:196/64` | Frys-IX peering LAN | +| `nl-ix` | 1845 | `193.239.116.145/22` | `2001:7f8:13::a521:1024:1/64` | NL-ix peering LAN | +| `fogixp` | 1147 | `185.1.147.159/24` | `2001:7f8:ca:1::159/64` | FogIXP peering LAN | +| `ifog-transit` | 702 | — | `2a0c:9a40:100f:370::2/64` | iFog IPv6 transit | + +The IXP interfaces run at 1500 MTU with DHCP/RA/LLDP off; an nftables `ixp` +chain rejects non-IP/ARP ethertypes in both directions. + +- `base` — colony base network; sends RAs and serves DNS to the site, and + routes the `vms`/`ctrs`/`oci`, Tailscale, `qclk`, `vip*` and customer + prefixes back via `colony`. +- `as211024` — the L2 mesh interface (see VPNs). + +## Firewall and NAT + +`my.firewall` (nftables). Inbound port forwards (`my.firewall.nat.forwardPorts`, +driven by the shared `lib.my.c.colony.firewallForwards` list): + +| Service | Forwarded to | +|---|---| +| HTTP/S, Matrix federation | `middleman` | +| Git | `git` | +| Game servers | OCI servers on `whale2`, `gam` | +| Tailscale | `waffletail` | +| `qclk` WireGuard | `qclk` | + +Besides the forwards, `extraRules` defines: + +- `routing-tcp` / `routing-udp` chains — the inbound allow-list for new + connections from `wan`/`as211024`/IXPs towards internal services (SSH + anywhere, otherwise per-service v4/v6 rules mirroring `firewallForwards`). +- `filter-routing` — applied to `wan`/`as211024`/IXPs → `base` forwards; + customer prefixes (`mail`/`darts` v4, `cust.v6`) are accepted wholesale, the + rest goes through the `routing-*` chains. +- SNAT: everything from `prefixes.all.v4` leaving non-`as211024` interfaces is + NATed to the public IP; the WireGuard tunnel prefixes get their own SNAT + addresses. +- DNS redirect: DNS traffic arriving at estuary's own public addresses is + redirected to port 5353 (the authoritative server) — see below. + +## DNS + +Both halves are PowerDNS ([`dns.nix`](../../../nixos/boxes/colony/vms/estuary/dns.nix)). + +### Authoritative + +`my.pdns.auth`, listening on `0.0.0.0:5353` / `[::]:5353`. Primary for +`ams1.int.nul.ie`, `100.10.in-addr.arpa` and the `2a0e:97c0:4d2::/48` reverse +zone. + +- Zone contents are largely generated from `allAssignments` + (`lib.my.dns.fwdRecords` / `ptrRecords` / `ptr6Records`); `ALIAS` records + (with `expand-alias`) point the zone apex at estuary itself. +- AXFR is allowed to HE.net's secondary (`216.218.133.2` / `2001:470:600::2`). +- `_acme-challenge` is a LUA `TXT` record answered from a file (DNS-01 issuance). +- Reached publicly via the NAT redirect of port 53 → 5353; the `base` side also + accepts DNS directly. + +### Recursor + +`my.pdns.recursor` (`pdns-recursor`), listening on localhost and the `base` +addresses, serving `prefixes.all` and the Tailscale prefixes. + +- Authoritative zones are forwarded back to `127.0.0.1:5353` (with NOTIFY + support, so changes show up immediately). +- A small Lua `preresolve` hook rewrites `nix-cache.nul.ie` → + `http.ams1.int.nul.ie` so cache traffic stays on-site. + +## BGP + +BIRD2 ([`bgp.nix`](../../../nixos/boxes/colony/vms/estuary/bgp.nix)) speaking **AS211024**: + +| Peer | ASN | Role | Where / notes | +|---|---|---|---| +| ColoClue | AS8283 | Upstream | `euNetworks` 2/3, v4+v6 | +| iFog | AS34927 | Upstream | IPv6 transit | +| Hurricane Electric | AS6939 | Upstream | IPv6 over Frys-IX | +| Frys-IX | AS56393 | IXP route server | | +| NL-ix | AS34307 | IXP route server | lower preference | +| FogIXP | AS47498 | IXP route server | | +| LUJE.net | AS212855 | Direct peer | ColoClue/Frys-IX/FogIXP + multihop labs | +| jurrian | AS212635 | Direct peer | | +| Meta | AS32934 | Direct peer | Frys-IX/NL-ix | +| Cloudflare | AS13335 | Direct peer | Frys-IX | +| Apple | AS714 | Direct peer | NL-ix | +| HE | AS6939 | Direct peer | Frys-IX (v4) | +| bgp.tools | AS212232 | Monitoring | multihop collector, `add paths` | + +Statics originate the site prefixes: the `vip*` ranges via `base`, the +ams1/internal v6 prefixes via `base`/`as211024`, and the home /48 towards the +home routers' `as211024` VIP. + +Currently disabled (commented out): efero transit over FogIXP ("not working so +well lately") and the NL-ix Cloudflare sessions. + +## VPNs + +### `as211024` L2 mesh + +Member alongside `river`/`stream`/`britway` (`my.vpns.l2`, the `l2mesh` module). +The mesh transport, crypto and addressing are shared fabric — see +[The AS211024 L2 mesh](../../networking.md#the-as211024-l2-mesh) in networking.md. + +### WireGuard tunnels + +Point-to-point tunnels terminated here as networkd `wireguard` netdevs (private +keys from agenix); each SNATs out its own interface address: + +| Tunnel | Port | Prefix | Notes | +|---|---|---|---| +| `kelder` | 51820 | — | to the remote `kelder` site; kelder's public `estuary` assignment is routed over the tunnel | +| `hillcrest` | 51822 | `prefixes.hillcrest.v4` (`10.100.5.0/30`) | /32 pair, estuary `.1` ↔ remote `.2` | +| `john-valorant` | 51823 | `prefixes.john-valorant.v4` (`10.100.5.4/30`) | /32 pair, estuary `.1` ↔ remote `.2` | + +## Bandwidth management + +[`bandwidth.nix`](../../../nixos/boxes/colony/vms/estuary/bandwidth.nix) implements a WAN shaper: a +token-bucket filter on `wan` (outbound) and on an `ifb-wan` IFB device that ingress traffic is +mirrored into (inbound), with +[`bandwidth.py`](../../../nixos/boxes/colony/vms/estuary/bandwidth.py) as a `bandwidth-limiter` +service that watches utilisation and can adjust the configured rate. **Currently disabled** — the +file is not in estuary's `imports` (only `dns.nix` and `bgp.nix` are), so no shaping is applied. + +## Notable config files + +- [`nixos/boxes/colony/vms/estuary/default.nix`](../../../nixos/boxes/colony/vms/estuary/default.nix) — system, networkd, firewall, WireGuard, mesh membership. +- [`nixos/boxes/colony/vms/estuary/bgp.nix`](../../../nixos/boxes/colony/vms/estuary/bgp.nix) — BIRD2 config. +- [`nixos/boxes/colony/vms/estuary/dns.nix`](../../../nixos/boxes/colony/vms/estuary/dns.nix) — PowerDNS auth + recursor. +- [`nixos/boxes/colony/vms/estuary/bandwidth.nix`](../../../nixos/boxes/colony/vms/estuary/bandwidth.nix) — WAN shaper (disabled, not imported). diff --git a/docs/sites/colony/git.md b/docs/sites/colony/git.md new file mode 100644 index 0000000..d518781 --- /dev/null +++ b/docs/sites/colony/git.md @@ -0,0 +1,61 @@ +# git + +The Gitea VM — source hosting and CI for the boxes (`git.nul.ie`). + +- **Source:** [`nixos/boxes/colony/vms/git/`](../../../nixos/boxes/colony/vms/git) + (`default.nix`, `gitea.nix`, `gitea-actions.nix`) +- **Host:** VM on `colony` +- **nixpkgs:** `mine` + +## Role + +### Gitea + +The Git forge at `git.nul.ie` (self-registration disabled), configured in +[`gitea.nix`](../../../nixos/boxes/colony/vms/git/gitea.nix). + +- Backed by PostgreSQL on [`colony-psql`](shill/containers/colony-psql.md) (waited on via + `lib.my.systemdAwaitPostgres`). +- LFS enabled; all object storage (incl. LFS and packages) is on MinIO at `s3.nul.ie` (bucket + `gitea`, on [`object`](shill/containers/object.md)) — the secret is spliced into `app.ini` at + startup. +- Mail goes out via `mail.nul.ie`, including the issue-reply incoming-mail poller. + +### Gitea Actions runner + +One Docker-mode instance (`main-docker`) on podman (privileged, `podman` network), configured in +[`gitea-actions.nix`](../../../nixos/boxes/colony/vms/git/gitea-actions.nix). + +- Labels for `node:24-trixie` and the self-built `git.nul.ie/dev/actions-ubuntu:26.04` images. +- Runs as a fixed `gitea-runner` user (not `DynamicUser`) so it can read its token; jobs have a + configured timeout. +- The action cache lives on a dedicated disk (`/var/cache/gitea-runner`). +- Executes the repo's own `.gitea/workflows/ci.yaml`. + +### nginx + +Terminates TLS for `git.nul.ie` (and a default vhost) and proxies to Gitea on `:3000`. ACME +(Let's Encrypt, production) issues `nul.ie` + `*.nul.ie` via the Cloudflare DNS-01 challenge. + +### podman + +Local container backend for the runner; `/var/lib/containers` is an XFS data disk, and the default +`10.88.0.0/16` podman subnet is allowed to forward. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `git`). + +## Storage + +- `/var/lib/gitea` — the `git` LV (repositories, config). +- `/var/cache/gitea-runner` — the `gitea-actions-cache` LV. +- `/var/lib/containers` — the `oci` LV (XFS with project quotas). Despite the + name this is local to the `git` VM and unrelated to `whale2`'s `oci` + network. + +## Notable config files + +- [`nixos/boxes/colony/vms/git/default.nix`](../../../nixos/boxes/colony/vms/git/default.nix) — VM config, nginx + ACME, podman, firewall. +- [`nixos/boxes/colony/vms/git/gitea.nix`](../../../nixos/boxes/colony/vms/git/gitea.nix) — Gitea itself. +- [`nixos/boxes/colony/vms/git/gitea-actions.nix`](../../../nixos/boxes/colony/vms/git/gitea-actions.nix) — the Actions runner. diff --git a/docs/sites/colony/mail.md b/docs/sites/colony/mail.md new file mode 100644 index 0000000..8d2ba0b --- /dev/null +++ b/docs/sites/colony/mail.md @@ -0,0 +1,45 @@ +# mail + +A Debian VM running [mailcow](https://mailcow.email/) (`mail.nul.ie`) — the +mail server for `nul.ie`. Declared in `colony`'s `my.vms.instances` but **not +a NixOS system**: everything inside the VM is configured out of band. + +- **Source (host-side only):** the `mail` instance in + [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix) + and the `90-vm-mail` network in + [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix) +- **Host:** VM on `colony` +- **nixpkgs:** not applicable (Debian guest; host-side definition uses `colony`'s `mine-stable`) + +## Role + +- Runs the full mailcow stack (Postfix/Dovecot/SOGo/Rspamd) for `nul.ie`. + Other colony services send through it as `mail.nul.ie` (e.g. Gitea, and the + disabled Mastodon config). +- `root` and `data` LVM disks; the `vm-mail-data` LV is included in `colony`'s `borgthin` backups. + +## Network assignments + +This guest is not a NixOS system, so its host-routed addresses are not rows in the generated +[network assignments](../../networking.md#box-assignments) table. + +### Link and addressing + +The VM attaches to a dedicated, unbridged TAP (`vm-mail`). `colony` puts the point-to-point +`custRouting.mail-vm` address on the host side, link-routes public +`94.142.241.227/32` down the TAP, and advertises `2a0e:97c0:4d2:2000::/64` with RAs. + +- DNS: `mail-vm.ams1.int.nul.ie` (and `mail.nul.ie` publicly, incl. the PTR in + estuary's reverse zone). `estuary` accepts traffic to the customer prefixes + without per-port filtering; `colony` forwards it on ("trust for now"). + +## Notes + +- ACME certificates are issued on `middleman` and pushed to the VM over SSH + (`acme@mail.nul.ie mailcow-ssl-reload`, key `middleman/mailcow-ssh.key`); + the VM's SSH host key is pinned at `.keys/mail-vm-host.pub`. + +## Notable config files + +- [`nixos/boxes/colony/vms/default.nix`](../../../nixos/boxes/colony/vms/default.nix) — VM definition. +- [`nixos/boxes/colony/default.nix`](../../../nixos/boxes/colony/default.nix) — host-side network and routing. diff --git a/docs/sites/colony/shill/README.md b/docs/sites/colony/shill/README.md new file mode 100644 index 0000000..55ed8e1 --- /dev/null +++ b/docs/sites/colony/shill/README.md @@ -0,0 +1,84 @@ +# shill + +The colony NixOS container host — most colony applications run as +`systemd-nspawn` containers on this VM. + +- **Source:** [`nixos/boxes/colony/vms/shill/`](../../../../nixos/boxes/colony/vms/shill) + (`default.nix`, `containers-ext.nix`, `containers/`) +- **Host:** VM on `colony` +- **nixpkgs:** `mine` + +## Role + +### Container hosting + +`my.containers.instances` runs the colony containers on the `ctrs` bridge. Each is a full NixOS +system rendered through `my.asContainer` and deployed as a profile on `shill`; containers are not +standalone deploy targets. The shared container module supplies the nspawn units, `/persist` and +store binds. + +### Shared storage + +LVM-backed host volumes are bind-mounted into the consumers: `/mnt/media` is read-only in +`middleman` and read-write in `jackflix`; `/mnt/minio` and `/mnt/nix-cache` are read-write in +`object`. + +### Routing + +`shill` routes between `vms` and `ctrs`, advertises `estuary` as DNS on `ctrs`, and routes Tailscale +through `waffletail` and the `qclk` prefix through `qclk`. It applies the shared `firewallForwards` +DNAT for `estuary`'s public IP; a connection-mark-based SNAT rule keeps replies symmetric. + +### Host tuning + +The box has a larger conntrack table and ephemeral-port range for high connection counts. Netdata +listens on port 19999. + +## Network assignments + +See the consolidated [network assignments](../../../networking.md#box-assignments) table (this box: `shill`). + +## Containers + +Defined under +[`shill/containers/`](../../../../nixos/boxes/colony/vms/shill/containers) and +wired up in `shill`'s `my.containers.instances`. The generated +[network assignments](../../../networking.md#box-assignments) table is the source of truth for +their current addresses. Each container has its own page: + +| Container | Role | +|---|---| +| [`middleman`](containers/middleman.md) | Reverse proxy, ACME, nginx-sso, librespeed | +| [`vaultwarden`](containers/vaultwarden.md) | Password manager | +| [`colony-psql`](containers/colony-psql.md) | Shared PostgreSQL (14) | +| [`chatterbox`](containers/chatterbox.md) | Matrix Synapse + bridges | +| [`jackflix`](containers/jackflix.md) | Media stack | +| [`object`](containers/object.md) | MinIO, Harmonia Nix cache, Sharry, HedgeDoc, wastebin | +| [`toot`](containers/toot.md) | Bluesky PDS (Mastodon disabled) | +| [`waffletail`](containers/waffletail.md) | Tailscale subnet router / exit node | +| [`qclk`](containers/qclk.md) | WireGuard management appliance | +| [`gam`](containers/gam.md) | Terraria server | + +### `jam` + +A one-off: [`containers-ext.nix`](../../../../nixos/boxes/colony/vms/shill/containers-ext.nix) +runs a raw `systemd-nspawn` container (not a `my.containers` instance, not +NixOS) with its root on the `jam` LV, private user namespaces and a `ve-jam` +veth. It gets the `jam` customer prefix (`prefixes.jam`, `jam-cust` in DNS) +and SSH is forwarded to it from `shill`'s public IP port 60022. + +## Notes + +- `nix.settings.substituters` is forced to just `https://cache.nixos.org` — + `shill` sits next to the S3 cache on `object`, so it doesn't use it. +- [`hercules.nix`](../../../../nixos/boxes/colony/vms/shill/hercules.nix) + (Hercules CI agent + the `nix-cache-gc` timer for the S3 binary cache) + exists but is **currently disabled**: the file is not imported by + `shill/default.nix`. + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/default.nix`](../../../../nixos/boxes/colony/vms/shill/default.nix) — VM config, networkd, firewall, `my.containers.instances`. +- [`nixos/boxes/colony/vms/shill/containers/default.nix`](../../../../nixos/boxes/colony/vms/shill/containers/default.nix) — container imports. +- [`nixos/boxes/colony/vms/shill/containers-ext.nix`](../../../../nixos/boxes/colony/vms/shill/containers-ext.nix) — the `jam` nspawn container. +- [`nixos/modules/containers.nix`](../../../../nixos/modules/containers.nix) — the `my.containers` module. diff --git a/docs/sites/colony/shill/containers/chatterbox.md b/docs/sites/colony/shill/containers/chatterbox.md new file mode 100644 index 0000000..cb1fa2e --- /dev/null +++ b/docs/sites/colony/shill/containers/chatterbox.md @@ -0,0 +1,41 @@ +# chatterbox + +The Matrix homeserver for `nul.ie` (Synapse) and its bridges to other chat networks. +[middleman](middleman.md) fronts it as `matrix.nul.ie` for clients and on `:8448` for +federation. + +- **Source:** [`shill/containers/chatterbox.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/chatterbox.nix) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +### Synapse + +`matrix-synapse` serves `nul.ie` at `https://matrix.nul.ie`, with Element at `element.nul.ie`. +Port 8008 carries client and federation traffic with forwarded headers, while a localhost manhole +uses port 9000. Registration and guest access are disabled; uploads, dynamic thumbnails and URL +previews are enabled, with preview fetching restricted to [middleman](middleman.md). + +### Bridges + +The box runs heisenbridge for IRC, `mautrix-whatsapp`, and two `mautrix-meta` instances for +Messenger and Instagram. The mautrix bridges use [colony-psql](colony-psql.md), default to +end-to-end encryption, and share the secret `doublepuppet.yaml` registration for double puppeting +onto `nul.ie`. The firewall admits ports 8008 and 8009 in addition to netdata. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `chatterbox`). + +## Notes + +- Synapse's real database config lives in the `chatterbox/synapse.yaml` age secret — options + only merge at the top level, so the base config carries a dummy `sqlite3` block to satisfy + the module defaults. The signing key is also an age secret. +- `olm-3.2.16` is allowed via `permittedInsecurePackages` (a nixpkgs E2EE library issue). +- The bridge services get `ffmpeg` on their `PATH` for GIF→video conversion. + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/chatterbox.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/chatterbox.nix) — container definition, Synapse settings and all bridge configuration diff --git a/docs/sites/colony/shill/containers/colony-psql.md b/docs/sites/colony/shill/containers/colony-psql.md new file mode 100644 index 0000000..0983a4b --- /dev/null +++ b/docs/sites/colony/shill/containers/colony-psql.md @@ -0,0 +1,39 @@ +# colony-psql + +The shared PostgreSQL instance for colony services. Rather than each service running its own +database, the containers (and the `git` VM) connect here over the `ctrs` network. + +- **Source:** [`shill/containers/colony-psql.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/colony-psql.nix) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +- **PostgreSQL 14** with TCP/IP enabled, reachable from the whole colony address space using `md5` + authentication. The firewall allows `5432`. +- Local `peer` auth maps `postgres`, `root`, `netdata` and `dev` to the `postgres` superuser via + the ident map. +- **netdata** with the Python PostgreSQL collector. +- Consumers wait for the database to accept connections with the `lib.my.systemdAwaitPostgres` + helper (e.g. `sharry`, `atticd`, `mastodon-init-db`, and `middleman`'s nginx as a DNS + bootstrap hack). + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `colony-psql`). + +The assignment also has the alt name `colony-psql` (no `-ctr` suffix), which is what consumers +use as the database hostname. + +## Consumers + +- [object](object.md) — `sharry` and `hedgedoc` (and `atticd` when enabled) over + `colony-psql:5432` +- [toot](toot.md) — Mastodon's database (Mastodon currently disabled) +- [chatterbox](chatterbox.md) — the mautrix bridges (WhatsApp, Messenger, Instagram) via + Postgres URIs in their secret env files +- `git` VM — Gitea + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/colony-psql.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/colony-psql.nix) — container definition and PostgreSQL configuration diff --git a/docs/sites/colony/shill/containers/gam.md b/docs/sites/colony/shill/containers/gam.md new file mode 100644 index 0000000..750c4cd --- /dev/null +++ b/docs/sites/colony/shill/containers/gam.md @@ -0,0 +1,29 @@ +# gam + +A game-server container currently dedicated to Terraria. + +- **Source:** [`shill/containers/gam.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/gam.nix) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +Runs lightweight game servers directly as NixOS services, rather than as OCI containers on +[`whale2`](../../whale2.md). + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `gam`). + +## Terraria server + +`services.terraria` runs the dedicated server with its world at +`/var/lib/terraria/NotWorld.wld`. It creates large worlds automatically, uses the MOTD +"sup gamers", and disables UPnP. Additional settings such as the password come from the +`gam/terraria.conf` age secret used as the service configuration file. + +`openFirewall` is enabled, and `estuary` forwards TCP and UDP port `7777` to the container. + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/gam.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/gam.nix) — container definition and the Terraria service diff --git a/docs/sites/colony/shill/containers/jackflix.md b/docs/sites/colony/shill/containers/jackflix.md new file mode 100644 index 0000000..d2ec508 --- /dev/null +++ b/docs/sites/colony/shill/containers/jackflix.md @@ -0,0 +1,56 @@ +# jackflix + +The media stack — acquisition, library, streaming and photos. Torrent traffic is routed through +an AirVPN WireGuard tunnel so downloads only flow while the VPN is up. + +- **Source:** [`shill/containers/jackflix/`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix) + (`default.nix`, `networking.nix`) +- **Host:** NixOS container on [`shill`](../README.md) (bind-mounts `/mnt/media` read-write) +- **nixpkgs:** `mine` + +## Role + +| Service | Port | Purpose | +| --- | --- | --- | +| Jellyfin | `8096` | streaming, published as `jackflix.nul.ie` | +| Transmission | `9091` | BitTorrent client (`transmission_4`), published as `torrents.nul.ie` (SSO) | +| Jackett | `9117` | indexer aggregator, `jackett.nul.ie` (SSO) | +| FlareSolverr | — | Cloudflare challenge solver for Jackett | +| Radarr | `7878` | movies, `radarr.nul.ie` (SSO) | +| Sonarr | `8989` | TV, `sonarr.nul.ie` (SSO) | +| Jellyseerr (`seerr`) | `5055` | request portal, `gib.nul.ie` (`openFirewall` on) | +| PhotoPrism | `2342` | photos, `photos.nul.ie`; password auth, sqlite DB, originals/import under `/mnt/media/photoprism` | +| copyparty | `3923` | file sharing, `stuff.nul.ie`; serves `/mnt/media/public` (read-only to everyone) and `/priv` → `/mnt/media/stuff` (admin for `dev`), share creation, indexing (`e2dsa`/`e2t`), file-magic checks | + +All published through [middleman](middleman.md) as shown. A shared `media` group plus a group-writable +umask on Radarr/Sonarr gives the apps coordinated access to the media volume. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `jackflix`). + +## VPN download path + +[`networking.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix) +defines a `vpn` WireGuard netdev to **AirVPN NL** using a key and PSK from age secrets: + +- Policy routing keeps colony traffic on the main table while everything else falls through to the + VPN's dedicated table, so the services stay reachable on the `ctrs` network while outbound + torrent traffic exits via AirVPN. `DNSDefaultRoute` is disabled on `host0`; the VPN provides DNS. +- `transmission` and `jackett` `bindsTo` `systemd-networkd-wait-online@vpn.service` — they only + run while the tunnel is up. +- AirVPN forwards a peer port to Transmission (`peer-port`); the firewall accepts it and drops + other new inbound TCP from `vpn`, while non-VPN input is limited to the service ports + (netdata, Transmission, Jackett, Radarr, Sonarr, Jellyfin, PhotoPrism) plus copyparty's `3923` + from the base config and Jellyseerr's `5055`. + +## Storage + +Media lives on the shared `/mnt/media` volume (bind-mounted read-write from `shill`); Transmission +downloads into `/mnt/media/downloads/torrents` with a `.incomplete` directory and configured +bandwidth and seed-ratio limits. + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/jackflix/default.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/default.nix) — container definition and the media services +- [`nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix) — AirVPN WireGuard netdev, policy routing and VPN firewall rules diff --git a/docs/sites/colony/shill/containers/middleman.md b/docs/sites/colony/shill/containers/middleman.md new file mode 100644 index 0000000..79be915 --- /dev/null +++ b/docs/sites/colony/shill/containers/middleman.md @@ -0,0 +1,96 @@ +# middleman + +The front-end reverse proxy for the colony's public web services — the single ingress that +`estuary` DNATs HTTP/HTTPS (and Matrix federation on `:8448`) to. Terminates TLS with wildcard +certificates it issues itself, provides nginx-sso for gated vhosts, and runs a librespeed +backend. + +- **Source:** [`shill/containers/middleman/`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman) + (`default.nix`, `vhosts.nix`) +- **Host:** NixOS container on [`shill`](../README.md) (`my.containers` ephemeral nspawn on the + `ctrs` bridge; bind-mounts `/mnt/media` read-only for the static file vhosts) +- **nixpkgs:** `mine` + +## Role + +### nginx + +The reverse proxy enables `vts`, `fancyindex`, Brotli, kTLS and a proxy cache. Its dynamic resolver +points at `estuary`, allowing upstreams named under `ams1.int.nul.ie` to resolve again at runtime. +All vhosts live in +[`vhosts.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix). nginx +also waits for `colony-psql` through `systemdAwaitPostgres`, avoiding an early-boot DNS stall. + +### ACME + +`middleman` issues certificates for its own vhosts; it is not a shared CA for other boxes. + +- `ams1.int.nul.ie` and its wildcard use a lego `exec` challenge that SSHes to + `pdns-file-records@estuary-vm`. This is the default `useACMEHost` certificate internally. +- `nul.ie`, `*.nul.ie` and `*.s3.nul.ie` use Cloudflare DNS. A `postRun` hook copies renewed + material to the `mail` VM and runs `mailcow-ssl-reload` there. +- Renewal reloads nginx; the `acme` group owns the secret files and includes the nginx user. + +### nginx-sso + +The `generic` SSO instance at `sso.nul.ie` uses Google OAuth by default and also offers a simple +username/password provider. Its cookie domain is `.nul.ie`; gated vhosts include the generated +`server-generic.conf` / `location-generic.conf` snippets from `/etc/nginx/includes/sso/`. + +### librespeed + +The frontend and backend are published as `speed.nul.ie` and `librespeed.ams1.int.nul.ie`, both +proxied to `localhost:8989`. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `middleman`). + +The firewall allows `http`, `https` and `8448` (Matrix federation). A small nftables SNAT rule +rewrites outbound IPv6 to the container's own address on `host0`. + +## Published vhosts + +Everything is under `*.nul.ie` with the public wildcard cert unless noted; defaults applied to +all vhosts are `onlySSL`, kTLS and HTTP/2. "SSO" = gated behind nginx-sso (`generic` instance). + +| Host | Upstream | Notes | +| --- | --- | --- | +| `nul.ie` (`_`, default) | static | landing page (`index.html`, CV PDF, SSH pubkey); serves Matrix `.well-known`s and redirects `webfinger`/`nodeinfo`/`host-meta` → `toot.nul.ie`, `atproto-did` → `pds.nul.ie`; `forceSSL` (plain HTTP redirects to HTTPS) | +| `localhost` | — | loopback-only VTS status page at `/status` (scraped by netdata); plain HTTP | +| `sso.nul.ie` | `localhost:8082` | nginx-sso endpoint | +| `netdata-colony.nul.ie` | `.ams1.int.nul.ie:19999` | netdata fan-out over `vm`, `fw`, `ctr`, `oci`, `http`, `jackflix-ctr`, `chatterbox-ctr`, `colony-psql-ctr`; **SSO** | +| `pass.nul.ie` | `vaultwarden-ctr:8080` | [vaultwarden](vaultwarden.md); `/notifications/hub` proxied with websockets | +| `matrix.nul.ie` | `chatterbox-ctr:8008` | [chatterbox](chatterbox.md) Synapse client + federation; also listens on `:8448` as federation `default_server`; `= /` redirects to Element; serves Matrix `.well-known`s | +| `element.nul.ie` | static `element-web` | Element configured for the `nul.ie` homeserver | +| `torrents.nul.ie` | `jackflix-ctr:9091` | Transmission ([jackflix](jackflix.md)); **SSO** | +| `jackett.nul.ie` | `jackflix-ctr:9117` | **SSO** | +| `radarr.nul.ie` | `jackflix-ctr:7878` | **SSO**; websockets | +| `sonarr.nul.ie` | `jackflix-ctr:8989` | **SSO**; websockets | +| `gib.nul.ie` | `jackflix-ctr:5055` | Jellyseerr requests | +| `jackflix.nul.ie` | `jackflix-ctr:8096` | Jellyfin; `/socket` websockets; `/` redirects to `/web/` | +| `toot.nul.ie` | `toot-ctr:80` | Mastodon — **upstream currently disabled**, see [toot](toot.md) | +| `pds.nul.ie` | `toot-ctr:3000` | Bluesky PDS ([toot](toot.md)); websockets | +| `share.nul.ie` | `object-ctr:9090` | Sharry ([object](object.md)); websockets | +| `stuff.nul.ie` | `jackflix-ctr:3923` | copyparty | +| `public.nul.ie` (+ alias `p.nul.ie`) | static `/mnt/media/public` | fancyindex file listing; `addSSL` so plain HTTP also works | +| `mc-map.nul.ie` | `simpcraft-oci:8100` | Minecraft map (OCI container on [`whale2`](../../whale2.md#game-servers)) | +| `mc-rail.nul.ie` | `simpcraft-oci:3876` | Minecraft railway map ([`whale2`](../../whale2.md#game-servers)) | +| `mc-map-kink.nul.ie` | `kinkcraft-oci:8100` | Minecraft map ([`whale2`](../../whale2.md#game-servers)) | +| `speed.nul.ie` | `localhost:8989` | librespeed | +| `librespeed.ams1.int.nul.ie` | `localhost:8989` | librespeed on the internal domain (internal wildcard cert) | +| `md.nul.ie` | `object-ctr:3000` | HedgeDoc; websockets | +| `pb.nul.ie` | `object-ctr:8088` | wastebin | +| `photos.nul.ie` | `jackflix-ctr:2342` | PhotoPrism; websockets | +| `pront.nul.ie` | `stream-hi.h.nul.ie:5000` | OctoPrint on the home network ([`stream`](../../../home/stream.md)); `/webcam/` → `:5050`; **SSO** | +| `hass.nul.ie` | `hass-ctr.h.nul.ie:8123` | [Home Assistant](../../../home/sfh/containers/hass.md) (home network); websockets | +| `hass-john.nul.ie` | `john-valorant-tun.ams1.int.nul.ie:8123` | remote Home Assistant over the point-to-point tunnel; websockets | +| `minio.nul.ie` | `object-ctr:9001` | MinIO console; `/ws` websockets | +| `s3.nul.ie` (+ `*.s3.nul.ie`) | `object-ctr:9000` | MinIO S3 API (virtual-host style via the `*.s3` wildcard cert); `/gitea/packages/` has a hack forcing the correct `Content-Type` for Docker image manifests | +| `nix-cache.nul.ie` | `object-ctr:5000` | Harmonia Nix binary cache; `.narinfo`/`nar/`/`serve/` paths get immutable `Cache-Control`/`Expires` headers | + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/middleman/default.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/default.nix) — container definition: nginx, ACME, nginx-sso, librespeed, secrets +- [`nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix) — all virtual hosts, the SSO include helpers, and the `.well-known` tree +- [`nixos/boxes/colony/vms/shill/containers/middleman/default.html`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/default.html) — default vhost landing page diff --git a/docs/sites/colony/shill/containers/object.md b/docs/sites/colony/shill/containers/object.md new file mode 100644 index 0000000..4ca16c7 --- /dev/null +++ b/docs/sites/colony/shill/containers/object.md @@ -0,0 +1,39 @@ +# object + +Object storage and the Nix binary cache, plus a few small self-hosted web apps (Sharry, +HedgeDoc, wastebin). + +- **Source:** [`shill/containers/object.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/object.nix) +- **Host:** NixOS container on [`shill`](../README.md) (bind-mounts `/mnt/minio` and + `/mnt/nix-cache` read-write) +- **nixpkgs:** `mine` + +## Role + +| Service | Port | Purpose | +| --- | --- | --- | +| MinIO | `9000` (S3) / `9001` (console) | S3-compatible object storage, `s3.nul.ie` + `*.s3.nul.ie` (virtual-host style via `MINIO_DOMAIN`), console at `minio.nul.ie`; region `eu-central-1`; data on the `/mnt/minio` XFS volume | +| Harmonia | `5000` | Nix binary cache at `nix-cache.nul.ie` — `harmonia-dev` cache serves `shill`'s `/nix/store` out of a dedicated store view rooted at `/var/lib/harmonia` (bind-mounted from `/mnt/nix-cache`), signed with the `nix-cache.key` secret; a `harmonia` user with authorized keys exists for cache pushes | +| Sharry | `9090` | file sharing at `share.nul.ie`; Postgres on [colony-psql](colony-psql.md), files stored in the `share` MinIO bucket; fixed `dev` account + invite signup; mail via `mail.nul.ie`; configured share-size limit | +| HedgeDoc | `3000` | collaborative markdown notes at `md.nul.ie`; Postgres on [colony-psql](colony-psql.md); anonymous edits but no anonymous notes, email login, no open email registration | +| wastebin | `8088` | pastebin at `pb.nul.ie` | +| atticd | `8069` | **currently disabled** (`services.atticd.enable = false`) — an alternative Nix cache that would store locally and sit behind `nix-cache.nul.ie`; config (including the `object/atticd.env` secret) is kept around | + +Everything public is fronted by [middleman](middleman.md) (see its vhost table). The +`minio-client` is installed and the user's `~/.mc/config.json` points at an age-secret config. +`minio-2025-10-15T17-29-55Z` is allowlisted via `permittedInsecurePackages` (flagged as a TODO). + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `object`). + +## Backing services + +- [colony-psql](colony-psql.md) — Sharry and HedgeDoc databases (atticd too, when enabled). +- MinIO buckets back other boxes' services: Gitea LFS/packages (with the `middleman` MIME hack + for Docker manifests), Mastodon's `mastodon` bucket and the Bluesky PDS `pds` bucket on + [toot](toot.md), and Sharry's `share` bucket. + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/object.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/object.nix) — container definition and all services diff --git a/docs/sites/colony/shill/containers/qclk.md b/docs/sites/colony/shill/containers/qclk.md new file mode 100644 index 0000000..b7d5bdc --- /dev/null +++ b/docs/sites/colony/shill/containers/qclk.md @@ -0,0 +1,34 @@ +# qclk + +A WireGuard management appliance for the `qclk` network — it terminates the `management` +tunnel and routes/NATs the `qclk` prefix. No service daemon is currently defined in the config; +the container provides the network plumbing and opens the API port. + +- **Source:** [`shill/containers/qclk/`](../../../../../nixos/boxes/colony/vms/shill/containers/qclk) + (`default.nix`) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +- **WireGuard `management` interface** — listens on UDP `51821` (`lib.my.c.colony.qclk.wgPort`, + allowed through the firewall; `estuary` port-forwards it here) with the private key from the + `qclk/wg.key` age secret. Managed devices are static peers, each pinned to its own address in the + `qclk` prefix; the peer list currently has a single entry. +- **Routing/NAT** — `shill` routes the `qclk` prefix to this container, and outbound traffic from + `host0` into `management` is + SNATed to the container's `qclk` address. Forwarding into `management` is accepted from the + AS211024 trusted IPv4 ranges (`lib.my.c.as211024.trusted.v4`). +- **API port** — TCP `8080` is accepted on the `management` interface (`apiPort`), but note + `services = { }`: whatever serves the qclk API is not defined in this configuration today. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `qclk`). + +Two assignments: `internal` on the `ctrs` network like the other containers, and `qclk` on the +`management` WireGuard interface (IPv4 only, no DNS name). + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/qclk/default.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/qclk/default.nix) — container definition: WireGuard netdev, peer list and firewall rules diff --git a/docs/sites/colony/shill/containers/toot.md b/docs/sites/colony/shill/containers/toot.md new file mode 100644 index 0000000..a7aac97 --- /dev/null +++ b/docs/sites/colony/shill/containers/toot.md @@ -0,0 +1,47 @@ +# toot + +Federated-social container. Despite the name, the only service actually running is a **Bluesky +PDS** — the Mastodon instance ("toots") is **currently disabled**. + +- **Source:** [`shill/containers/toot.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/toot.nix) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +### Bluesky PDS + +The active service listens on port 3000 as `pds.nul.ie`, fronted by [middleman](middleman.md), +which also redirects `/.well-known/atproto-did` here. It requires invites, applies an upload-size +limit, and stores blobs in [object](object.md)'s `pds` MinIO bucket. Federation uses the standard +Bluesky services and crawlers. `toot/pds.env` supplies secrets, including S3 credentials, and mail +comes from `pds@nul.ie`. + +### Mastodon + +Mastodon is disabled with `services.mastodon.enable = false`, but its configuration remains: + +- `nul.ie` is the local domain and `toot.nul.ie` the web domain. +- PostgreSQL runs on [colony-psql](colony-psql.md), with local Redis and SMTP through `mail.nul.ie`. +- Media uses the `mastodon` MinIO bucket, a configured streaming-process pool, and periodic cleanup. +- [middleman](middleman.md) still proxies the dead vhost and `.well-known` endpoints. + +The removed `otpSecretFile` option must be addressed before the service can return. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `toot`). + +The firewall allows `http` (the Mastodon nginx vhost) and the PDS port `3000` besides netdata. + +## Notes + +- The local nginx still carries the Mastodon virtual host (`toot.nul.ie`) with proxy-header + overrides for being behind `middleman` — part of the preserved-but-disabled Mastodon setup. +- `mastodon-init-dirs` appends the S3 secret key to Mastodon's `.secrets_env` (the module has no + option for a secret-key file), and `mastodon-init-db` waits for `colony-psql` — moot while + Mastodon is disabled. + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/toot.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/toot.nix) — container definition; active PDS config and the preserved (disabled) Mastodon config diff --git a/docs/sites/colony/shill/containers/vaultwarden.md b/docs/sites/colony/shill/containers/vaultwarden.md new file mode 100644 index 0000000..e959fe0 --- /dev/null +++ b/docs/sites/colony/shill/containers/vaultwarden.md @@ -0,0 +1,33 @@ +# vaultwarden + +[Vaultwarden](https://github.com/dani-garcia/vaultwarden), a Bitwarden-compatible password +manager, published as `pass.nul.ie` through [middleman](middleman.md). + +- **Source:** [`shill/containers/vaultwarden.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/vaultwarden.nix) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +- **vaultwarden** — HTTP on `[::]:8080`, WebSocket notifications on `3012` (both allowed through + the firewall). Web vault enabled, signups disabled, Bitwarden push notifications enabled + (`PUSH_ENABLED`). `DOMAIN` is `https://pass.nul.ie`. +- **SMTP** via `mail.nul.ie:587` (STARTTLS) as `pass@nul.ie`; credentials and other sensitive + settings come from the `vaultwarden/config.env` age secret. +- **Backups** — a `borgbackup` job pushes `/var/lib/vaultwarden` to rsync.net + using a repokey-encrypted repository; its passphrase and SSH key come from secrets, and the job + keeps daily, weekly and monthly archives according to its configured retention policy. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `vaultwarden`). + +## Persistence + +`/var/lib/vaultwarden` is persisted through `my.tmproot.persistence` — like the other +`shill` containers the root is ephemeral and real state lives under `/persist` (bind-mounted +from the host's `/persist/containers/vaultwarden`). + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/vaultwarden.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/vaultwarden.nix) — container definition, service config and the borgbackup job diff --git a/docs/sites/colony/shill/containers/waffletail.md b/docs/sites/colony/shill/containers/waffletail.md new file mode 100644 index 0000000..326c90a --- /dev/null +++ b/docs/sites/colony/shill/containers/waffletail.md @@ -0,0 +1,34 @@ +# waffletail + +The colony Tailscale node: a subnet router and exit node that advertises the colony prefixes +into the tailnet. + +- **Source:** [`shill/containers/waffletail.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/waffletail.nix) +- **Host:** NixOS container on [`shill`](../README.md) +- **nixpkgs:** `mine` + +## Role + +### Tailscale + +The node authenticates to `hs.nul.ie` with the secret `tailscale-auth.key`. It disables Tailscale's +netfilter management, advertises itself as an exit node and advertises the colony IPv4/IPv6 ranges, +but does not accept routes. UDP port 41641 is open and forwarded here by `estuary`. + +### Routing and firewall + +`shill` routes the Tailscale prefixes to this container. +The repository's nftables rules trust `tailscale0`, permit colony-sourced forwarding into the +tailnet, and SNAT tailnet traffic leaving through `host0` unless its destination is already within +colony. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `waffletail`). + +Two assignments: `internal` on the `ctrs` network like the other containers, and `tailscale` for +its addresses on the tailnet itself (no DNS name). + +## Notable config files + +- [`nixos/boxes/colony/vms/shill/containers/waffletail.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/waffletail.nix) — container definition, Tailscale setup and forward/NAT rules diff --git a/docs/sites/colony/whale2.md b/docs/sites/colony/whale2.md new file mode 100644 index 0000000..a6b0c36 --- /dev/null +++ b/docs/sites/colony/whale2.md @@ -0,0 +1,80 @@ +# whale2 + +The colony podman/OCI host, dedicated to game servers (kept off `shill` so +container churn and resource use stay isolated). + +- **Source:** [`nixos/boxes/colony/vms/whale2/`](../../../nixos/boxes/colony/vms/whale2) + (`default.nix`, `valheim.nix`, `minecraft/`, `enshrouded.nix`) +- **Host:** VM on `colony` +- **nixpkgs:** `mine` + +## Role + +### Container runtime + +OCI containers run under podman (`virtualisation.oci-containers`) with the netavark backend and +`firewall_driver = "none"`, leaving firewall management to `my.firewall`. + +### Routable game servers + +Each server gets an address from `extraAssignments` (`valheim-oci`, `simpcraft-oci`, …) on the +`colony` netavark network. That network is backed by the `oci` interface and `prefixes.oci` v4/v6 +ranges; `lib.my.dockerNetAssignment` supplies the address through `--network=colony:ip=…`. +`estuary` forwards the public game ports, while IPv6 reaches the containers directly. + +### Storage + +`/var/lib/containers` is a dedicated XFS disk with project quotas. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `whale2`). + +## Game servers + +The OCI containers are documented here (they have no pages of their own). +Their per-container `extraAssignments` on the `oci` network are listed in the generated +[network assignments](../../networking.md#box-assignments) table. Ports below are the public ones +forwarded by `estuary`. + +| Container | Ports | Status | +|---|---|---| +| `valheim` | `2456-2457`/udp | running | +| `simpcraft` | `25565` tcp+udp | running | +| `simpcraft-staging` | `25566` tcp | **disabled** (commented out) | +| `enshrouded` | `15636-15637`/udp | **disabled** (`enshrouded.nix` not imported) | +| `kevcraft` | `25567` tcp+udp | running | +| `kinkcraft` | `25568` tcp+udp | running | +| `graeme` | `25569` tcp+udp | running | + +- **valheim** ([`valheim.nix`](../../../nixos/boxes/colony/vms/whale2/valheim.nix)) — + `lloesche/valheim-server`, public server "amogus sus", world `simpland2`, + allow-listed Steam IDs, password from agenix. +- **simpcraft** ([`minecraft/`](../../../nixos/boxes/colony/vms/whale2/minecraft)) — + `itzg/minecraft-server` (self-built `git.nul.ie/dev/craftblock` image), + Modrinth "Simpcraft" modpack, whitelist + ops. +- **simpcraft-staging** — the same setup pinned to an older pack version, currently commented out. +- **kevcraft** — vanilla Minecraft 1.20.1, extra op. +- **kinkcraft** — same Simpcraft modpack as `simpcraft`. +- **graeme** — vanilla Minecraft on hard difficulty with its own whitelist. +- **enshrouded** ([`enshrouded.nix`](../../../nixos/boxes/colony/vms/whale2/enshrouded.nix)) — + `sknnr/enshrouded-dedicated-server` ("UWUshrouded"); the file exists but is + commented out of `whale2`'s `imports`, so the server is down (its forwards + and DNS records remain). + +The Minecraft containers share one whitelist/ops list and agenix env file +(`whale2/simpcraft.env`, which also carries the RCON password). + +## Backups + +A local borg job (`services.borgbackup.jobs.simpcraft`) archives the `simpcraft` world frequently, +offset from its autosave timer, into `/var/lib/containers/backup/simpcraft`. It uses `mcrcon` to +`save-off`/`save-on` around each run and keeps a short history for quick world rollback rather than +disaster recovery; the `oci` LV itself is covered by `colony`'s `borgthin`. + +## Notable config files + +- [`nixos/boxes/colony/vms/whale2/default.nix`](../../../nixos/boxes/colony/vms/whale2/default.nix) — VM config, podman/netavark setup, `extraAssignments`. +- [`nixos/boxes/colony/vms/whale2/valheim.nix`](../../../nixos/boxes/colony/vms/whale2/valheim.nix) — Valheim server. +- [`nixos/boxes/colony/vms/whale2/minecraft/default.nix`](../../../nixos/boxes/colony/vms/whale2/minecraft/default.nix) — the Minecraft servers + world backup job. +- [`nixos/boxes/colony/vms/whale2/enshrouded.nix`](../../../nixos/boxes/colony/vms/whale2/enshrouded.nix) — Enshrouded server (disabled, not imported). diff --git a/docs/sites/home/README.md b/docs/sites/home/README.md new file mode 100644 index 0000000..28132fc --- /dev/null +++ b/docs/sites/home/README.md @@ -0,0 +1,45 @@ +# Home site + +The home network (domain `h.nul.ie`): a redundant pair of routers in front of a VM host, an +NVMe-oF storage target, an IoT container host, and a workstation. The two routers — `river` (a VM) +and `stream` (a physical box) — are built from one shared +[`routing-common`](../../../nixos/boxes/home/routing-common) definition as an active/backup VRRP +pair, and everything clients touch (gateway, DNS) is a floating VIP that follows the master. + +- **Source:** [`nixos/boxes/home/`](../../../nixos/boxes/home) + +## Boxes + +| Box | Role | Host | +|---|---|---| +| [`palace`](palace.md) | VM host | physical | +| [`river`](river.md) | Primary router (VRRP pair with `stream`) | VM on `palace` | +| [`stream`](stream.md) | Secondary router (VRRP pair with `river`) | physical | +| [`cellar`](cellar.md) | NVMe-oF / SPDK storage target | VM on `palace` | +| [`sfh`](sfh/README.md) | NixOS container host (containers on its page) | VM on `palace` | +| [`castle`](castle.md) | Workstation / gaming desktop | physical | + +## Router VIPs + +Clients use per-VLAN floating VIPs as their gateway and DNS server; `keepalived` moves them between +`river` and `stream`. The addresses, DHCP/RA behavior and failover mechanics are documented once in +[Router VIPs](../../networking.md#router-vips) and [Router HA](../../networking.md#router-ha). + +## Networks + +The site separates core management, high-MTU trusted traffic, general trusted traffic, untrusted +clients and the two WAN paths. VLAN IDs, prefixes, MTUs and router addressing live in the canonical +[`home` section of networking.md](../../networking.md#home). + +## Switch fabric + +The boxes hang off three hand-configured switches — `jim` and `dave` (MikroTik, RouterOS) and +`brian` (Ubiquiti, UniFi) — which are **not** managed by this flake. The physical topology, VLAN +map, the Digiweb WAN path (trunked VLAN 10 + PVID 140 at the ONT edge), and the multi-ONT plan are +documented in [switches.md](switches.md). + +## Wireless APs + +The Wi-Fi APs — `vibe` (MikroTik cAP ax) and `wave` (Cudy AX3000 on OpenWrt) — are dumb APs, also +**not** managed by this flake. The shared VLAN-trunk design, SSIDs, per-AP management addressing, +and the OpenWrt flash/config for `wave` are in [aps.md](aps.md). diff --git a/home-aps.md b/docs/sites/home/aps.md similarity index 83% rename from home-aps.md rename to docs/sites/home/aps.md index 6013e6d..53f64b1 100644 --- a/home-aps.md +++ b/docs/sites/home/aps.md @@ -1,11 +1,12 @@ # Home wireless APs -Reference for the home Wi-Fi access points. Like the switches (`home-switches.md`), these are **not** -managed by this flake — they are configured on-device (RouterOS on the MikroTik, OpenWrt/UCI on the -Cudy). This file documents the shared VLAN/trunk design and each AP. +Reference for the home Wi-Fi access points. Like the switches ([switches.md](switches.md)), these +are **not** managed by this flake — they are configured on-device (RouterOS on the MikroTik, +OpenWrt/UCI on the Cudy). This file documents the shared VLAN/trunk design and each AP. -Only the DNS records live in the flake (`nixos/boxes/home/routing-common/dns.nix`, `h.nul.ie` zone). -Everything else here is applied by hand on the device. +Only the DNS records live in the flake +([`nixos/boxes/home/routing-common/dns.nix`](../../../nixos/boxes/home/routing-common/dns.nix), +`h.nul.ie` zone). Everything else here is applied by hand on the device. ## The APs @@ -64,16 +65,18 @@ RouterOS, one hardware-offloaded bridge `main` with `vlan-filtering=yes`. Access (WPA2-PSK), untagged onto VLAN 120. `country=Ireland`. - **Bridge VLANs** — 100 tagged `main,ether1`; 110 tagged `main,ether1` + untagged `ether2,wifi1,wifi2`; 120 tagged `main,ether1` + untagged `wifi3`. -- **Management** — `jim`/`dave`-style (core/hi/lo), on host `.15`: `192.168.64.15` on core - (native/untagged, backup), `192.168.68.15/22` + `2a0e:97c0:4d0:1::1:6` on the `hi` VLAN-100 - interface (holds the default route, via the hi VIP `192.168.71.254`), and - `192.168.72.15/21` + `2a0e:97c0:4d0:2::1:6` on `lo` VLAN 110. No IP on `untrusted`. - `l2mtu 9214`, so `hi` carries jumbo (9000) here — `vibe` sits on `hi` because it *can* jumbo, - unlike `wave` (see its MTU note). - **Roaming** — 802.11k/v via a `/interface wifi steering` profile (`rrm=yes wnm=yes`, `neighbor-group=home-aps`) assigned to `wifi1`/`wifi2`/`wifi3`. - **Resolver** — the hi VIP `192.168.71.254` / `2a0e:97c0:4d0:1::ffff`. +### Management + +Management uses host `.15`: `192.168.64.15` on native/core as a backup, +`192.168.68.15/22` + `2a0e:97c0:4d0:1::1:6` on `hi` VLAN 100, and +`192.168.72.15/21` + `2a0e:97c0:4d0:2::1:6` on `lo` VLAN 110. The `hi` address holds the default +route through its VIP; `untrusted` has no address. With `l2mtu 9214`, `vibe` can use the jumbo +`hi` network unlike `wave`. + ## wave (Cudy AX3000, OpenWrt) Single-port AP, so the port is a VLAN **trunk** carrying management + both SSIDs. @@ -99,7 +102,7 @@ zone with `input REJECT` (and `wave` has no IP there) — **no management via th `wave` hangs off **brian** (UniFi). Its port is a **trunk**: tagged VLAN **110/120** (`lo` + guest), and **native/untagged = core** (the fabric's management VLAN, carrying `wave-core`). VLAN 100 (`hi`) is **not** needed here — `wave` isn't on `hi` (see Management addressing). Configure via the UniFi -controller (brian has no CLI); see `home-switches.md`. +controller (brian has no CLI); see [switches.md](switches.md). ### Flashing OpenWrt (Cudy AX3000 / `cudy_ap3000-v1`) @@ -110,6 +113,7 @@ Hardware: MT7981B, 512 MB RAM, 256 MB SPI-NAND, 1× 2.5 GbE (RTL8221B), 2×2 WiF OpenWrt can't be flashed directly over stock. Two-stage, via a Cudy **transition** firmware (Cudy OpenWrt download page / `support@cudy.com`; `warnning.txt` in that bundle has the steps): + 1. Stock Cudy UI: update to **≥ 2.4.7** (adds TFTP `recovery.bin` recovery), then flash the Cudy **intermediate** firmware (`cudy_ap3000-v1-sysupgrade_*.bin`), "keep settings" **unchecked**. It reboots into an OpenWrt-based build at `192.168.1.1` (SSH `root`, empty password). @@ -122,11 +126,13 @@ stock-side flashing is done from a browser, not headless. ### On-device config notes -- Package manager is **`apk`** (not `opkg`). WiFi runs **`wpad-mbedtls`** (full — swapped from the - default `wpad-basic-mbedtls`, which lacks 802.11v). **802.11k + 802.11v** (`ieee80211k` + - `bss_transition`) are enabled on all SSIDs. ⚠️ Swapping wpad **live** leaves the mac80211 vifs - stuck in a start→teardown loop (`nl80211 ... No such device`); a `wifi reload`/`network restart` - won't recover it — **reboot** after `apk add wpad-mbedtls`. +#### Wireless packages + +The package manager is `apk`, not `opkg`. Wi-Fi uses the full `wpad-mbedtls` package so all SSIDs +can enable 802.11k/v (`ieee80211k` + `bss_transition`). Replacing `wpad-basic-mbedtls` live leaves +the mac80211 interfaces in a start/teardown loop that reloads cannot recover; reboot after +`apk add wpad-mbedtls`. + - Radios: `radio0` = 2.4 GHz, `radio1` = 5 GHz (keyed by `band`, don't assume). 5 GHz is pinned to **channel 36 / HE160** (any 160 MHz block in IE is DFS; ch36 has the shortest ~60 s CAC). - Bridge: `br-lan` with `vlan_filtering`, single port `eth0` — tagged `110/120`, untagged/PVID diff --git a/docs/sites/home/castle.md b/docs/sites/home/castle.md new file mode 100644 index 0000000..6e9256f --- /dev/null +++ b/docs/sites/home/castle.md @@ -0,0 +1,63 @@ +# castle + +The home workstation / gaming desktop. Diskless-style: it netboots from `river` and keeps its +root storage on NVMe-oF volumes from `cellar`. + +- **Source:** [`nixos/boxes/home/castle/`](../../../nixos/boxes/home/castle) (`default.nix`) +- **Host:** physical +- **nixpkgs:** `mine` + +## Role + +### Desktop + +The AMD desktop runs the GUI stack (`my.gui.enable`, Sway/Wayland via home-manager), low-latency +PipeWire, Bluetooth and Thunderbolt. Local `libvirtd`/`virt-manager` and the IOMMU are enabled, but +the box has no VFIO or GPU-passthrough configuration. + +### Netboot + +With `my.netboot.client.enable`, the firmware iPXE-boots from the 2.5G NIC. Kea matches +`et2.5g`'s MAC and directs it to `boot.h.nul.ie` on [`river`](river.md). + +### NVMe-oF root + +`/nix`, `/persist` and `/home` are `/dev/nvmeof/*` LVs in `cellar`'s +`nqn.2016-06.io.spdk:castle` namespace (`my.nvme.boot`, RDMA). The initrd brings +up `et100g`/`lan-hi` with `roceBootModules`; the running network keeps +`KeepConfiguration=static` so networkd does not drop the storage address. The root filesystem is a +size-limited tmpfs (`my.tmproot`). + +### Other configuration + +Both firewalls are disabled on this trusted `hi` desktop. Other settings include `binfmt` +emulation for `aarch64-linux`/`armv7l-linux`, `recursive-nix`, Wireshark and `rdma-core`/`qperf`; +a `drm-amd-display` flicker patch remains commented out. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `castle`). + +## Hardware + +| Component | Inventory | +|---|---| +| Platform | ASUS ProArt X670E-CREATOR WIFI | +| CPU | AMD Ryzen 9 7950X (16 cores / 32 threads) | +| Memory | 64 GiB | +| Graphics | Integrated AMD Radeon graphics | +| Network | Mellanox ConnectX-4 100G, Aquantia AQC113CS 10G, Intel I225-V 2.5G and MediaTek MT7922 Wi-Fi 6E controllers | +| System storage | No local root disk; the box netboots and uses the SPDK NVMe-oF namespace exported by `cellar` | + +## Networking + +- `et100g` (100G, MTU 9000) carries `lan-hi` (the `hi` assignment, also pinned by a kea + reservation on its MAC) and `lan-lo`. +- `lan-lo` is a secondary network attachment: DHCPv4 with `UseGateway`/`UseDNS` off and RAs + accepted with gateway/DNS use off — present for reaching `lo` devices, never a default route. +- `et2.5g` (netboot) and `et10g` are renamed but carry no network config. + +## Notable config files + +- [`nixos/boxes/home/castle/default.nix`](../../../nixos/boxes/home/castle/default.nix) — box + config: netboot/NVMe-oF boot, 100G networking, GUI/audio, virtualisation. diff --git a/docs/sites/home/cellar.md b/docs/sites/home/cellar.md new file mode 100644 index 0000000..9f74d69 --- /dev/null +++ b/docs/sites/home/cellar.md @@ -0,0 +1,48 @@ +# cellar + +The home storage target. A VM on `palace` that drives three passed-through NVMe disks with SPDK +and exports them over NVMe-oF/RDMA — `river`, `sfh` and `castle` all run their root storage off +it. + +- **Source:** [`nixos/boxes/home/palace/vms/cellar/`](../../../nixos/boxes/home/palace/vms/cellar) + (`default.nix`, `spdk.nix`) +- **Host:** VM on `palace` +- **nixpkgs:** `mine` + +## Role + +- Runs an **SPDK userspace target** (`my.spdk`, [`spdk.nix`](../../../nixos/boxes/home/palace/vms/cellar/spdk.nix)): + the kernel `nvme` driver is blacklisted so SPDK can claim the three NVMe controllers directly + (host BDFs `41:00.0`–`43:00.0`, attached in the guest as `02:00.0`–`04:00.0`). +- Builds a **RAID-0** (`NVMeRaid`) across the three drives and exports one + partition per consumer as an **NVMe-oF subsystem over RDMA** (port 4420) on the `hi` network: + + | Bdev | NQN | Consumer | + |---|---|---| + | `NVMeRaidp1` | `nqn.2016-06.io.spdk:river` | [`river`](river.md) | + | `NVMeRaidp2` | `nqn.2016-06.io.spdk:castle` | [`castle`](castle.md) | + | `NVMeRaidp3` | `nqn.2016-06.io.spdk:sfh` | [`sfh`](sfh/README.md) | + + Each subsystem is pinned to its consumer's `hostnqn` (the `my.nvme.uuid` on the client side). +- `spdk-tgt` is ordered after `lan-hi` is online; the RDMA listener binds the `hi` assignment on + port 4420. The VM itself is pinned to NUMA node 1 on `palace` and gets SR-IOV VF 0. +- `netdata` (port 19999 allowed in the firewall) and `fstrim`. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `cellar`). + +## Notes + +- The `ublk_*` calls in `my.spdk.debugCommands` are only a debugging aid — they create a local + ublk device so the RAID can be mounted and inspected on `cellar` itself. Client exports are the + `nvmf` subsystems above. + +## Notable config files + +- [`nixos/boxes/home/palace/vms/cellar/default.nix`](../../../nixos/boxes/home/palace/vms/cellar/default.nix) — + box config (assignment, networking, netdata). +- [`nixos/boxes/home/palace/vms/cellar/spdk.nix`](../../../nixos/boxes/home/palace/vms/cellar/spdk.nix) — + SPDK target: RAID-0, NVMe-oF/RDMA subsystems. +- [`nixos/boxes/home/palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix) — + the VM definition on `palace` (VF 0, NVMe passthrough, NUMA pinning). diff --git a/docs/sites/home/palace.md b/docs/sites/home/palace.md new file mode 100644 index 0000000..89ffe94 --- /dev/null +++ b/docs/sites/home/palace.md @@ -0,0 +1,70 @@ +# palace + +The physical VM host for the home site. Runs the `river`, `cellar` and `sfh` VMs and feeds them +SR-IOV VFs, PCI NVMe drives and LVM disks. + +- **Source:** [`nixos/boxes/home/palace/default.nix`](../../../nixos/boxes/home/palace/default.nix) + (VM definitions in [`palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix)) +- **Host:** physical +- **nixpkgs:** `mine-stable` + +## Role + +- Home hypervisor: VMs are declared in `my.vms.instances` + ([`palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix)); disks are LVs in + the `main` thin pool (`services.lvm.boot.thin.enable`). +- AMD box (`kvm-amd`, `amd_iommu=on`, microcode updates); the kernel is built with + `ACPI_APEI_PCIEAER`/`PCIEAER` for the PCIe passthrough work below. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `palace`). + +## Hardware + +| Component | Inventory | +|---|---| +| Platform | Gigabyte X399 DESIGNARE EX | +| CPU | AMD Ryzen Threadripper 1950X (16 cores / 32 threads) | +| Memory | 128 GiB | +| Host storage | 500 GB Samsung SSD 860 EVO containing the EFI partition and the `main` LVM thin pool | +| Bulk storage | Three 8 TB Seagate IronWolf disks in the `hdds` VG, providing the RAID-backed `hdd-storage` and `frigate` LVs | +| NVMe storage | Three 2 TB Samsung NVMe devices passed through to `cellar`; SPDK combines them as the `NVMeRaid` RAID 0 device | +| Network / graphics | Mellanox ConnectX-4 100G adapter with four SR-IOV VFs, two Intel I211 Gigabit Ethernet controllers, and an AMD Radeon RX 550/560-family GPU | + +## Networking + +100G `et100g` (mlx5, MTU 9000) uplinks to the `dave` switch and carries `lan-hi` (VLAN 100, the +`hi` assignment). A udev rule creates four SR-IOV VFs on the PF: + +| VF | Consumer | VLAN handling | +|---|---|---| +| 0 | `cellar` | `hi` | +| 1 | `river` | untagged VF; `river` tags its LAN and WAN VLANs | +| 2 | `sfh` | `hi` | +| 3 | `sfh` container MACVLAN parent | `hi` | + +- `lan-core` is a bridge with the `core` assignment (no gateway); the 1G + `lan-core-phy` and the `lan-lo-phy` VLAN ride on it. `lan-lo` is a second, L3-less bridge used + for VM netboot and `lo` clients. +- The 1G `et1g0` (igb) is exported to `river` as a passthru-mode macvtap (`vm-et1g0`) — river sees + it as `wan-old`. + +## VMs + +| VM | vCPUs | RAM | Passthrough | Notes | +|---|---|---|---|---| +| `cellar` | 8c × 2t | 16 GiB | VF 0 (`44:00.1`); NVMe `41:00.0`–`43:00.0` | pinned to NUMA node 1; split IRQ chip + vIOMMU | +| `river` | 3c × 2t | 4 GiB | VF 1 (`44:00.2`); macvtap `vm-et1g0` | only an ESP local disk (plus an installer ISO) — root is NVMe-oF from `cellar` | +| `sfh` | 8c × 2t | 32 GiB | VF 2 (`44:00.3`), VF 3 (`44:00.4`); two USB host ports | no boot disk — netboots; gets the `hdds/frigate` LV | + +Boot ordering is enforced with systemd dependencies: `vm@river` waits for `cellar`'s SSH port (and +for the `vm-et1g0` device), and `vm@sfh` waits for `river` — storage first, then the router, then +everything that boots off both. + +## Notable config files + +- [`nixos/boxes/home/palace/default.nix`](../../../nixos/boxes/home/palace/default.nix) — host + hardware, networkd (links/bridges/SR-IOV), LVM. +- [`nixos/boxes/home/palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix) — + VM instances and boot ordering. diff --git a/docs/sites/home/river.md b/docs/sites/home/river.md new file mode 100644 index 0000000..2c4b4ef --- /dev/null +++ b/docs/sites/home/river.md @@ -0,0 +1,79 @@ +# river + +A home router VM on `palace` with a Digiweb PPPoE WAN on the ISP's VLAN 10. It forms the redundant +router pair with [`stream`](stream.md). + +- **Source:** [`nixos/boxes/home/palace/vms/river.nix`](../../../nixos/boxes/home/palace/vms/river.nix) + (shared router config: [`routing-common`](../../../nixos/boxes/home/routing-common), index 0) +- **Host:** VM on `palace` +- **nixpkgs:** `mine` + +## Role + +At `routing-common` index 0, `river` normally holds the primary position in the router pair. +Pair-wide addressing, VIP, DHCP/DNS and failover behavior is documented in the +[`home` networking overview](../../networking.md#home) and [Router HA](../../networking.md#router-ha). +This page covers `river`'s Digiweb WAN, VM platform, storage and netboot duties. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `river`). + +## WAN (Digiweb PPPoE) + +### Link and addressing + +`services.pppd` peer `digiweb` attaches directly to `wan-pon-isp`, the raw ISP VLAN 10 +(`vlans.pon-isp`) trunked untranslated from the ONT through `brian` and `dave`; see +[switches.md](switches.md). The netdev has no L3 configuration and uses `MTUBytes=1508`, leaving a +clean `mtu`/`mru 1500` after PPPoE overhead. + +The static `84.203.124.128` address is requested through IPCP. The provider-wide credentials are +deliberately not secret; the peer persists indefinitely with LCP echo monitoring and ignores +Digiweb DNS in favor of the local recursor. + +### Management subnet + +`wan-pon-ont` (VLAN 140, PVID'd at `brian`) holds `192.168.100.100/24`, reaching the ONT UI at +`192.168.100.1`. The `.100` address follows `stream`'s modem-management convention. + +### WAN readiness + +The `pppd` hooks drive this shared gate with `DefaultDependencies=false`: `ip-up` installs the +link-scoped default route and starts the target, while `ip-down` stops it and removes the route. +`ipsec` and `ipv6-clear-default-route` attach through `wantedBy` + `partOf`, so they reload after +every WAN flap. + +### Traffic shaping + +The shared `wan-ifb` ingress-shaping pieces are inert here. CAKE is specific to `stream`, and +`networkd-dispatcher` is `mkForce false` pending scheduling tests. + +## Platform + +### Virtual machine and network attachment + +The 100G `lan` NIC is VF 1 of `palace`'s `et100g` (MTU 9000), with every router VLAN tagged on top +as `55-lan`. A macvtap of `palace`'s 1G `et1g0` remains as the old `wan-old` path without L3 +configuration. Deployments use the `hi` assignment. + +### Storage + +The VM's local disk holds only an ESP; `/nix` and `/persist` are LVs on `cellar`'s +`nqn.2016-06.io.spdk:river` namespace over RDMA. The initrd brings up `lan-hi` +with `roceBootModules`, and `KeepConfiguration=static` prevents networkd from dropping the address +during reconfiguration. An installer ISO remains attached. + +## Netboot + +`my.netboot.server` serves iPXE/TFTP for `sfh` and `castle` at `boot.h.nul.ie` from the `lo` +assignment, restricted to the `hi` and `lo` prefixes. + +## Notable config files + +- [`nixos/boxes/home/palace/vms/river.nix`](../../../nixos/boxes/home/palace/vms/river.nix) — box + config: pppd, WAN VLANs, `wan-online.target` hooks, netboot server, NVMe-oF boot. +- [`nixos/boxes/home/routing-common/default.nix`](../../../nixos/boxes/home/routing-common/default.nix) — + shared router definition (assignments, firewall/NAT, `as211024`). +- [`nixos/boxes/home/palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix) — + the VM definition on `palace` (VF 1, macvtap, ESP disk). diff --git a/docs/sites/home/sfh/README.md b/docs/sites/home/sfh/README.md new file mode 100644 index 0000000..944b153 --- /dev/null +++ b/docs/sites/home/sfh/README.md @@ -0,0 +1,59 @@ +# sfh + +"Services for home" — the NixOS container host for the home site. A VM on `palace` that netboots +from `river` and runs its root off NVMe-oF from `cellar`. + +- **Source:** [`nixos/boxes/home/palace/vms/sfh/`](../../../../nixos/boxes/home/palace/vms/sfh) + (`default.nix`, `containers/`) +- **Host:** VM on `palace` +- **nixpkgs:** `mine` + +## Role + +- Runs the home NixOS containers via `my.containers.instances` (systemd-nspawn); each container is + its own `nixos.systems.*` entry rendered through `my.asContainer`. +- **Netboot client** (`my.netboot.client.enable`): the VM has no boot disk — its `netboot` NIC + on palace's `lan-lo` bridge is matched by a kea client-class on [`river`](../river.md) and + iPXE-boots from `boot.h.nul.ie`. +- **Root on NVMe-oF**: `my.nvme.boot` connects to `nqn.2016-06.io.spdk:sfh` + ([`cellar`](../cellar.md), RDMA) from the initrd (`lan-hi` up + `roceBootModules`); `/nix` and + `/persist` are LVs on that volume. `KeepConfiguration=static` on `lan-hi` protects the + NVMe-oF address from networkd reconfigures. +- **Frigate footage disk**: palace passes the `hdds/frigate` LVM LV through as a virtio disk; + sfh mounts it at `/mnt/frigate` (by label) and bind-mounts it into the `hass` container at + `/var/lib/frigate`. +- USB: two host ports are passed to the VM (qemu flags) for the Zigbee coordinator and webcam used + by `hass`; the nspawn unit gets `DeviceAllow` for `char-ttyUSB` and `char-video4linux`. + +## Network assignments + +See the consolidated [network assignments](../../../networking.md#box-assignments) table (this box: `sfh`). + +## Networking + +Four NICs, all MTU 9000 where jumbo-capable: + +- `lan-hi` — SR-IOV VF 2, the box's own `hi` assignment. +- `lan-hi-ctrs` — SR-IOV VF 3, no L3: the MACVLAN parent for the containers' `hi` interfaces + (`host0` inside each container). +- `lan-core-ctrs` / `lan-lo-ctrs` — virtio NICs (bridged to palace's `lan-core` / `lan-lo`), no + L3: MACVLAN parents for containers that need a `core` or `lo` interface. + +The per-container MACVLAN wiring lives in `systemd.nspawn.*.networkConfig` in +[`sfh/default.nix`](../../../../nixos/boxes/home/palace/vms/sfh/default.nix). + +## Containers + +| Container | Role | +|---|---| +| [`hass`](containers/hass.md) | Home Assistant + Frigate + MQTT | +| [`unifi`](containers/unifi.md) | UniFi controller | + +## Notable config files + +- [`nixos/boxes/home/palace/vms/sfh/default.nix`](../../../../nixos/boxes/home/palace/vms/sfh/default.nix) — + box config: netboot/NVMe-oF boot, container instances, MACVLAN plumbing, Frigate disk. +- [`nixos/boxes/home/palace/vms/sfh/containers/`](../../../../nixos/boxes/home/palace/vms/sfh/containers) — + the container system definitions. +- [`nixos/boxes/home/palace/vms/default.nix`](../../../../nixos/boxes/home/palace/vms/default.nix) — + the VM definition on `palace` (VFs, USB passthrough, netboot NIC, `hdds/frigate` disk). diff --git a/docs/sites/home/sfh/containers/hass.md b/docs/sites/home/sfh/containers/hass.md new file mode 100644 index 0000000..8e64160 --- /dev/null +++ b/docs/sites/home/sfh/containers/hass.md @@ -0,0 +1,59 @@ +# hass + +Home automation container: Home Assistant plus its supporting services (MQTT, camera restreaming, +Frigate NVR), running on [`sfh`](../README.md). + +- **Source:** [`nixos/boxes/home/palace/vms/sfh/containers/hass.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/containers/hass.nix) +- **Host:** NixOS container on `sfh` +- **nixpkgs:** `mine` + +## Role + +### Home Assistant + +`services.home-assistant` uses declarative configuration (`configWritable = false`). It enables +the `esphome`, `zha`, `denonavr`, `webostv`, `androidtv_remote`, `heos`, `mqtt`, `wled`, `met` and +`google_translate` components, plus custom `alarmo`, `frigate`, `west_wood_club` and Irish Rail +integrations. A `hass-cli` wrapper uses a token from `my.secrets` to reach the local server. + +- **mosquitto** — MQTT broker (anonymous local listener; port 1883 allowed, alongside HTTP). +- **go2rtc** — restreams the Reolink living-room camera (RTSP from `reolink-living-room`, on the + `lo` network) and the office USB webcam (`/dev/video0` via ffmpeg). +- **Frigate** (`services.frigate`, `frigate.h.nul.ie` — the `frigate` alt name on the `hi` + assignment) — records both restreamed cameras with a short retention policy; detection is + disabled. +- External access is via `https://hass.nul.ie` through the `middleman` reverse proxy + (`trusted_proxies`); internally it's `hass-ctr.h.nul.ie`. +- Not a deploy-rs target (`my.deploy.enable = false`) — it's rendered via `my.asContainer` and + started by `sfh`'s `my.containers.instances`. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `hass`). + +## Storage + +Frigate footage lives on a **separate HDD LV**: `palace` passes the `hdds/frigate` LVM LV to the +`sfh` VM, sfh mounts it at `/mnt/frigate`, and the container bind-mounts it at `/var/lib/frigate` +(read-write). This keeps recording churn off the NVMe-oF root. + +## Devices + +Passed through from `sfh` (USB host ports on `palace`): + +- Nabu Casa Connect ZBT-1 Zigbee coordinator → `/dev/ttyUSB0` (used by `zha`). +- USB webcam → `/dev/video0` (go2rtc's `webcam_office` stream). +- The matching raw USB device node allowed through from `sfh`. + +## Networking + +MACVLAN interfaces created from `sfh`'s container NICs: `host0` on `lan-hi-ctrs` (the `hi` assignment, +alt name `frigate`, default gateway via the VIP) and `lan-lo` on `lan-lo-ctrs` (the `lo` +assignment, no gateway) — the `lo` interface reaches the IoT devices (the Reolink camera lives there). + +## Notable config files + +- [`nixos/boxes/home/palace/vms/sfh/containers/hass.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/containers/hass.nix) — + container system: Home Assistant, Frigate, mosquitto, go2rtc. +- [`nixos/boxes/home/palace/vms/sfh/default.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/default.nix) — + the `sfh` side: bind mounts, MACVLAN wiring, `DeviceAllow`. diff --git a/docs/sites/home/sfh/containers/unifi.md b/docs/sites/home/sfh/containers/unifi.md new file mode 100644 index 0000000..d785048 --- /dev/null +++ b/docs/sites/home/sfh/containers/unifi.md @@ -0,0 +1,42 @@ +# unifi + +The UniFi network controller, running as a container on [`sfh`](../README.md). It manages the +home UniFi switch `brian` (see [switches.md](../../switches.md)). + +- **Source:** [`nixos/boxes/home/palace/vms/sfh/containers/unifi.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/containers/unifi.nix) +- **Host:** NixOS container on `sfh` +- **nixpkgs:** `mine` + +## Role + +- **UniFi controller** (`services.unifi`, `pkgs.unifi` on `mongodb-7_0`, firewall open; TCP 8443 + allowed). +- Not a deploy-rs target (`my.deploy.enable = false`) — it's rendered via `my.asContainer` and + started by `sfh`'s `my.containers.instances`. + +## Network assignments + +See the consolidated [network assignments](../../../../networking.md#box-assignments) table (this box: `unifi`). + +## Status + +**Currently enabled.** The container spent a while disabled — its import was commented out of +[`containers/default.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/containers/default.nix) +while there was no UniFi gear to manage — and was re-enabled when the UniFi switch `brian` was +added, gaining a `core` interface (`unifi-ctr-core`) at the same time so it can reach the switch on its +management network. It is imported, listed in `sfh`'s `my.containers.instances`, and +`services.unifi.enable = true`. + +## Networking + +Two MACVLAN interfaces come from `sfh`'s container NICs: `host0` on `lan-hi-ctrs` carries the `hi` +assignment (`unifi-ctr`, default gateway via the VIP), while `lan-core` on `lan-core-ctrs` carries +the gatewayless `core` assignment (`unifi-ctr-core`). The `core` interface is how the controller +talks to `brian` and the other switches on their management network. + +## Notable config files + +- [`nixos/boxes/home/palace/vms/sfh/containers/unifi.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/containers/unifi.nix) — + container system: UniFi service, assignments. +- [`nixos/boxes/home/palace/vms/sfh/default.nix`](../../../../../nixos/boxes/home/palace/vms/sfh/default.nix) — + the `sfh` side: container instance, MACVLAN wiring. diff --git a/docs/sites/home/stream.md b/docs/sites/home/stream.md new file mode 100644 index 0000000..b15656b --- /dev/null +++ b/docs/sites/home/stream.md @@ -0,0 +1,100 @@ +# stream + +A physical Intel home router with a DHCP WAN from the Virgin Media cable modem. It forms the +redundant router pair with [`river`](river.md) and is dual-homed to both switches. + +- **Source:** [`nixos/boxes/home/stream.nix`](../../../nixos/boxes/home/stream.nix) (shared router + config: [`routing-common`](../../../nixos/boxes/home/routing-common), index 1) +- **Host:** physical +- **nixpkgs:** `mine` + +## Role + +At `routing-common` index 1, `stream` normally holds the secondary position in the router pair. +Pair-wide addressing, VIP, DHCP/DNS and failover behavior is documented in the +[`home` networking overview](../../networking.md#home) and [Router HA](../../networking.md#router-ha). +This page covers `stream`'s Virgin Media WAN, physical platform and redundant switch attachment. + +## Network assignments + +See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `stream`). + +## WAN (Virgin Media DHCP) + +### Link and addressing + +`wan` is a renamed igc NIC (`00:f0:cb:ee:ca:dd`) towards the cable modem. The modem segment is +switch VLAN 130; `jim` handles the tag, so the box interface is untagged. See +[switches.md](switches.md) for the fabric side. + +`DHCP=ipv4` pulls the public lease. `dhcpV4Config.UseDNS=false` points resolution at the local +recursor, and `IPv6AcceptRA=false` because public IPv6 arrives over the tunnel rather than this WAN. + +### Management subnet + +The static `192.168.0.100/24` address (host `.100` of `prefixes.modem.v4`) sits on `wan` without a +gateway, keeping the modem UI reachable alongside the DHCP lease. + +### WAN readiness + +`wan-wait-online.service` polls until the DHCP default route exists, then satisfies +`wan-online.target`. The route is the gate because the permanent modem address would make +networkd's wait-online report success before the public lease arrives, allowing `ipsec` to start +without its public `left=` address. + +### Traffic shaping + +Egress is shaped at the `wan` root qdisc. A `routing-common` `networkd-dispatcher` rule redirects +ingress through `tc`/`mirred` into `wan-ifb`; each direction has its own configured bandwidth and +uses the DOCSIS overhead preset. + +### Per-box `routing-common` options + +The modem's management subnet shares the `wan` interface, which `routing-common` itself knows +nothing about — it declares two per-box options +([`routing-common/default.nix`](../../../nixos/boxes/home/routing-common/default.nix)) that this +box sets: + +- `my.homeRouter.dns.wanSkipBroadcasts` — skip the modem subnet's broadcast address when + auto-selecting the router's own `wan` A record for the zone's LUA record. +- `my.homeRouter.firewall.untrustedRejectV4` — reject untrusted clients from + reaching the modem subnet (needed only because it shares `wan`; WAN egress is otherwise + accepted). + +## Platform + +### Hardware + +| Component | Inventory | +|---|---| +| Platform | BROUNION R86S | +| CPU | Intel Celeron N5105 (4 cores / 4 threads) | +| Memory | 16 GiB | +| Storage | 512 GB Samsung SSD 970 PRO NVMe containing `/boot`, `/nix` and `/persist`; integrated 128 GB eMMC is present but is not used by the declared filesystems | +| Network | Three Intel `igc` interfaces and a dual-port Mellanox `mlx4_en` adapter; `wan`, `lan-jim` and `lan-dave` use three of these ports | + +The platform configuration enables `kvm-intel`, `intel_iommu=on` and Intel microcode updates. + +### Switching (RSTP) + +`stream` is dual-homed to both switches: `lan-jim` (igc) and `lan-dave` (mlx4_en), both MTU 9000, +are enslaved to the `lan` bridge with `STP=true`. [`routing-common/mstpd.nix`](../../../nixos/boxes/home/routing-common/mstpd.nix) +runs a patched `mstpd` and forces RSTP on `lan` once it's routable, so exactly one uplink carries +traffic at a time. (The remaining NICs are renamed `et2`/`et5` and left unconfigured.) + +### Deployment + +`my.deploy.node.hostname` is currently commented out. + +## Disabled printer services + +`octoprint` and `mjpg-streamer` are defined but disabled (`enable = false`). + +## Notable config files + +- [`nixos/boxes/home/stream.nix`](../../../nixos/boxes/home/stream.nix) — box config: DHCP WAN, + modem management, CAKE, `wan-online.target` gate, STP bridge. +- [`nixos/boxes/home/routing-common/default.nix`](../../../nixos/boxes/home/routing-common/default.nix) — + shared router definition (index 1). +- [`nixos/boxes/home/routing-common/mstpd.nix`](../../../nixos/boxes/home/routing-common/mstpd.nix) — + RSTP on the `lan` bridge. diff --git a/home-switches.md b/docs/sites/home/switches.md similarity index 66% rename from home-switches.md rename to docs/sites/home/switches.md index abd82c8..7b99810 100644 --- a/home-switches.md +++ b/docs/sites/home/switches.md @@ -9,7 +9,9 @@ In short: the Digiweb ISP VLAN (10) is trunked straight through to river (which and the ONT's untagged management is PVID'd onto VLAN 140 at brian, its edge switch. VLAN 10 is carried untranslated because a single ONT makes it unique on the fabric — see [the WAN path](#the-digiweb-wan-path-trunked-vlan-10--pvid-140) and -[why not translation](#why-not-translation-for-one-ont). +[why not translation](#why-not-translation-for-one-ont). The router side lives in +[river.md](river.md); the logical network map in [networking.md](../../networking.md). The Wi-Fi +APs that hang off these switches are in [aps.md](aps.md). ## The switches @@ -18,8 +20,8 @@ carried untranslated because a single ONT makes it unique on the fabric — see | Identity | `jim-sw` | `dave-sw` | (UniFi) | | Model | CRS326-24G-2S+ | CRS504-4XQ | Ubiquiti Switch Pro XG 8 PoE | | Switch chip | Marvell 98DX3236 | Marvell 98DX4310 (+ Atheros 8227 for the 1G mgmt port) | — | -| OS | RouterOS 7.18 | RouterOS 7.18 | UniFi | -| Ports | 24×1G + 2×SFP+ | 4×QSFP28 (100G, breakout-capable) + 1G mgmt | 8×10GBASE-T PoE + 2×25G SFP28 | +| OS | RouterOS | RouterOS | UniFi | +| Ports | 24×1G + 2×SFP+ | 4×QSFP28 (100G, breakout-capable) + 1G mgmt | 8×10GBASE-T PoE + 2×10G SFP+ | | Bridge | `main`, `vlan-filtering=yes` | `main`, `vlan-filtering=yes` | UniFi VLAN profiles | jim and dave run a single hardware-offloaded bridge (`main`) with VLAN filtering. Access to the @@ -29,52 +31,45 @@ chips); brian cannot rewrite tags, only trunk/PVID them. ## Physical topology -The ONT terminates on brian; jim's `wan-pon-in` (`sfp-sfpplus2`) is a spare SFP+ port. +The two WAN sources enter at the top: the Virgin Media modem lands on **jim** (VLAN 130), and the +Digiweb **ONT** lands on **brian**. Both `jim` and `brian` are edge switches that uplink down into +the **dave** core; the home boxes hang off dave's 100G ports, with backup links up to jim. jim's +`wan-pon-in` (`sfp-sfpplus2`) is a spare SFP+ port, unused today. ``` - Virgin Media cable modem - │ VLAN 130 (wan1 / wan2 / wan-in) - ┌───────────────┴──────────────────────────────────────┐ - │ jim CRS326-24G-2S+ (Marvell 98DX3236) │ - │ 1G edge: fort, pronter, laptop-dock, palace-kvm, │ - │ ups, ether15-20, wan1/wan2/wan-in │ - │ wan-pon-in = sfp-sfpplus2 (spare SFP+) │ - └───────┬──────────────────────────────────────────────┘ - │ dave-uplink = sfp-sfpplus1 (10G trunk) - │ also: palace, stream (1G secondaries), - │ castle (2.5G, normally down) - ┌───────┴──────────────────────────────────────────────┐ - │ dave CRS504-4XQ (Marvell 98DX4310) │ - │ jim-downlink = qsfp28-3-1 │ - └──┬──────────────────┬───────────────────┬────────────┘ - │ palace │ castle │ brian-downlink - │ = qsfp28-1-1 │ (100G) │ 802.3ad LAG - │ (100G) │ │ (brian1 + brian2) - │ … │ - ┌───────┴────────┐ ┌───────┴─────────────┐ - │ palace host │ │ brian │ - │ └ river (VM) │ │ Switch Pro XG 8 PoE │ - └────────────────┘ └───────┬─────────────┘ - river WAN + LAN ride the 100G link │ hosts the ONT - │ - ┌───────┴─────────────┐ - │ ONT (Digiweb) │ untagged mgmt - │ PPPoE via ONT │ 192.168.100.1 - └─────────────────────┘ + VLAN 10; PVID 140 + Virgin Media cable modem Digiweb ONT + stream WAN, VLAN 130 river WAN, management + VLAN 10 + | | + jim brian + | 10G trunk 802.3ad LAG | + +--------------------+ +---------------+ + | | + +---+----------+---+ + | dave | + +--------+---------+ + | + +---------------------+---------------------+ + | | | + palace (100G) castle (100G) stream + river VM NVMe-oF root second router + + Backup links to jim (normally idle): + * palace: 1G + * stream: 1G; STP selects the active link + * castle: 2.5G, normally down; no live failover ``` Notes: - **river** runs as a VM on the **palace** host; its uplink is dave's 100G `palace` port. jim also has 1G `palace`/`stream` ports, but those are secondary links and do **not** carry the WAN. - **stream** (the second router box) is dual-homed to both jim and dave (STP picks the active path). -- **castle** is dual-homed but **not** via STP: its primary uplink is dave's **100G** `castle` port - (`et100g`, active), and it has a secondary **2.5G** link to jim's `castle` edge port (`et2.5g`, - **normally down** — no live failover). ⚠️ **castle's root disk is NVMe-oF over the fabric** (via - `et100g`→dave), so rebooting **dave** — or downing castle's `et100g` — freezes castle mid-I/O. - Do dave maintenance (upgrades/reboots) from a host that doesn't depend on dave for storage or - network, or with castle cleanly powered off; don't drive it from castle. -- **brian** is a Ubiquiti **Switch Pro XG 8 PoE** (8×10GBASE-T), downlinked from dave over an - **802.3ad LAG** (`brian-downlink` = `brian1` + `brian2`, layer-2 hash). It hosts the ONT. + +### Castle storage dependency + +`castle` is dual-homed without STP: its primary link is dave's 100G `castle` port (`et100g`), while +the 2.5G link to jim (`et2.5g`) is normally down and provides no live failover. Its root disk is +NVMe-oF over `et100g` and dave, so interrupting either freezes `castle` mid-I/O. Do dave maintenance +from a box that does not depend on it, or power `castle` off cleanly first. ## VLANs @@ -115,12 +110,13 @@ we just carry it end to end and let river run PPPoE directly on it: 1500). Net result: **river runs PPPoE single-tagged on VLAN 10 and holds a VLAN 140 address to reach the -ONT.** See `nixos/boxes/home/palace/vms/river.nix` for the river side. +ONT.** See [`nixos/boxes/home/palace/vms/river.nix`](../../../nixos/boxes/home/palace/vms/river.nix) +for the river side. ``` -ONT ──(untagged + VLAN10)── brian ──(VLAN140 + VLAN10)── dave ──(VLAN140 + VLAN10)── river - ONT port │ PVID140 + tagged 10 │ plain bridging - └─ brian-downlink LAG ── dave ┘ +ONT -- untagged + VLAN 10 -- brian -- VLAN 140 + VLAN 10 -- dave -- palace -- river + | + +-- ONT port PVID 140; VLAN 10 remains tagged ``` ### Why not translation (for one ONT)? @@ -183,8 +179,10 @@ translation there, feeding distinct fabric VLANs up to dave. ## Accessing the switches The switches resolve by **short hostname** on the home network — the home routers serve their -records in the home zone (`nixos/boxes/home/routing-common/dns.nix`: `jim` → hi `.10`, `dave` → hi -`.11`, `brian` → core `.13`). From a box on the home network just `ssh admin@jim` / `admin@dave`. +records in the home zone +([`nixos/boxes/home/routing-common/dns.nix`](../../../nixos/boxes/home/routing-common/dns.nix): +`jim` → hi `.10`, `dave` → hi `.11`, `brian` → core `.13`). From a box on the home network just +`ssh admin@jim` / `admin@dave`. **Key auth** for `admin` is installed on jim/dave (and the `vibe` AP) — `ssh -i ~/.ssh/id_rsa admin@jim` works keyless (imported via `/user ssh-keys import`). Password `admin`/`admin` remains as diff --git a/lib/constants.nix b/lib/constants.nix index 60a3753..a5df815 100644 --- a/lib/constants.nix +++ b/lib/constants.nix @@ -318,7 +318,7 @@ rec { # the switches to river (PPPoE runs directly on it), and the switch at the ONT edge PVIDs the # untagged management port onto wan-pon-ont (140). wan-pon-isp (141) is reserved for the # future multi-ONT case, where per-port VLAN translation on a dedicated switch swaps each - # ONT's VLAN 10 to a distinct fabric VLAN (see home-switches.md). + # ONT's VLAN 10 to a distinct fabric VLAN (see docs/sites/home/switches.md). pon-isp = 10; wan-pon-ont = 140; wan-pon-isp = 141; diff --git a/nixos/boxes/home/palace/vms/river.nix b/nixos/boxes/home/palace/vms/river.nix index d3810b0..31c13d6 100644 --- a/nixos/boxes/home/palace/vms/river.nix +++ b/nixos/boxes/home/palace/vms/river.nix @@ -20,7 +20,7 @@ # collision, so the switches simply trunk the ISP's VLAN 10 straight through to river (PPPoE # runs directly on it) and PVID the ONT's untagged management port onto wan-pon-ont (140). # river takes .100 in the ONT's /24 (matching stream's modem-mgmt .100) to reach its web - # UI at 192.168.100.1. (See home-switches.md for the switch side and the multi-ONT plan.) + # UI at 192.168.100.1. (See docs/sites/home/switches.md for the switch side and the multi-ONT plan.) ontV4 = net.cidr.host 100 prefixes.ont.v4; # river is routing-common index 0; the Digiweb static IP we request via IPCP