docs: Document the deployment
Add a top-level README mapping the boxes and a full docs/ tree: topic pages (architecture, networking, deployment), per-site box pages for colony and home with containers nested under their hosts, remote and mobile boxes, the installer, and the home switch fabric reference (folded in from home-switches.md, with AGENTS.md and code comments retargeted to its new home). Box pages carry marked assignment tables that CI regenerates from nixos.allAssignments. AGENTS.md points at the new docs and keeps its terse agent version of the mechanics, referring to the topic pages for depth.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# 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)
|
||||
|
||||
## Shape
|
||||
|
||||
`colony` is the physical VM host. It runs the VMs below; `shill` is itself a
|
||||
NixOS container host where most applications run.
|
||||
|
||||
```
|
||||
colony (physical VM host, ams1)
|
||||
├── estuary ── edge router: WAN, firewall/NAT, DNS, BGP (AS211024), WireGuard
|
||||
├── shill ──── NixOS container host ──┬── middleman (reverse proxy, ACME, nginx-sso, librespeed)
|
||||
│ ├── vaultwarden (password manager)
|
||||
│ ├── colony-psql (shared PostgreSQL)
|
||||
│ ├── chatterbox (Matrix Synapse + bridges)
|
||||
│ ├── jackflix (media stack)
|
||||
│ ├── object (MinIO, Harmonia Nix cache, Sharry, HedgeDoc, wastebin)
|
||||
│ ├── toot (Bluesky PDS; Mastodon disabled)
|
||||
│ ├── waffletail (Tailscale subnet router / exit node)
|
||||
│ ├── qclk (WireGuard management appliance)
|
||||
│ ├── gam (Terraria server)
|
||||
│ └── jam (raw nspawn customer container)
|
||||
├── whale2 ─── podman/OCI host for game servers
|
||||
├── git ────── Gitea + Gitea Actions runner
|
||||
├── mail ───── Debian VM running mailcow (not NixOS)
|
||||
└── darts ──── third-party/customer VM (opaque, not NixOS)
|
||||
```
|
||||
|
||||
## Networks
|
||||
|
||||
All internal space is carved out of `10.100.0.0/16` and `2a0e:97c0:4d2:10::/60`
|
||||
(`lib.my.c.colony.prefixes`):
|
||||
|
||||
| Network | IPv4 CIDR | IPv6 CIDR | Purpose |
|
||||
|---|---|---|---|
|
||||
| `base` | `10.100.0.0/24` | `2a0e:97c0:4d2:10::/64` | Base LAN shared by `colony` and `estuary` |
|
||||
| `vms` | `10.100.1.0/24` | `2a0e:97c0:4d2:11::/64` | VM network |
|
||||
| `ctrs` | `10.100.2.0/24` | `2a0e:97c0:4d2:12::/64` | `shill` container network |
|
||||
| `oci` | `10.100.3.0/24` | `2a0e:97c0:4d2:13::/64` | `whale2` podman network |
|
||||
|
||||
Public addressing — the WAN /24 (`94.142.240.44`), the `vip*` ranges shared by
|
||||
the VMs, and the customer /32s for `mail` / `darts` — terminates on `estuary`;
|
||||
see [estuary.md](estuary.md).
|
||||
|
||||
## Machines
|
||||
|
||||
| Machine | Role | Page |
|
||||
|---|---|---|
|
||||
| `colony` | Physical VM host (AMD, KVM, LVM-thin, `borgthin` backups → rsync.net) | [colony.md](colony.md) |
|
||||
| `estuary` | Edge router: WAN, firewall/NAT, DNS, BGP (AS211024), WireGuard | [estuary.md](estuary.md) |
|
||||
| `shill` | NixOS container host (most applications) | [shill.md](shill.md) |
|
||||
| `whale2` | podman/OCI game-server host | [whale2.md](whale2.md) |
|
||||
| `git` | Gitea + Gitea Actions runner | [git.md](git.md) |
|
||||
| `mail` | Debian VM running mailcow (not NixOS) | [mail.md](mail.md) |
|
||||
| `darts` | Third-party/customer VM (not NixOS) | [darts.md](darts.md) |
|
||||
|
||||
### `shill` containers
|
||||
|
||||
Each has its own page under `shill/containers/`:
|
||||
|
||||
| Container | Role | Page |
|
||||
|---|---|---|
|
||||
| `middleman` | Front-end nginx reverse proxy, ACME, nginx-sso, librespeed | [middleman](shill/containers/middleman.md) |
|
||||
| `vaultwarden` | Vaultwarden password manager | [vaultwarden](shill/containers/vaultwarden.md) |
|
||||
| `colony-psql` | Shared PostgreSQL for colony services | [colony-psql](shill/containers/colony-psql.md) |
|
||||
| `chatterbox` | Matrix homeserver + bridges | [chatterbox](shill/containers/chatterbox.md) |
|
||||
| `jackflix` | Media stack (Jellyfin, *arr, Transmission, PhotoPrism, copyparty) | [jackflix](shill/containers/jackflix.md) |
|
||||
| `object` | MinIO (S3), Harmonia Nix cache, Sharry, HedgeDoc, wastebin | [object](shill/containers/object.md) |
|
||||
| `toot` | Bluesky PDS (Mastodon disabled) | [toot](shill/containers/toot.md) |
|
||||
| `waffletail` | Tailscale subnet router / exit node | [waffletail](shill/containers/waffletail.md) |
|
||||
| `qclk` | WireGuard management appliance | [qclk](shill/containers/qclk.md) |
|
||||
| `gam` | Terraria server | [gam](shill/containers/gam.md) |
|
||||
|
||||
## Non-NixOS VMs
|
||||
|
||||
Two VMs are declared in `colony`'s `my.vms.instances` — so `colony` runs them
|
||||
and routes/firewalls their traffic — but they are **not** managed as NixOS
|
||||
systems by this repo:
|
||||
|
||||
- **`mail`** — a Debian VM running [mailcow](https://mailcow.email/)
|
||||
(`mail.nul.ie`). ACME certificates are pushed to it from `middleman`.
|
||||
See [mail.md](mail.md).
|
||||
- **`darts`** — an opaque third-party/customer VM, given a routed public /32
|
||||
and IPv6 /64 and otherwise left alone. See [darts.md](darts.md).
|
||||
@@ -0,0 +1,86 @@
|
||||
# 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:** QEMU/KVM (`kvm-amd`, IOMMU on) driven by the `my.vms`
|
||||
module: each entry in `my.vms.instances` becomes a `vm@<name>` systemd
|
||||
service running `qemu-kvm` with UEFI, a QMP/monitor socket under
|
||||
`/run/vms/<name>/`, TAP networking and optional PCI passthrough
|
||||
(`hostDevices`, bound to `vfio-pci`). `estuary` gets the WAN NIC this way.
|
||||
- **Storage:** LVM-thin (`services.lvm.boot.thin`) in the `main` VG; VM disks
|
||||
are logical volumes (`vm-<name>-<disk>`, see the `lib.my.vm.disk` /
|
||||
`lvmDisk` helpers). `/persist` holds host state, `/mnt/backup` the local
|
||||
borg repo. Only the boot-critical LVs are activated in the initrd; the rest
|
||||
come up via `lvm-activate-main.service`.
|
||||
- **Backups:** `my.borgthin` job `main` snapshots the persist/data LVs of the
|
||||
host and its VMs into `/mnt/backup/main`; `borgthin-rsync.service` then
|
||||
rsyncs the repo to rsync.net and `rsync-lvm-meta.service` ships the LVM
|
||||
metadata alongside (both run idle-priority, after the borg job).
|
||||
- **Monitoring/health:** netdata (freeipmi, ignoring the VCCM sensor), `smartd`
|
||||
(logging to `/var/log/smartd`), `rasdaemon`, `fstrim` at 04:45 (before the
|
||||
05:00 backup).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: colony -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| colony (vm) | internal | `94.142.241.224/32` | `2a0e:97c0:4d2:10::2/64` | ams1.int.nul.ie | |
|
||||
| colony-routing | routing | `10.100.0.2/24 gw 10.100.0.1` | — | ams1.int.nul.ie | |
|
||||
| colony-vms | vms | `10.100.1.1/24` | `2a0e:97c0:4d2:11::1/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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 (a `vip1` /32, 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 | MAC | Disks |
|
||||
|---|---|---|---|---|---|
|
||||
| `estuary` | 2 | 2 | 3 GiB | `52:54:00:15:1a:53` (`base`) | `esp` / `nix` / `persist` LVs + WAN NIC passthrough |
|
||||
| `shill` | 12 | 2 | 40 GiB | `52:54:00:27:3d:5c` (`vms`) | `esp` / `nix` / `persist` + `media` / `minio` / `nix-cache` / `jam` LVs |
|
||||
| `whale2` | 8 | 2 | 16 GiB | `52:54:00:d5:d9:c6` (`vms`) | `esp` / `nix` / `persist` + `oci` LV |
|
||||
| `git` | 12 | 2 | 40 GiB | `52:54:00:75:78:a8` (`vms`) | `esp` / `nix` / `persist` / `oci` + `git` / `gitea-actions-cache` LVs |
|
||||
| `mail` | 3 | 2 | 6 GiB | `52:54:00:a8:d1:03` (`vm-mail` tap) | `root` / `data` LVs |
|
||||
| `darts` | 4 | 2 | 16 GiB | `52:54:00:a8:29:cd` (`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#machines)); `mail` and `darts` are not (see
|
||||
[Non-NixOS VMs](README.md#non-nixos-vms)).
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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`
|
||||
|
||||
## Role
|
||||
|
||||
- Customer/dedicated VM, left alone beyond hosting and connectivity.
|
||||
- 4 cores, 16 GiB RAM; a `root` LV plus `darts-media` and `darts-ext` LVs from
|
||||
the `media`/`ext` volume groups.
|
||||
|
||||
## Networking
|
||||
|
||||
- Same customer-VM pattern as [`mail`](mail.md): dedicated unbridged TAP
|
||||
(`vm-darts`, MAC `52:54:00:a8:29:cd`), 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`.
|
||||
@@ -0,0 +1,145 @@
|
||||
# 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
|
||||
(`94.142.240.44/24`, `2a02:898:0:20::329:1/64`), NATs outbound traffic, and
|
||||
port-forwards inbound services (`my.firewall.nat.forwardPorts` driven by the
|
||||
shared `lib.my.c.colony.firewallForwards` list): HTTP/S and Matrix
|
||||
federation to `middleman`, git to `git`, game ports to the OCI servers on
|
||||
`whale2` and to `gam`, Tailscale to `waffletail`, and the `qclk` WireGuard
|
||||
port.
|
||||
- **DNS:** PowerDNS authoritative server *and* recursor (see below).
|
||||
- **BGP:** BIRD2 speaking AS211024 with upstreams, IXP route servers and
|
||||
direct peers (see below).
|
||||
- **VPNs:** member of the `as211024` L2 VXLAN mesh and endpoint for three
|
||||
point-to-point WireGuard tunnels (see below).
|
||||
- **Misc:** `iperf3` server, netdata.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: estuary -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| estuary | as211024 | `10.100.50.1/24` | `2a0e:97c0:4df::1/64` | — | |
|
||||
| estuary-vm-base | base | `10.100.0.1/24` | `2a0e:97c0:4d2:10::1/64` | ams1.int.nul.ie | |
|
||||
| estuary-vm (fw) | internal | `94.142.240.44/24 gw 94.142.240.254` | `2a02:898:0:20::329:1/64 gw 2a02:898:0:20::1` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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). Besides the port 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`) listens on `0.0.0.0:5353` / `[::]:5353` as
|
||||
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`),
|
||||
and `_acme-challenge` is a LUA `TXT` record answered from a file (used for
|
||||
DNS-01 issuance). Public DNS reaches it via the NAT redirect of port 53 to
|
||||
5353; the `base` side also accepts DNS directly.
|
||||
- **Recursor** (`my.pdns.recursor`, `pdns-recursor`) listens on localhost and
|
||||
the `base` addresses, serving `prefixes.all` and the Tailscale prefixes. The
|
||||
authoritative zones are forwarded back to `127.0.0.1:5353` (with NOTIFY
|
||||
support so changes show up immediately), and a small Lua `preresolve` hook
|
||||
rewrites `nix-cache.nul.ie` to `http.ams1.int.nul.ie` so cache traffic stays
|
||||
on-site.
|
||||
|
||||
## BGP
|
||||
|
||||
BIRD2 ([`bgp.nix`](../../../nixos/boxes/colony/vms/estuary/bgp.nix)) speaking **AS211024**:
|
||||
|
||||
- **Upstreams:** ColoClue (AS8283, `euNetworks` 2/3, v4+v6); iFog IPv6 transit
|
||||
(AS34927); Hurricane Electric IPv6 over Frys-IX (AS6939).
|
||||
- **IXP route servers:** Frys-IX (AS56393), NL-ix (AS34307, depref'd by 1),
|
||||
FogIXP (AS47498).
|
||||
- **Direct peers:** LUJE.net (AS212855, on ColoClue/Frys-IX/FogIXP + multihop
|
||||
labs), jurrian (AS212635), Meta (AS32934, Frys-IX/NL-ix), Cloudflare
|
||||
(AS13335, Frys-IX), Apple (AS714, NL-ix), HE (AS6939, Frys-IX v4).
|
||||
- **Monitoring:** a multihop session to the bgp.tools collector (AS212232)
|
||||
exporting everything with `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** (`my.vpns.l2`, the `l2mesh` module): VXLAN (VNI
|
||||
211024, UDP-encapsulated) secured with libreswan IPsec, meshing estuary with
|
||||
the home routers `river`/`stream` and `britway`. This carries the AS211024
|
||||
anycast-ish internal address space between sites.
|
||||
- **WireGuard endpoints** (networkd `wireguard` netdevs, keys from agenix):
|
||||
- `kelder` — tunnel to the remote `kelder` site, port `51820`.
|
||||
- `hillcrest` — port `51822`, point-to-point /32 pair out of
|
||||
`prefixes.hillcrest.v4`.
|
||||
- `john-valorant` — port `51823`, same pattern out of
|
||||
`prefixes.john-valorant.v4`.
|
||||
|
||||
## Bandwidth management
|
||||
|
||||
[`bandwidth.nix`](../../../nixos/boxes/colony/vms/estuary/bandwidth.nix)
|
||||
implements a ~95% WAN shaper: a 245 Mbit 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/utilises the link and can adjust
|
||||
the 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).
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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** ([`gitea.nix`](../../../nixos/boxes/colony/vms/git/gitea.nix)) — the
|
||||
Git forge at `git.nul.ie` (self-registration disabled). Backed by PostgreSQL
|
||||
on `colony-psql` (waiting on it via `lib.my.systemdAwaitPostgres`), LFS
|
||||
enabled, with all object storage (incl. LFS and packages) on MinIO at
|
||||
`s3.nul.ie` (bucket `gitea`, on `object`; 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**
|
||||
([`gitea-actions.nix`](../../../nixos/boxes/colony/vms/git/gitea-actions.nix)) —
|
||||
one Docker-mode instance (`main-docker`) on podman (privileged, `podman`
|
||||
network), with 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; 8 h job
|
||||
timeout; the action cache lives on a dedicated disk
|
||||
(`/var/cache/gitea-runner`). This 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
|
||||
|
||||
<!-- assignments: git -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| git-vm | internal | `94.142.241.117/32` | `2a0e:97c0:4d2:11::4/64` | ams1.int.nul.ie | |
|
||||
| git-vm-routing | routing | `10.100.1.4/24 gw 10.100.1.1` | — | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,36 @@
|
||||
# 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`
|
||||
|
||||
## 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).
|
||||
- 3 cores, 6 GiB RAM; `root` and `data` LVM disks (the `vm-mail-data` LV is
|
||||
included in `colony`'s `borgthin` backups).
|
||||
|
||||
## Networking
|
||||
|
||||
- Attaches to a dedicated, unbridged TAP (`vm-mail`, MAC
|
||||
`52:54:00:a8:d1:03`). `colony` puts the point-to-point address
|
||||
(`custRouting.mail-vm`) on the host side, link-routes the public /32
|
||||
`94.142.241.227` down the tap, and serves the IPv6 /64
|
||||
`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`.
|
||||
@@ -0,0 +1,83 @@
|
||||
# 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` (large: 12 cores, 40 GiB RAM)
|
||||
- **nixpkgs:** `mine`
|
||||
|
||||
## Role
|
||||
|
||||
- Runs the colony containers via `my.containers.instances`, each attached to
|
||||
the `ctrs` bridge with its own address. The containers are full NixOS
|
||||
systems rendered via `my.asContainer` and deployed as container profiles on
|
||||
`shill` (`my.deploy.enable = false` — they are not standalone deploy
|
||||
targets); the `my.containers` module wires up the nspawn units, `/persist`
|
||||
bind mounts and store binds.
|
||||
- Provides shared data volumes to containers via bind mounts from LVM-backed
|
||||
disks: `/mnt/media` (→ `middleman` read-only, `jackflix` read-write),
|
||||
`/mnt/minio` and `/mnt/nix-cache` (→ `object`, both read-write).
|
||||
- Routes between the `vms` network and the `ctrs` container network: sends RAs
|
||||
on `ctrs` (DNS = `estuary`'s base address) and routes the Tailscale prefixes
|
||||
via `waffletail` and the `qclk` prefix via `qclk`. Applies the shared
|
||||
`firewallForwards` DNAT for traffic addressed to `estuary`'s public IP, with
|
||||
an nftables `ct mark 0x1337` SNAT hack so forwarded return traffic stays
|
||||
symmetric.
|
||||
- Tuned for high connection counts (larger conntrack table, wider ephemeral
|
||||
port range); netdata on 19999.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: shill -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| shill-vm-ctrs | ctrs | `10.100.2.1/24` | `2a0e:97c0:4d2:12::1/64` | ams1.int.nul.ie | |
|
||||
| shill-vm (ctr) | internal | `94.142.241.225/32` | `2a0e:97c0:4d2:11::2/64` | ams1.int.nul.ie | |
|
||||
| shill-vm-routing | routing | `10.100.1.2/24 gw 10.100.1.1` | — | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## Containers
|
||||
|
||||
Defined under
|
||||
[`shill/containers/`](../../../nixos/boxes/colony/vms/shill/containers) and
|
||||
wired up in `shill`'s `my.containers.instances`. Each has its own page:
|
||||
|
||||
| Container | IPv4 | IPv6 | Role | Page |
|
||||
|---|---|---|---|---|
|
||||
| `middleman` | `10.100.2.2` | `2a0e:97c0:4d2:12::2` | Reverse proxy, ACME, nginx-sso, librespeed | [middleman](shill/containers/middleman.md) |
|
||||
| `vaultwarden` | `10.100.2.3` | `2a0e:97c0:4d2:12::3` | Password manager | [vaultwarden](shill/containers/vaultwarden.md) |
|
||||
| `colony-psql` | `10.100.2.4` | `2a0e:97c0:4d2:12::4` | Shared PostgreSQL (14) | [colony-psql](shill/containers/colony-psql.md) |
|
||||
| `chatterbox` | `10.100.2.5` | `2a0e:97c0:4d2:12::5` | Matrix Synapse + bridges | [chatterbox](shill/containers/chatterbox.md) |
|
||||
| `jackflix` | `10.100.2.6` | `2a0e:97c0:4d2:12::6` | Media stack | [jackflix](shill/containers/jackflix.md) |
|
||||
| `object` | `10.100.2.7` | `2a0e:97c0:4d2:12::7` | MinIO, Harmonia Nix cache, Sharry, HedgeDoc, wastebin | [object](shill/containers/object.md) |
|
||||
| `toot` | `10.100.2.8` | `2a0e:97c0:4d2:12::8` | Bluesky PDS (Mastodon disabled) | [toot](shill/containers/toot.md) |
|
||||
| `waffletail` | `10.100.2.9` | `2a0e:97c0:4d2:12::9` | Tailscale subnet router / exit node | [waffletail](shill/containers/waffletail.md) |
|
||||
| `qclk` | `10.100.2.10` | `2a0e:97c0:4d2:12::a` | WireGuard management appliance | [qclk](shill/containers/qclk.md) |
|
||||
| `gam` | `10.100.2.11` | `2a0e:97c0:4d2:12::b` | Terraria server | [gam](shill/containers/gam.md) |
|
||||
|
||||
### `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.
|
||||
@@ -0,0 +1,45 @@
|
||||
# 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`](../../shill.md)
|
||||
|
||||
## Role
|
||||
|
||||
- **matrix-synapse** — `server_name = "nul.ie"`, `public_baseurl = https://matrix.nul.ie`,
|
||||
Element at `element.nul.ie` as the web client. Listens on `[::]:8008` (client + federation
|
||||
resources, `x_forwarded`) with a localhost manhole on `:9000`. Registration and guest access
|
||||
are disabled; uploads up to 1024M with dynamic thumbnails and URL previews enabled (previews
|
||||
are limited to [middleman](middleman.md)'s addresses as the fetch proxy).
|
||||
- **heisenbridge** — IRC bridge, owner `@dev:nul.ie`, exclusive `@irc_*` user namespace.
|
||||
- **mautrix-whatsapp** — WhatsApp bridge (appservice `whatsapp2`, `!wa` commands).
|
||||
- **mautrix-meta** — two instances, `messenger` (`fbm2_*`, `!fbm`) and `instagram` (`ig_*`,
|
||||
`!ig`), both with backfill enabled.
|
||||
- All three mautrix bridges use Postgres on [colony-psql](colony-psql.md) (URIs in their secret
|
||||
env files), require end-to-end encryption by default, and double-puppet onto `nul.ie` via the
|
||||
shared `doublepuppet.yaml` appservice registration (an age secret).
|
||||
- The firewall allows `8008` (Synapse) and `8009` besides netdata.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: chatterbox -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| chatterbox-ctr | internal | `10.100.2.5/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::5/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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`](../../shill.md)
|
||||
|
||||
## Role
|
||||
|
||||
- **PostgreSQL 14** with TCP/IP enabled, reachable from the whole colony (`10.100.0.0/16` and
|
||||
`2a0e:97c0:4d2:10::/60`, `md5` auth). 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
|
||||
|
||||
<!-- assignments: colony-psql -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| colony-psql-ctr (colony-psql) | internal | `10.100.2.4/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::4/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
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
|
||||
@@ -0,0 +1,27 @@
|
||||
# gam
|
||||
|
||||
A game-server container — the lightweight counterpart to the OCI game servers on `whale2`,
|
||||
running servers directly as NixOS services. Currently it runs a single Terraria server.
|
||||
|
||||
- **Source:** [`shill/containers/gam.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/gam.nix)
|
||||
- **Host:** NixOS container on [`shill`](../../shill.md)
|
||||
|
||||
## Role
|
||||
|
||||
- **terraria** — dedicated server (`services.terraria`): world at
|
||||
`/var/lib/terraria/NotWorld.wld`, auto-created large worlds, MOTD "sup gamers", UPnP off.
|
||||
Extra settings (e.g. password) come from the `gam/terraria.conf` age secret used as the config
|
||||
file. `openFirewall` is on, and `estuary` port-forwards TCP and UDP `7777` to this container.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: gam -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| gam-ctr | internal | `10.100.2.11/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::b/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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`](../../shill.md) (bind-mounts `/mnt/media` read-write)
|
||||
|
||||
## 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 (gid 2000) plus
|
||||
`UMask=0002` on Radarr/Sonarr gives the apps coordinated access to the media volume.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: jackflix -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| jackflix-ctr | internal | `10.100.2.6/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::6/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## VPN download path
|
||||
|
||||
[`networking.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix)
|
||||
defines a `vpn` WireGuard netdev to **AirVPN NL** (key + PSK from age secrets, MTU 1320 per
|
||||
AirVPN, fwmark `42`, route table `51820`):
|
||||
|
||||
- Policy routing keeps colony traffic on the main table (from/to `prefixes.all` v4/v6 rules at
|
||||
priority 100, plus a default-route suppression) while everything else falls through to the VPN
|
||||
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 peer port `28457` 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` dir, 28 MB/s up/down limits
|
||||
and a seed ratio limit of 2.0.
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,94 @@
|
||||
# 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`](../../shill.md) (`my.containers` ephemeral nspawn on the
|
||||
`ctrs` bridge; bind-mounts `/mnt/media` read-only for the static file vhosts)
|
||||
|
||||
## Role
|
||||
|
||||
- **nginx** reverse proxy with the `vts` and `fancyindex` modules, Brotli, kTLS, a proxy cache,
|
||||
and a dynamic resolver pointed at `estuary` (`proxyResolveWhileRunning`) so upstreams re-resolve
|
||||
at runtime. All vhosts are declared in
|
||||
[`vhosts.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix);
|
||||
upstreams are addressed by their internal container/VM hostnames under `ams1.int.nul.ie`.
|
||||
nginx is made to wait for `colony-psql` (`systemdAwaitPostgres`) before starting — a hack for
|
||||
it getting stuck on DNS early in boot.
|
||||
- **ACME** — issues the wildcard certificates its own vhosts are served with (it is not a shared
|
||||
CA for the other boxes):
|
||||
- `ams1.int.nul.ie` + `*.ams1.int.nul.ie` via a lego `exec` DNS challenge that SSHes to
|
||||
`pdns-file-records@estuary-vm` to add/remove records in the internal PowerDNS zone. This is
|
||||
the default cert (`useACMEHost`) for vhosts on the internal domain.
|
||||
- `nul.ie` + `*.nul.ie` + `*.s3.nul.ie` via the Cloudflare DNS provider. A `postRun` hook
|
||||
`scp`s the renewed key/cert to the `mail` (Mailcow) VM and runs `mailcow-ssl-reload` there.
|
||||
- Certs reload `nginx` on renewal; the `acme` group owns the secret files and `nginx` is a
|
||||
member.
|
||||
- **nginx-sso** — single sign-on at `sso.nul.ie` (one instance, `generic`), with Google OAuth as
|
||||
the default method and a simple username/password provider. Cookie domain is `.nul.ie`.
|
||||
SSO-gated vhosts below include `server-generic.conf` / `location-generic.conf` from
|
||||
`/etc/nginx/includes/sso/`.
|
||||
- **librespeed** — speed test, backend + frontend, published as `speed.nul.ie` and
|
||||
`librespeed.ams1.int.nul.ie` (both proxy to `localhost:8989`).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: middleman -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| middleman-ctr | internal | `10.100.2.2/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::2/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
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` | `<host>.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`) |
|
||||
| `mc-rail.nul.ie` | `simpcraft-oci:3876` | Minecraft railway map |
|
||||
| `mc-map-kink.nul.ie` | `kinkcraft-oci:8100` | Minecraft map |
|
||||
| `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; `/webcam/` → `:5050`; **SSO** |
|
||||
| `hass.nul.ie` | `hass-ctr.h.nul.ie:8123` | Home Assistant (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
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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`](../../shill.md) (bind-mounts `/mnt/minio` and
|
||||
`/mnt/nix-cache` read-write)
|
||||
|
||||
## 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; priority 30 |
|
||||
| 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`; max share size 128G |
|
||||
| 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
|
||||
|
||||
<!-- assignments: object -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| object-ctr | internal | `10.100.2.7/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::7/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,39 @@
|
||||
# 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`](../../shill.md)
|
||||
|
||||
## 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 (`10.100.4.0/24`); the peer list currently has a single entry (host 2).
|
||||
- **Routing/NAT** — the container itself is host 1 of the `qclk` prefix. `shill` routes
|
||||
`10.100.4.0/24` 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
|
||||
|
||||
<!-- assignments: qclk -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| qclk-ctr | internal | `10.100.2.10/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::a/64` | ams1.int.nul.ie | |
|
||||
| qclk | qclk | `10.100.4.1/24` | — | — | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
Two assignments: `internal` on the `ctrs` network like the other containers, and `qclk` — host 1
|
||||
of the `qclk` prefix 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
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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`](../../shill.md)
|
||||
|
||||
## Role
|
||||
|
||||
- **bluesky-pds** — the active service. `pds.nul.ie` on port `3000`, fronted by
|
||||
[middleman](middleman.md) (which also redirects `/.well-known/atproto-did` here). Invites
|
||||
required; blob store is the `pds` bucket on [object](object.md)'s MinIO (`s3.nul.ie`,
|
||||
`eu-central-1`), upload limit 50 MiB; federation settings point at the stock Bluesky
|
||||
infrastructure (`plc.directory`, `api.bsky.app`, `mod.bsky.app`, `bsky.network` crawlers).
|
||||
Secrets (including the S3 credentials) come from the `toot/pds.env` age secret. Email from
|
||||
`pds@nul.ie`.
|
||||
- **mastodon** — **disabled** (`services.mastodon.enable = false`). The full config is still
|
||||
present: `LOCAL_DOMAIN = nul.ie` with `WEB_DOMAIN = toot.nul.ie`, Postgres on
|
||||
[colony-psql](colony-psql.md), local Redis, SMTP via `mail.nul.ie`, media in the `mastodon`
|
||||
MinIO bucket (`S3_ALIAS_HOST = mastodon.s3.nul.ie`), 4 streaming processes, and media
|
||||
auto-cleanup after 30 days. [middleman](middleman.md) still proxies `toot.nul.ie` →
|
||||
`toot-ctr:80` and redirects the `webfinger`/`nodeinfo`/`host-meta` well-knowns there, but with
|
||||
the service off those endpoints are dead. The config notes the removed `otpSecretFile` option
|
||||
would need addressing before Mastodon can come back.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: toot -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| toot-ctr | internal | `10.100.2.8/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::8/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
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
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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`](../../shill.md)
|
||||
|
||||
## 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
|
||||
(`zh2855@zh2855.rsync.net:borg/vaultwarden2`), repokey-encrypted (passphrase and SSH key from
|
||||
secrets), `zstd,10` compression, keeping 7 daily / 4 weekly / all monthly archives.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: vaultwarden -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| vaultwarden-ctr | internal | `10.100.2.3/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::3/64` | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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`](../../shill.md)
|
||||
|
||||
## Role
|
||||
|
||||
- **tailscale** — joins via an auth key from secrets (`tailscale-auth.key`) against the
|
||||
self-hosted Headscale control plane at `hs.nul.ie`. Runs with `--netfilter-mode=off` (firewall
|
||||
is managed by the repo's own nftables rules), `--advertise-exit-node`, and
|
||||
`--advertise-routes` covering the whole colony — `10.100.0.0/16` and `2a0e:97c0:4d2:10::/60`.
|
||||
Does **not** accept routes itself. Listens on UDP `41641` (`openFirewall`), which `estuary`
|
||||
port-forwards to this container.
|
||||
- `shill` routes the Tailscale prefixes (`100.64.0.0/10`, `fd7a:115c:a1e0::/48`) to this
|
||||
container, so colony hosts can reach tailnet clients and vice versa.
|
||||
- nftables: `tailscale0` is a trusted interface; forwarding from `host0` into Tailscale is
|
||||
allowed for the colony source ranges, and tailnet-sourced traffic leaving via `host0` is
|
||||
SNATed to the container's colony addresses (except when destined to the colony ranges
|
||||
themselves).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: waffletail -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| waffletail-ctr | internal | `10.100.2.9/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::9/64` | ams1.int.nul.ie | |
|
||||
| waffletail | tailscale | `100.64.0.5/32` | `fd7a:115c:a1e0::5/128` | — | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
Two assignments: `internal` on the `ctrs` network like the other containers, and `tailscale` —
|
||||
its addresses on the tailnet itself (host 5 of `100.64.0.0/10` and `fd7a:115c:a1e0::/48`; 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
|
||||
@@ -0,0 +1,84 @@
|
||||
# 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
|
||||
|
||||
- Runs OCI containers via podman (`virtualisation.oci-containers`, netavark
|
||||
backend, `firewall_driver = "none"` so podman doesn't fight `my.firewall`).
|
||||
- Each game server gets its own routable address on the `colony` netavark
|
||||
network (defined in `/etc/containers/networks/colony.json`), which is backed
|
||||
by the `oci` interface and the `prefixes.oci` v4/v6 ranges; per-game
|
||||
addresses come from `extraAssignments` (`valheim-oci`, `simpcraft-oci`, …)
|
||||
and are passed to podman with `--network=colony:ip=…` (`lib.my.dockerNetAssignment`).
|
||||
- `estuary` forwards the game ports in (see `firewallForwards`), so the
|
||||
servers are reachable on the public IP as well as directly over IPv6.
|
||||
- `/var/lib/containers` is a dedicated XFS disk (project quotas).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: whale2 -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| whale-vm (oci) | internal | `94.142.241.226/32` | `2a0e:97c0:4d2:11::3/64` | ams1.int.nul.ie | |
|
||||
| whale-vm-oci | oci | `10.100.3.1/24` | `2a0e:97c0:4d2:13::1/64` | ams1.int.nul.ie | |
|
||||
| whale-vm-routing | routing | `10.100.1.3/24 gw 10.100.1.1` | — | ams1.int.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## Game servers
|
||||
|
||||
The OCI containers are documented here (they have no pages of their own).
|
||||
Addresses are the per-container `extraAssignments` on the `oci` network;
|
||||
ports are the public ones forwarded by `estuary`.
|
||||
|
||||
| Container | Address (v4 / v6 host) | Ports | Status |
|
||||
|---|---|---|---|
|
||||
| `valheim` | `10.100.3.2` / `2a0e:97c0:4d2:13::2` | `2456-2457`/udp | running |
|
||||
| `simpcraft` | `10.100.3.3` / `2a0e:97c0:4d2:13::3` | `25565` tcp+udp | running |
|
||||
| `simpcraft-staging` | `10.100.3.4` / `2a0e:97c0:4d2:13::4` | `25566` tcp | **disabled** (commented out) |
|
||||
| `enshrouded` | `10.100.3.5` / `2a0e:97c0:4d2:13::5` | `15636-15637`/udp | **disabled** (`enshrouded.nix` not imported) |
|
||||
| `kevcraft` | `10.100.3.6` / `2a0e:97c0:4d2:13::6` | `25567` tcp+udp | running |
|
||||
| `kinkcraft` | `10.100.3.7` / `2a0e:97c0:4d2:13::7` | `25568` tcp+udp | running |
|
||||
| `graeme` | `10.100.3.8` / `2a0e:97c0:4d2:13::8` | `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, 8 GiB heap.
|
||||
**simpcraft-staging** is the same setup pinned to an older pack version,
|
||||
currently commented out.
|
||||
- **kevcraft** — vanilla Minecraft 1.20.1, 4 GiB heap, extra op.
|
||||
- **kinkcraft** — same Simpcraft modpack as `simpcraft`, 6 GiB heap.
|
||||
- **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 every ~15 minutes (offset from the usual 5-minute autosave
|
||||
ticks) into `/var/lib/containers/backup/simpcraft`, using `mcrcon` to
|
||||
`save-off`/`save-on` around each run. Retention is short (12 h + 48 hourly) —
|
||||
this is for quick world rollback, not 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).
|
||||
@@ -0,0 +1,81 @@
|
||||
# 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)
|
||||
|
||||
## Hierarchy
|
||||
|
||||
```
|
||||
h.nul.ie
|
||||
├── palace (physical VM host — AMD, 100G, SR-IOV)
|
||||
│ ├── river ── primary router VM (PPPoE / Digiweb WAN)
|
||||
│ ├── cellar ─ NVMe-oF / SPDK storage target VM
|
||||
│ └── sfh ──── container host VM ("services for home")
|
||||
│ ├── hass ── Home Assistant + Frigate + MQTT (container)
|
||||
│ └── unifi ─ UniFi controller (container)
|
||||
├── stream (physical secondary router — Virgin Media WAN)
|
||||
└── castle (workstation / gaming desktop — netboot, NVMe-oF root)
|
||||
```
|
||||
|
||||
## Machines
|
||||
|
||||
| Box | Role | Host | Page |
|
||||
|---|---|---|---|
|
||||
| `palace` | VM host | physical | [palace.md](palace.md) |
|
||||
| `river` | Primary router (VRRP pair with `stream`) | VM on `palace` | [river.md](river.md) |
|
||||
| `stream` | Secondary router (VRRP pair with `river`) | physical | [stream.md](stream.md) |
|
||||
| `cellar` | NVMe-oF / SPDK storage target | VM on `palace` | [cellar.md](cellar.md) |
|
||||
| `sfh` | NixOS container host | VM on `palace` | [sfh.md](sfh.md) |
|
||||
| `castle` | Workstation / gaming desktop | physical | [castle.md](castle.md) |
|
||||
| `hass` | Home Assistant + Frigate + MQTT | container on `sfh` | [sfh/containers/hass.md](sfh/containers/hass.md) |
|
||||
| `unifi` | UniFi controller | container on `sfh` | [sfh/containers/unifi.md](sfh/containers/unifi.md) |
|
||||
|
||||
## Router VIPs
|
||||
|
||||
Clients never use a router's real address: `kea` (DHCP) and `radvd` (RAs) hand out the per-VLAN
|
||||
floating VIPs as both gateway and DNS server, and `keepalived` moves them between `river` and
|
||||
`stream`. They are declared as pseudo-systems (`router-hi` / `router-lo` / `router-ut`) in
|
||||
`routing-common`'s `extraAssignments`, with addresses from `lib.my.c.home.vips`
|
||||
([`lib/constants.nix`](../../../lib/constants.nix)); `pdns-recursor` binds them on both routers
|
||||
(`ip_nonlocal_bind`) so DNS follows the master instead of relying on client resolver timeouts.
|
||||
|
||||
| Pseudo-system | Network | IPv4 | IPv6 |
|
||||
|---|---|---|---|
|
||||
| `router-hi` | `hi` (VLAN 100) | `192.168.71.254/22` | `2a0e:97c0:4d0:1::ffff/64` |
|
||||
| `router-lo` | `lo` (VLAN 110) | `192.168.79.254/21` | `2a0e:97c0:4d0:2::ffff/64` |
|
||||
| `router-ut` | `untrusted` (VLAN 120) | `192.168.80.254/24` | `2a0e:97c0:4d0:3::ffff/64` |
|
||||
|
||||
Notes:
|
||||
|
||||
- The IPv6 gateway clients learn from RAs is the link-local `fe80::1` on each VLAN (a
|
||||
`virtual_ipaddress_excluded` VIP; `radvd` advertises from it), not the global VIP above.
|
||||
- `keepalived` also floats a VIP on the `as211024` mesh interface (`10.100.50.4`,
|
||||
`2a0e:97c0:4df:0:1::ffff`) — it has no pseudo-system because nothing client-facing uses it.
|
||||
- The `untrusted` VLAN is an exception to "DNS follows the master": its DHCP/RA options hand out
|
||||
Cloudflare resolvers (`1.1.1.1` / `2606:4700:4700::1111`), not the VIP.
|
||||
|
||||
## Networks
|
||||
|
||||
| Network | VLAN | IPv4 | IPv6 | MTU |
|
||||
|---|---|---|---|---|
|
||||
| `core` | native | `192.168.64.0/24` | — | 1500 |
|
||||
| `hi` | 100 | `192.168.68.0/22` | `2a0e:97c0:4d0:1::/64` | 9000 |
|
||||
| `lo` | 110 | `192.168.72.0/21` | `2a0e:97c0:4d0:2::/64` | 1500 |
|
||||
| `untrusted` | 120 | `192.168.80.0/24` | `2a0e:97c0:4d0:3::/64` | 1500 |
|
||||
| `wan` (stream) | 130 | DHCP public lease + modem mgmt `192.168.0.0/24` | — | 1500 |
|
||||
| `pon-isp` (river) | 10 | PPPoE transport (no L3) | — | 1508 |
|
||||
| `wan-pon-ont` (river) | 140 | ONT mgmt `192.168.100.0/24` | — | 1500 |
|
||||
|
||||
The logical network map lives in [networking.md](../../networking.md).
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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
|
||||
|
||||
## Role
|
||||
|
||||
- AMD desktop running the GUI stack (`my.gui.enable`, Sway/Wayland via home-manager), PipeWire
|
||||
(low-latency `quantum 128`, EasyEffects, jacktrip), Bluetooth, Thunderbolt (`bolt`).
|
||||
- **Netboot client** (`my.netboot.client.enable`): the firmware iPXE-boots off the 2.5G NIC —
|
||||
kea's client-class for `castle` matches `et2.5g`'s MAC (`c8:7f:54:6e:17:0f`) and points at
|
||||
`boot.h.nul.ie` on [`river`](river.md).
|
||||
- **Root on NVMe-oF**: `/nix`, `/persist` and `/home` are `/dev/nvmeof/*` LVs on the
|
||||
`nqn.2016-06.io.spdk:castle` namespace from [`cellar`](cellar.md) (`my.nvme.boot`,
|
||||
`192.168.68.80`, RDMA). The initrd brings up `et100g`/`lan-hi` plus `roceBootModules` to reach
|
||||
it, and the running system keeps `KeepConfiguration=static` on `lan-hi` so networkd never drops
|
||||
the NVMe-oF address. The root itself is a 24 GiB tmpfs (`my.tmproot`).
|
||||
- Local virtualisation: `libvirtd` + `virt-manager` are enabled and the IOMMU is on
|
||||
(`amd_iommu=on`), but no VFIO/GPU-passthrough is configured in the box config today.
|
||||
- Both firewalls are off (`networking.firewall.enable` and `my.firewall.enable` — it's a trusted
|
||||
desktop on `hi`).
|
||||
- Misc: `binfmt` emulation for `aarch64-linux`/`armv7l-linux`, `recursive-nix`, Wireshark,
|
||||
`rdma-core`/`qperf` for the RoCE link. A `drm-amd-display` flicker patch sits commented out in
|
||||
`kernelPatches`.
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: castle -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| castle | hi | `192.168.68.40/22 gw 192.168.71.254` | `2a0e:97c0:4d0:1::3:1/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## Networking
|
||||
|
||||
- `et100g` (100G, MTU 9000) carries `lan-hi` (the `hi` assignment, statically `.40` — also pinned
|
||||
by a kea reservation on its MAC) and `lan-lo`.
|
||||
- `lan-lo` is a secondary leg: 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.
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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`
|
||||
|
||||
## 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`, 64 KiB strip) 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.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` address
|
||||
(`192.168.68.80: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`.
|
||||
- Deploy address `192.168.68.80` (`my.deploy.node.hostname`).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: cellar -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| cellar | hi | `192.168.68.80/22 gw 192.168.71.254` | `2a0e:97c0:4d0:1::4:1/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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
|
||||
|
||||
## 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.
|
||||
- Deploy address `192.168.68.22` (`my.deploy.node.hostname`).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: palace -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| palace-core | core | `192.168.64.20/24` | — | h.nul.ie | |
|
||||
| palace | hi | `192.168.68.22/22 gw 192.168.71.254` | `2a0e:97c0:4d0:1::2:1/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## Networking
|
||||
|
||||
- 100G `et100g` (mlx5, MTU 9000) uplinks to the `dave` switch and carries `lan-hi` (VLAN 100,
|
||||
the `hi` assignment). A udev rule creates **4 SR-IOV VFs** on the PF, distributed in
|
||||
`50-et100g`'s `[SR-IOV]` sections: VF 0 → `cellar` (VLAN hi), VF 1 → `river` (no VLAN — river
|
||||
tags all of its own VLANs, including both WAN VLANs, on this trunk), VF 2 → `sfh` (VLAN hi),
|
||||
VF 3 → `sfh`'s container MACVLAN parent (VLAN hi).
|
||||
- `lan-core` is a bridge with the `core` assignment (`192.168.64.20`, 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.
|
||||
@@ -0,0 +1,81 @@
|
||||
# river
|
||||
|
||||
The primary home router. A VM on `palace`, built from `routing-common` at index 0; it runs PPPoE
|
||||
to Digiweb directly on the ISP's VLAN 10 and normally holds the master side of the VRRP 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`
|
||||
|
||||
## Role
|
||||
|
||||
- Primary of the router pair: `routing-common` index 0 → keepalived starts `MASTER` (priority
|
||||
255), kea serves the lower-half DHCP pools, the zone's SOA/`ns1` point here.
|
||||
- Everything from `routing-common`: keepalived/VRRP ([`keepalived.nix`](../../../nixos/boxes/home/routing-common/keepalived.nix)),
|
||||
PowerDNS recursor + authoritative with a blocklist ([`dns.nix`](../../../nixos/boxes/home/routing-common/dns.nix)),
|
||||
kea DHCP + DDNS ([`kea.nix`](../../../nixos/boxes/home/routing-common/kea.nix)), radvd
|
||||
([`radvd.nix`](../../../nixos/boxes/home/routing-common/radvd.nix)), NAT/firewall, the
|
||||
`as211024` L2 mesh link back to colony, `iperf3`, `nginx`. See
|
||||
[networking.md](../../networking.md) for the logical view and [README.md](README.md#router-vips)
|
||||
for the floating VIPs.
|
||||
- **Netboot server** for `sfh` and `castle` (`my.netboot.server`): iPXE/TFTP at
|
||||
`boot.h.nul.ie` (a CNAME to `river-hi`), served from the `lo` address `192.168.72.1` and
|
||||
restricted to the hi/lo prefixes.
|
||||
- **NVMe-oF client of `cellar`**: the VM's only local disk is an ESP (an installer ISO is also
|
||||
still attached); `/nix` and `/persist` are LVs on the `nqn.2016-06.io.spdk:river` namespace
|
||||
exported by `cellar` (`192.168.68.80`, RDMA).
|
||||
The initrd brings up `lan-hi` with the RoCE modules (`roceBootModules`), and
|
||||
`KeepConfiguration=static` on `lan-hi` stops networkd from dropping the NVMe-oF address on
|
||||
reconfigure.
|
||||
- **SR-IOV VF passthrough**: the 100G `lan` NIC is VF 1 of palace's `et100g` (MAC
|
||||
`52:54:00:8a:8a:f2`, MTU 9000). All router VLANs — hi/lo/untrusted plus both WAN VLANs — are
|
||||
tagged on top of it (`55-lan`).
|
||||
- Also carries a macvtap passthrough of palace's 1G `et1g0`, renamed `wan-old` — the pre-100G WAN
|
||||
path, kept around with no L3 config today.
|
||||
- Deploy address `192.168.68.1` (`my.deploy.node.hostname`).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: river -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| river | as211024 | `10.100.50.2/24` | `2a0e:97c0:4df:0:1::1/64 gw 2a0e:97c0:4df:0:2::1` | — | |
|
||||
| river-core | core | `192.168.64.1/24` | — | h.nul.ie | |
|
||||
| river-hi | hi | `192.168.68.1/22` | `2a0e:97c0:4d0:1::1/64` | h.nul.ie | |
|
||||
| river-lo | lo | `192.168.72.1/21` | `2a0e:97c0:4d0:2::1/64` | h.nul.ie | |
|
||||
| river-ut | untrusted | `192.168.80.1/24` | `2a0e:97c0:4d0:3::1/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## WAN (Digiweb PPPoE)
|
||||
|
||||
- `services.pppd` peer `digiweb` attaches PPPoE directly to `wan-pon-isp`
|
||||
(`plugin pppoe.so wan-pon-isp`) — the **raw ISP VLAN 10** (`vlans.pon-isp`), trunked
|
||||
untranslated from the ONT through `brian` and `dave` (the fabric side is in
|
||||
[switches.md](switches.md)). The netdev is up with no L3; `MTUBytes=1508` (baby jumbo) absorbs
|
||||
PPPoE's 8-byte overhead so the `wan` ppp interface gets a clean `mtu`/`mru 1500`.
|
||||
- The Digiweb static IP (`84.203.124.128`, `elemAt routersPubV4 0`) is requested as the local
|
||||
address in IPCP. Auth is the across-all-customers shared `digiweb@nga.digiweb.ie` / `digiweb`
|
||||
(deliberately not a secret); `persist`, `maxfail 0`, 1s LCP echoes. `usepeerdns` is absent on
|
||||
purpose — Digiweb's resolvers are ignored in favour of the local recursor.
|
||||
- **ONT management**: `wan-pon-ont` (VLAN 140, PVID'd at the `brian` edge) holds
|
||||
`192.168.100.100/24` so the ONT web UI at `192.168.100.1` is reachable — the `.100` mirrors
|
||||
stream's modem-management convention.
|
||||
- **`wan-online.target`** is the shared "public WAN is up" gate declared by `routing-common`;
|
||||
here it is driven by the pppd hooks (`DefaultDependencies=false`, so nothing pulls it in
|
||||
early): `ip-up` installs `default dev wan scope link metric 100` and starts the target,
|
||||
`ip-down` stops it and deletes the route. Consumers (`ipsec`, `ipv6-clear-default-route`)
|
||||
attach with `wantedBy` + `partOf`, so they re-load on every WAN flap.
|
||||
- The `wan-ifb` ingress-shaping pieces from `routing-common` are inert on this box: the CAKE
|
||||
config itself is stream's, and `networkd-dispatcher` (which installs the `tc` mirror) is
|
||||
`mkForce false` here pending scheduling testing.
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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`
|
||||
|
||||
## 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
|
||||
(MAC `52:54:00:a5:7e:93`, on palace's `lan-lo` bridge, `bootindex=1`) 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` at `192.168.68.80`
|
||||
([`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
|
||||
|
||||
<!-- assignments: sfh -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| sfh | hi | `192.168.68.81/22 gw 192.168.71.254` | `2a0e:97c0:4d0:1::4:2/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## Networking
|
||||
|
||||
Four NICs, all MTU 9000 where jumbo-capable:
|
||||
|
||||
- `lan-hi` — SR-IOV VF 2, the box's own `hi` assignment (`192.168.68.81`).
|
||||
- `lan-hi-ctrs` — SR-IOV VF 3, no L3: the MACVLAN parent for the containers' `hi` legs
|
||||
(`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` leg.
|
||||
|
||||
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 | Page |
|
||||
|---|---|---|
|
||||
| `hass` | Home Assistant + Frigate + MQTT | [sfh/containers/hass.md](sfh/containers/hass.md) |
|
||||
| `unifi` | UniFi controller | [sfh/containers/unifi.md](sfh/containers/unifi.md) |
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,61 @@
|
||||
# hass
|
||||
|
||||
Home automation container: Home Assistant plus its supporting services (MQTT, camera restreaming,
|
||||
Frigate NVR), running on [`sfh`](../../sfh.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`
|
||||
|
||||
## Role
|
||||
|
||||
- **Home Assistant** (`services.home-assistant`) — declarative config (`configWritable = false`),
|
||||
components `esphome`, `zha`, `denonavr`, `webostv`, `androidtv_remote`, `heos`, `mqtt`, `wled`,
|
||||
`met`, `google_translate`; custom components `alarmo`, `frigate`, `west_wood_club`; a custom
|
||||
Irish Rail sensor (Glenageary ↔ Dublin Connolly). A `hass-cli` wrapper is on the box, wired to
|
||||
the local server with a token from `my.secrets`.
|
||||
- **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` leg) 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 1-day retention; 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
|
||||
|
||||
<!-- assignments: hass -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| hass-ctr (frigate) | hi | `192.168.68.103/22 gw 192.168.71.254` | `2a0e:97c0:4d0:1::5:3/64` | h.nul.ie | |
|
||||
| hass-ctr-lo | lo | `192.168.72.103/21` | `2a0e:97c0:4d0:2::5:3/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## 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).
|
||||
- `/dev/bus/usb/001/002` (raw USB device node).
|
||||
|
||||
## Networking
|
||||
|
||||
MACVLAN legs 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` leg 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`.
|
||||
@@ -0,0 +1,47 @@
|
||||
# unifi
|
||||
|
||||
The UniFi network controller, running as a container on [`sfh`](../../sfh.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`
|
||||
|
||||
## 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` leg (`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`.
|
||||
|
||||
## 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
|
||||
|
||||
<!-- assignments: unifi -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| unifi-ctr-core | core | `192.168.64.21/24` | — | h.nul.ie | |
|
||||
| unifi-ctr | hi | `192.168.68.100/22 gw 192.168.71.254` | `2a0e:97c0:4d0:1::5:1/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## Networking
|
||||
|
||||
Two MACVLAN legs from `sfh`'s container NICs: `host0` on `lan-hi-ctrs` (the `hi` assignment —
|
||||
`unifi-ctr`, `192.168.68.100`, default gateway via the VIP) and `lan-core` on `lan-core-ctrs`
|
||||
(the `core` assignment — `unifi-ctr-core`, `192.168.64.21/24`, no gateway). The `core` leg is
|
||||
how the controller talks to the switches: `brian` lives at `192.168.64.13` on `core`.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,82 @@
|
||||
# stream
|
||||
|
||||
The secondary home router. A physical Intel box, built from `routing-common` at index 1; its WAN
|
||||
is a DHCP lease from the Virgin Media cable modem, and it 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
|
||||
|
||||
## Role
|
||||
|
||||
- Backup of the router pair: `routing-common` index 1 → keepalived starts `BACKUP` (priority
|
||||
254), kea serves the upper-half DHCP pools, the zone's `ns2` points here. Takes over all VIPs
|
||||
when [`river`](river.md) is down — see [README.md](README.md#router-vips).
|
||||
- Runs the same `routing-common` services as river: keepalived/VRRP, PowerDNS recursor +
|
||||
authoritative, kea DHCP + DDNS, radvd, NAT/firewall, the `as211024` L2 mesh, `iperf3`, `nginx`.
|
||||
- Intel box (`kvm-intel`, `intel_iommu=on`, microcode updates).
|
||||
- `octoprint` and `mjpg-streamer` (3D-printer services) are defined but **disabled**
|
||||
(`enable = false`).
|
||||
- `my.deploy.node.hostname` is currently commented out (it was `192.168.68.2`).
|
||||
|
||||
## Network assignments
|
||||
|
||||
<!-- assignments: stream -->
|
||||
<!-- assignments-start -->
|
||||
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| stream | as211024 | `10.100.50.3/24` | `2a0e:97c0:4df:0:1::2/64 gw 2a0e:97c0:4df:0:2::1` | — | |
|
||||
| stream-core | core | `192.168.64.2/24` | — | h.nul.ie | |
|
||||
| stream-hi | hi | `192.168.68.2/22` | `2a0e:97c0:4d0:1::2/64` | h.nul.ie | |
|
||||
| stream-lo | lo | `192.168.72.2/21` | `2a0e:97c0:4d0:2::2/64` | h.nul.ie | |
|
||||
| stream-ut | untrusted | `192.168.80.2/24` | `2a0e:97c0:4d0:3::2/64` | h.nul.ie | |
|
||||
<!-- assignments-end -->
|
||||
|
||||
## WAN (Virgin Media DHCP)
|
||||
|
||||
- `wan` is a renamed igc NIC (`00:f0:cb:ee:ca:dd`) towards the cable modem. The modem segment is
|
||||
switch **VLAN 130** — tagging is handled on the switch (`jim`), so the box interface itself is
|
||||
untagged. The fabric side is in [switches.md](switches.md).
|
||||
- `DHCP=ipv4` pulls the public lease; `dhcpV4Config.UseDNS=false` and the interface DNS points at
|
||||
the local recursor. `IPv6AcceptRA=false` — this is an IPv4-only WAN (public IPv6 arrives over
|
||||
the tunnel, not this link).
|
||||
- A **static modem-management address** (`192.168.0.100/24`, host `.100` of `prefixes.modem.v4`)
|
||||
sits on `wan` alongside the DHCP lease so the modem's web UI stays reachable; it has no
|
||||
gateway.
|
||||
- **`wan-online.target`** wiring: `wan-wait-online.service` (a oneshot) polls until the DHCP
|
||||
default route exists, and the target `requires`/`after`s it (`wantedBy multi-user.target`).
|
||||
The route — not networkd's wait-online — is the gate because the permanent static modem address
|
||||
would otherwise report "online" before the public lease arrives, letting `ipsec` start
|
||||
unoriented (`left=` is the public IP) and never connect.
|
||||
- **CAKE QoS**: egress is shaped at the `wan` root qdisc (`Bandwidth=48M`); ingress is redirected
|
||||
by `tc` (`mirred`, installed by the `networkd-dispatcher` rule in `routing-common`) into the
|
||||
`wan-ifb` IFB at `Bandwidth=490M` with the DOCSIS overhead preset.
|
||||
|
||||
### Modem specifics (de-shared from `routing-common`)
|
||||
|
||||
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 = [ 192.168.0.255 ]` — skip the modem subnet when
|
||||
auto-selecting the router's own `wan` A record for the zone's LUA record.
|
||||
- `my.homeRouter.firewall.untrustedRejectV4 = [ 192.168.0.0/24 ]` — reject untrusted clients from
|
||||
reaching the modem subnet (needed only because it shares `wan`; WAN egress is otherwise
|
||||
accepted).
|
||||
|
||||
## Switching (STP)
|
||||
|
||||
`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.)
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,203 @@
|
||||
# Home switches
|
||||
|
||||
Reference for the two MikroTik switches on the home network — **jim** and **dave** — plus the
|
||||
Ubiquiti switch **brian**, and how the home boxes and the Digiweb WAN hang off them. These switches
|
||||
are **not** managed by this flake; they are configured by hand (RouterOS on jim/dave, UniFi on
|
||||
brian). It covers the physical topology, the VLAN map, and how the Digiweb WAN reaches river.
|
||||
|
||||
In short: the Digiweb ISP VLAN (10) is trunked straight through to river (which runs PPPoE on it),
|
||||
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). The router side lives in
|
||||
[river.md](river.md); the logical network map in [networking.md](../../networking.md).
|
||||
|
||||
## The switches
|
||||
|
||||
| | jim | dave | brian |
|
||||
|---|---|---|---|
|
||||
| Identity | `jim-sw` | `dave-sw` | (UniFi) |
|
||||
| Model | CRS326-24G-2S+ | CRS504-4XQ | Ubiquiti 10GBASE-T |
|
||||
| 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 | 10GBASE-T |
|
||||
| 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
|
||||
MikroTiks is SSH as `admin` / `admin` by short hostname (see [Accessing the switches](#accessing-the-switches)).
|
||||
Only jim and dave can do hardware VLAN translation (`/interface ethernet switch rule` on the Marvell
|
||||
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.
|
||||
|
||||
```
|
||||
Virgin Media cable modem
|
||||
│
|
||||
│ (VLAN 130)
|
||||
│ wan1/wan2/wan-in
|
||||
┌──────────────────────────────────────────────────┴──┐
|
||||
│ jim CRS326-24G-2S+ (Marvell 98DX3236) │
|
||||
│ 1G edge ports: castle, fort, pronter, laptop-dock, │
|
||||
│ palace-kvm, ups, ether15-20, wan1/wan2/wan-in │
|
||||
│ wan-pon-in (= sfp-sfpplus2) ← spare SFP+ │
|
||||
└───────┬───────────────────────────────────┬─────────┘
|
||||
dave-uplink ┤ (= sfp-sfpplus1) │ stream, palace (1G secondaries)
|
||||
10G trunk│ └── stream (box, dual-homed)
|
||||
┌─────────┴───────────────────────────────────────────────┐
|
||||
│ dave CRS504-4XQ (Marvell 98DX4310) │
|
||||
│ jim-downlink = qsfp28-3-1 │
|
||||
└──┬───────────┬──────────┬───────────────────┬────────────┘
|
||||
palace(100G) │ castle │ stream │ brian-downlink
|
||||
= qsfp28-1-1 │ │ │ (802.3ad LAG: brian1+brian2)
|
||||
┌────────────────┴─┐ … │
|
||||
│ palace host │ Digiweb (PPPoE) via ONT
|
||||
│ └ river (VM) │ │
|
||||
└──────────────────┘ ┌─────┴─────┐
|
||||
↑ river's WAN + LAN ride the 100G link │ ONT │ untagged mgmt 192.168.100.1
|
||||
└─────┬─────┘ + tagged VLAN 10 (ISP)
|
||||
│
|
||||
┌────────┴────────┐
|
||||
│ brian (UniFi) │ PVID 140 on the ONT port,
|
||||
│ 10GBASE-T │ tagged VLAN 10 allowed through
|
||||
└────────┬────────┘
|
||||
└── brian-downlink LAG up to dave
|
||||
```
|
||||
|
||||
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).
|
||||
- **brian** is a Ubiquiti 10GBASE-T switch, downlinked from dave over an **802.3ad LAG**
|
||||
(`brian-downlink` = `brian1` + `brian2`, layer-2 hash). It hosts the ONT.
|
||||
|
||||
## VLANs
|
||||
|
||||
| VLAN | Name | Purpose |
|
||||
|---|---|---|
|
||||
| — (native) | core | Switch management, `192.168.64.0/24` (jim `.10`, dave `.11`, brian `.13`) |
|
||||
| 100 | hi | High-performance / jumbo network (MTU 9000) |
|
||||
| 110 | lo | Standard LAN |
|
||||
| 120 | untrusted | Guest / untrusted network |
|
||||
| 130 | wan | **stream's WAN** — Virgin Media cable modem (untagged on jim's `wan1`/`wan2`/`wan-in`) |
|
||||
| 140 | wan-pon-ont | ONT management, `192.168.100.0/24` (PVID'd at the ONT edge) |
|
||||
| 10 | pon-isp | Digiweb ISP transport — **trunked straight through** to river, PPPoE runs on it |
|
||||
| 141 | wan-pon-isp | **Reserved** — the translated ISP VLAN for the future multi-ONT design |
|
||||
|
||||
Switch L3 presence (`/interface vlan` on `main`) exists **only** for VLANs the switch is managed
|
||||
from — `hi` (100) and `lo` (110), plus native core. WAN and guest VLANs deliberately have no switch
|
||||
L3 interface.
|
||||
|
||||
## The Digiweb WAN path (trunked VLAN 10 + PVID 140)
|
||||
|
||||
The ONT presents two things on one wire:
|
||||
- **untagged** management traffic (`192.168.100.x`), and
|
||||
- **tagged VLAN 10** carrying the Digiweb ISP session (the BRAS requires VLAN 10).
|
||||
|
||||
With a **single ONT** there's no reason to translate anything — VLAN 10 is unique on the fabric, so
|
||||
we just carry it end to end and let river run PPPoE directly on it:
|
||||
|
||||
1. **Untagged mgmt → VLAN 140, at the ONT's edge switch (brian).** brian sets the ONT port's PVID to
|
||||
140 so the untagged management traffic becomes VLAN 140, and allows tagged VLAN 10 through the
|
||||
same port. river takes `192.168.100.100/24` on VLAN 140 (matching stream's modem-mgmt `.100`) to
|
||||
reach the ONT web UI at `192.168.100.1`. Doing the PVID at the ONT-facing edge keeps it clean —
|
||||
the untagged frames never share a domain with anything else.
|
||||
|
||||
2. **VLAN 10 (ISP) trunked straight through, untranslated.** brian → dave → palace carry tagged
|
||||
VLAN 10 by ordinary bridge-VLAN membership. No `/interface ethernet switch rule`, no pinning, no
|
||||
asymmetric-learning issues — it's just a normal tagged VLAN. river attaches PPPoE to VLAN 10
|
||||
directly (`wan-pon-isp` netdev = VLAN `pon-isp` = 10; baby-jumbo MTU 1508 so PPP nets a clean
|
||||
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`](../../../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 ┘
|
||||
```
|
||||
|
||||
### Why not translation (for one ONT)?
|
||||
|
||||
Translation would swap VLAN 10 → 141 with two pinned hardware ACL rules to keep VLAN 10 off the rest
|
||||
of the fabric. That buys nothing with a single ONT — VLAN 10 is already unique, so trunking it is
|
||||
simpler and rule-free. Translation only earns its keep when **two** ONTs both deliver VLAN 10 and
|
||||
would collide (below).
|
||||
|
||||
## Switch configuration
|
||||
|
||||
How each switch is set up for the Digiweb WAN path. **Confirm any change on the box before applying**
|
||||
(see [Accessing the switches](#accessing-the-switches)).
|
||||
|
||||
**brian (UniFi)** — hosts the ONT:
|
||||
- The ONT port has **native/untagged network = VLAN 140** (PVID) and is a **tagged member of VLAN 10**,
|
||||
so the ONT's untagged management lands on 140 and its tagged ISP frames pass through.
|
||||
- The `brian-downlink` LAG up to dave trunks **tagged 140 + tagged 10** (alongside the LAN VLANs).
|
||||
|
||||
**dave (RouterOS)** — trunks both WAN-pon VLANs to `brian-downlink` and `palace`. The ISP VLAN 10 row:
|
||||
```
|
||||
/interface bridge vlan add bridge=main vlan-ids=10 tagged=brian-downlink,palace
|
||||
```
|
||||
VLAN 140 also spans `brian-downlink,palace` (it carries a few other members too). No switch rules —
|
||||
this is plain tagged bridging.
|
||||
|
||||
**jim (RouterOS)** — carries **none** of the Digiweb WAN path: no translation rules, and no VLAN
|
||||
10/140/141 rows. `wan-pon-in` (`sfp-sfpplus2`) sits at `pvid=1` as a spare port. jim only handles
|
||||
stream's VLAN-130 WAN and the LAN VLANs.
|
||||
|
||||
## Future: multiple ONTs (per-port VLAN translation)
|
||||
|
||||
If a second ONT arrives (e.g. a Digiweb line for stream, or a second river), trunking breaks: both
|
||||
ONTs deliver **tagged VLAN 10**, and plain bridge-VLAN filtering can't tell them apart. That's when
|
||||
translation earns its place — a switch rule matches on the **ingress port**, so each ONT's VLAN 10
|
||||
becomes a *distinct* fabric VLAN:
|
||||
|
||||
- ONT-A port: VLAN 10 → **141** (→ river)
|
||||
- ONT-B port: VLAN 10 → **142** (→ stream / second river)
|
||||
- mgmt: PVID each ONT port onto its own VLAN (140, 143, …) so both ONTs' `192.168.100.1` stay in
|
||||
separate L2/L3 domains.
|
||||
|
||||
The forward direction isolates naturally (each ONT maps to a different fabric VLAN). The **return**
|
||||
direction is where port targeting is mandatory: both translate *back* to VLAN 10, so bridge VLAN 10
|
||||
now has two members and a plain FDB-miss flood would leak one ONT's upstream to the other. Each
|
||||
return must be pinned to its port with `new-dst-ports`:
|
||||
```
|
||||
# ONT-A: 141 in on palace → 10, forced out ONT-A's port
|
||||
# ONT-B: 142 in on stream → 10, forced out ONT-B's port
|
||||
```
|
||||
Each ONT port must also be a tagged member of bridge VLAN 10 for correct egress tagging (the missing
|
||||
piece that otherwise shows up as pppd "Timeout waiting for PADO"). The pins bypass the FDB, so the
|
||||
two ISP sessions never mix.
|
||||
|
||||
**Why a new switch:** jim (the only box with spare SFP+ *and* the translation feature) has just
|
||||
**one** free SFP+ port, so it can't host two ONTs. The plan is a dedicated
|
||||
**CRS305-1G-4S+** (4×SFP+, same Marvell rule support) to land multiple ONTs and do the per-port
|
||||
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`](../../../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`. Non-interactive pattern (password auth, avoids the ssh-agent hang):
|
||||
|
||||
```
|
||||
sshpass -p admin ssh -o IdentityAgent=none -o PubkeyAuthentication=no \
|
||||
-o PreferredAuthentications=password -o StrictHostKeyChecking=accept-new \
|
||||
-o UserKnownHostsFile=/tmp/sw_known_hosts admin@jim
|
||||
```
|
||||
|
||||
**Always confirm config changes on the switch** (print the affected menu, apply, re-verify). brian
|
||||
is UniFi — configured through its controller, not RouterOS CLI.
|
||||
|
||||
## Management IPs
|
||||
|
||||
| | core (`192.168.64.0/24`) | hi (`192.168.68.0/22`) | lo (`192.168.72.0/21`) |
|
||||
|---|---|---|---|
|
||||
| jim | `.10` (on `main`) | `.10` | `.10` |
|
||||
| dave | `.11` (on `management`, the 1G Atheros port) | `.11` | `.11` |
|
||||
| brian | `.13` (core) | — | — |
|
||||
Reference in New Issue
Block a user