18 Commits

Author SHA1 Message Date
jackos1998 135d52d3de nix: Skip readLinkAt unit test on XFS builder
Update docs / update (push) Successful in 1m6s
CI / Check, build and cache nixfiles (push) Successful in 1h7m38s
CI builds Determinate Nix from source, running its unit-test suite. The
`nix-util` `readLinkAt.works` test creates symlinks with PATH_MAX-length
targets, but our CI runner's build filesystem is XFS, which hard-caps
symlink targets at 1024 bytes (XFS_SYMLINK_MAXLEN). Creation fails with
ENAMETOOLONG, so the test — and the whole determinate-nix build — fails
on the runner while passing on non-XFS filesystems.

Filter out just that test via gtest's GTEST_FILTER on the
`nix-util-tests-run` check input, leaving the rest of the unit and
functional tests gating the build (they still matter, since we build
against several nixpkgs channels).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 00:26:37 +01:00
jackos1998 f8a89b9c87 docs: Add guided nixpkgs upgrade procedure
CI / Check, build and cache nixfiles (push) Has been cancelled
Update docs / update (push) Successful in 1m10s
Capture the periodic upgrade of the four nixpkgs channels and
home-manager as a repo doc: check for a NixOS stable bump first, rebase
the devplayer0 fork against upstream (re-verifying the patch stack
against freshly fetched refs), run the update commands, sweep
version-gated TODOs, and review the remaining flake inputs.

Keep the canonical, agent-agnostic procedure in docs/ and point both
AGENTS.md and a thin Claude Code skill at it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 23:31:26 +01:00
jackos1998 5e3b196ee0 nixos/git: Fix Actions runner cache timeout
Job containers using actions/cache hung and timed out reaching the
runner's built-in artifact cache server. With cache.host unset,
act_runner announced the box's autodetected outbound address, which
containers on podman0 can't route back to; even to the right address
the host input chain (policy drop) dropped the connection, as only the
forward chain was opened for the podman subnet.

Pin cache.host to the podman bridge gateway and cache.port to a fixed
value, and open that one port on podman0 in the input chain. Declare
the podman subnet once in the box file and derive the gateway, the
default_subnet and both firewall rules from it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 22:56:30 +01:00
jackos1998 b8f31e23f8 nixos/home: Prefer dave for stream RSTP
CI / Check, build and cache nixfiles (push) Failing after 24m39s
Update docs / update (push) Successful in 1m9s
Set explicit port costs so `stream` prefers `dave` over `jim`.

Trigger `mstpd` when the bridge is configured, before it is routable.
2026-08-03 22:31:43 +01:00
jackos1998 7fe3c8186c nix: Adopt Determinate Nix as the common Nix
Use Determinate Nix as `nix.package` for systems, homes and the
devshell, for its parallel evaluation and lazy trees. We only take the
package, not `determinate-nixd`: the daemon and `nix.conf` model are
unchanged and the Determinate NixOS module is not imported.

- Add the `determinate-nix` (`nix-src`) input, following our
  `nixpkgs-unstable`. FlakeHub's cache needs auth, so we build it
  ourselves and let it flow through Harmonia like everything else.
- `determinateOverlay` exposes it as `pkgs'.mine.determinate-nix`;
  `lib.my.c.nix.determinateSettings` (`lazy-trees`, `eval-cores = 0`)
  is merged into `nix.settings` and the devshell `nix.conf`.
- Switch CI to `DeterminateSystems/determinate-nix-action` so the
  runner itself evaluates with Determinate.
- Advertise the Harmonia cache via the flake's `nixConfig`, trusted
  without a prompt via `accept-flake-config` in the devshell, `.envrc`
  and CI only (boxes already trust it through `nix.settings`).
- Re-attach `pkgs`/`lib` to container and installer
  `nixosConfigurations` so Determinate's flake schemas can evaluate
  them (`nix flake check` otherwise fails with `attribute 'pkgs'
  missing`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 22:31:43 +01:00
jackos1998 1ca978baf4 home: Raise stateVersion and drop dead nix.package guards
Every managed home already had its stateVersion force-set per
home-manager branch (22.11 for stable/mine-stable, 23.05 otherwise);
pin them all to 23.05.

With the floor at 23.05 the `versionAtLeast config.home.stateVersion`
guards on `nix.package` are always taken, so drop them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 22:31:43 +01:00
jackos1998 b9bcb1eaeb nixos/home: Anchor static hi clients' DNS on VIPs
Update docs / update (push) Successful in 1m10s
CI / Check, build and cache nixfiles (push) Has been cancelled
Statically-addressed home servers on hi run no DHCP, so they learned a
resolver only from the v6 RA RDNSS and lost DNS whenever v6 (and thus
the RA) was absent. Factor the fix castle/palace applied inline into a
shared lib.my.c.home.vlanDns helper that points resolved at the VLAN's
VRRP VIPs (always-present static v4, plus v6 when up) and sets the
advertised search domains, then apply it to every statically-addressed
hi client: castle, palace, cellar, sfh and the sfh hass/unifi
containers. Document it under the router client DNS section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 22:27:47 +01:00
jackos1998 f9f61e19ad docs/home: Document switches/AP must not route
CI / Check, build and cache nixfiles (push) Successful in 47m37s
Update docs / update (push) Successful in 1m7s
jim, dave and the vibe AP are pure L2, but RouterOS ships ip-forward and
IPv6 forward on, and with IPv6 forwarding enabled it also advertises
itself as a default router. After the 7.18 -> 7.23 upgrade clients began
picking up the switches as IPv6 default routers alongside river.

Replace the earlier advertise-dns framing (which only strips RA options,
not the router lifetime) with the actual requirement: ip-forward=no,
IPv6 forward=no, accept-router-advertisements=no, ra-lifetime=0, and a
re-check after every RouterOS upgrade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 14:15:40 +01:00
jackos1998 14af217880 nixos/castle: Pin static DNS on lan-hi
CI / Check, build and cache nixfiles (push) Successful in 47m5s
Update docs / update (push) Successful in 1m16s
castle took its resolver solely from the IPv6 RA (radvd RDNSS) on
lan-hi, so DNS broke whenever RA was absent. Since castle's own v6 is
RA/SLAAC-derived it has no usable v6 address in that case, so anchor
DNS on the always-present static v4 via the VRRP VIP, with the v6 VIP
as a bonus when v6 is up. Search domains reuse the centralised
lib.my.c.home.searchDomains list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 22:04:34 +01:00
jackos1998 26e6870337 lib/home: Centralise advertised search domains
radvd's DNSSL and kea's domain-search hardcoded the same list of
search domains in two places. Hoist it to lib.my.c.home.searchDomains
so there is a single source of truth (and so other consumers, such as
statically-configured boxes, can reuse it). No change to what is
advertised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 22:04:34 +01:00
jackos1998 000c03d3c1 ci/docs: Fall back to discovered servers for AXFR
CI / Check, build and cache nixfiles (push) Successful in 47m47s
Update docs / update (push) Successful in 1m9s
The DNS reference generator transfers each zone from the nameservers
public NS discovery returns. The home IPv6 reverse zone
(0.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa) is delegated only to Hurricane
Electric, which refuses AXFR, so generation aborted before committing
any zone. Our own authoritative servers (ns1/ns2.h.nul.ie) serve that
zone and permit transfers but aren't in its public delegation, as their
addresses are dynamic.

When a zone's delegated servers all refuse, retry against the union of
nameservers discovered for every other zone. That pool includes
ns1/ns2.h.nul.ie via the h.nul.ie NS records, resolved to their current
addresses at query time, so the reverse zone transfers from the same
reachable servers h.nul.ie already uses. Zones whose own servers work
are unaffected; the fallback only runs after their transfers fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 21:49:40 +01:00
jackos1998 4f6ea35ee4 nixos/toot: Fix broke deploy due to mastodon secrets 2026-08-02 21:31:07 +01:00
jackos1998 de681f33da docs: Note container deploy targets
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 21:20:53 +01:00
jackos1998 ab2308e765 lib: Disable IPv6 privacy addresses on assignments
IPv6 temporary (privacy) addresses rotate an interface's stable source
address out from under long-lived connected sockets. nginx's resolver
on middleman binds a UDP socket to the preferred temporary address at
worker start; once that address expires and is removed, the socket can
no longer send, so every upstream lookup fails with "could not be
resolved (timed out)" until nginx is restarted (which then binds the
next temporary address, so it recurs).

These are servers with no need for privacy addresses, so disable them
in networkdAssignment for every RA-accepting interface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 21:20:53 +01:00
jackos1998 539a76a94f docs: Correct sfh acronym 2026-08-02 11:42:09 +01:00
jackos1998 88d0d19239 docs/dns: Add live record reference
CI / Check, build and cache nixfiles (push) Successful in 46m57s
Update docs / update (push) Failing after 1m6s
Generate forward and reverse record tables from authoritative AXFRs
while preserving handwritten Markdown outside per-zone markers. Run the
generator in CI and link the reference from the relevant docs.
2026-08-02 01:07:27 +01:00
jackos1998 8f9ca5e1c4 nixos/dns: Allow internal zone transfers
Permit AXFR from shared internal networks and cross-site egress
addresses while retaining loopback and the existing secondary.
2026-08-02 01:07:03 +01:00
jackos1998 08605ab422 docs/boxes: Prioritize hardware details
Place physical hardware inventories and VPS resource allocations near
the top of each box page, while leaving broader platform details in
their topical sections.
2026-08-02 00:23:30 +01:00
46 changed files with 1334 additions and 153 deletions
+25
View File
@@ -0,0 +1,25 @@
---
name: upgrade-nixpkgs
description: >-
Upgrade all four nixpkgs channels (unstable, stable, mine, mine-stable) and home-manager for this
flake: check for a NixOS stable bump, rebase the devplayer0 nixpkgs fork against upstream, run the
update commands, sweep version-gated TODOs, and review flake inputs. Use when the user wants to
update/bump nixpkgs, refresh the pins, or do the periodic nixpkgs/home-manager upgrade.
---
# Upgrade nixpkgs
The canonical, agent-agnostic procedure lives in the repo at
[`docs/nixpkgs-upgrade.md`](../../../docs/nixpkgs-upgrade.md). Read it and follow the phases in
order.
Key reminders (see the doc for the full steps):
- It is **guided, not automated** — do the mechanical/investigative work but stop at the ⏸ points:
pushing the fork, resolving rebase conflicts, editing the `flake.nix` stable pins, and deleting
version guards. Report and let the user decide.
- **Check the current NixOS stable first** (Phase 1) — the fork's `devplayer0-stable` rebase target
and the `flake.nix` stable pins must agree on one release.
- **Re-verify the patch stack against freshly fetched upstream**, not stale refs — enumerate it with
`git log`, don't assume a remembered list (stale `upstream/*` refs make already-upstreamed commits
masquerade as fork-only patches).
+3 -1
View File
@@ -1,2 +1,4 @@
watch_file devshell/{default,commands,install,vm-tasks}.nix
use flake
# --accept-flake-config trusts the flake's nixConfig (our Harmonia cache) non-interactively, so
# direnv doesn't stall on the trust prompt.
use flake . --accept-flake-config
+10 -5
View File
@@ -10,17 +10,22 @@ jobs:
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@v6
- uses: cachix/install-nix-action@v31
- uses: DeterminateSystems/determinate-nix-action@v3
with:
# Gitea will supply a token in GITHUB_TOKEN, which this action will
# try to pass to Nix when downloading from GitHub
github_access_token: ${{ secrets.GH_PULL_TOKEN }}
extra_nix_config: |
# Gitea will supply a token in GITHUB_TOKEN, which this action passes to
# Nix (as access-tokens) when downloading from GitHub
github-token: ${{ secrets.GH_PULL_TOKEN }}
extra-conf: |
# Make sure we're using sandbox
sandbox-fallback = false
# Big C++ projects fill up memory...
cores = 6
# Determinate performance features
lazy-trees = true
eval-cores = 0
accept-flake-config = true
extra-substituters = https://nix-cache.nul.ie
extra-trusted-public-keys = nix-cache.nul.ie-1:BzH5yMfF4HbzY1C977XzOxoPhEc9Zbu39ftPkUbH+m4=
+12 -2
View File
@@ -6,7 +6,7 @@ on:
jobs:
update:
if: "!contains(github.event.head_commit.message, 'docs: update generated tables')"
if: "!contains(github.event.head_commit.message, 'docs: Update generated references')"
runs-on: ubuntu-26.04
permissions:
contents: write
@@ -26,6 +26,16 @@ jobs:
- name: Update option reference
run: nix run .#update-docs-options
- name: Update DNS reference
run: >
nix run .#update-docs-dns --
ams1.int.nul.ie
100.10.in-addr.arpa
2.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa
h.nul.ie
168.192.in-addr.arpa
0.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa
- name: Commit and push if changed
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
@@ -36,6 +46,6 @@ jobs:
git remote set-url origin "${REPO_URL/https:\/\//https:\/\/oauth2:${GITEA_TOKEN}@}"
git add docs/
if ! git diff --cached --quiet; then
git commit -m "docs: update generated tables"
git commit -m "docs: Update generated references"
git push
fi
+20 -11
View File
@@ -46,7 +46,10 @@ Common ones:
Pass the flake-qualified node, e.g. `deploy .#git`. The deploy node name is **always** the system
name (`deploy-rs.nix` keys nodes directly off `nixos.systems` / `home-manager.homes`); a system is
only a deploy target when `config.my.deploy.enable` is true (defaults true; auto-disabled for dev
VMs and containers). Pass `--boot` to stage a config as the boot default **without** live-switching
VMs and containers). A container is **not** its own deploy node — it is generated as a
`container-<name>` profile on its **host** node. So `deploy .#<host>` deploys the host's `system`
profile and every one of its containers, whereas `deploy .#<host>.container-<name>` targets a
single container (e.g. `deploy .#shill.container-middleman`). Pass `--boot` to stage a config as the boot default **without** live-switching
(`deploy --boot .#<host>`) — the box keeps running its current generation until it reboots. Use this
when a live `switch` would break connectivity mid-change (e.g. a router's WAN VLAN rework), then
reboot to cut over.
@@ -58,7 +61,9 @@ Common ones:
`SSH_AUTH_SOCK= ssh-machine …` (or add `-o IdentityAgent=none` to a raw `ssh`).
- `ragenix` — edit age secrets using `.keys/dev.key` as identity (see Secrets).
- `repl``nix repl .#`.
- `update-nixpkgs` / `update-home-manager` — bump pinned inputs.
- `update-nixpkgs` / `update-home-manager` — bump pinned inputs. For the full periodic upgrade
(rebasing the `devplayer0` nixpkgs fork, stable-release bumps, version-gate sweep, input review)
follow the guided procedure in [`docs/nixpkgs-upgrade.md`](docs/nixpkgs-upgrade.md).
Use the narrowest relevant evaluation while iterating: `check-system <host>` for a box config,
`nix eval .#nixfiles.config.nixos.allAssignments --json` for assignment generation, or
@@ -66,8 +71,8 @@ Use the narrowest relevant evaluation while iterating: `check-system <host>` for
`nix flake check --no-build` for final broad validation or reproducing CI.
CI builds each attr of `.#ci.x86_64-linux` (systems, homes, packages, shell) and pushes to the
Harmonia binary cache; see `.gitea/workflows/ci.yaml` and `ci/push-to-cache.sh`. A separate
workflow (`.gitea/workflows/update-docs.yaml`) regenerates the network-assignment tables and NixOS
option reference via `nix run .#update-docs-{assignments,options}`.
workflow (`.gitea/workflows/update-docs.yaml`) regenerates the network assignments, NixOS option
reference and live DNS reference under `docs/`.
For DNS lookups use **`drill`** (ldns) — `dig` isn't installed in this environment (it fails with
exit 127, which is easy to miss if stderr is redirected). E.g. `drill -Q @<resolver> <name> A`.
@@ -212,10 +217,12 @@ physical box share a name, the site keeps the `README.md` and the box page stays
example `sites/colony/README.md` and `sites/colony/colony.md`).
**Box page layout** (match the existing pages): H1 + a one-line intro; a short bullet list of
`Source` / `Host` / `nixpkgs`; `## Role`; `## Network assignments` that **links** to
`Source` / `Host` / `nixpkgs`; an optional hardware inventory or VPS resource-allocation section;
`## Role`; `## Network assignments` that **links** to
[`networking.md#box-assignments`](docs/networking.md#box-assignments) (never inline the table); one
`##` section per topic; `## Notable config files` last. A box without static assignments still gets
the section with a short explanation instead of a generated-table link.
`##` section per topic; `## Notable config files` last. Keep non-hardware platform details in their
topical sections rather than moving them with the inventory. A box without static assignments still
gets the section with a short explanation instead of a generated-table link.
**Structure and layout:**
- Use **tables** for lists of structured items (BGP peers, forwarded ports, vhosts, containers,
@@ -247,10 +254,12 @@ the section with a short explanation instead of a generated-table link.
Addresses outside that data model (such as external peers or service endpoints) stay with the
topic that owns them.
**Generated content:** the network-assignment tables in `networking.md` and the option reference
(`docs/reference/nixos-options.md`) are CI-generated (`nix run .#update-docs-{assignments,options}`)
— don't hand-edit between the `<!-- ... -->` markers; write the prose and let the updater refresh
the tables.
**Generated content:** the network-assignment tables in `networking.md`, the option reference
(`docs/reference/nixos-options.md`) and the live DNS tables (`docs/reference/dns.md`) are
CI-generated by the corresponding `update-docs-*` packages; the workflow supplies the DNS zones.
Don't hand-edit content between `<!-- ... -->` markers; write the surrounding prose or source
configuration and let the updater refresh the tables. The option-reference file is generated in
full.
**Keep docs current:** when you add, remove or repurpose a box or service, update its box page, the
relevant site-index `README.md`, and any affected prose in `networking.md` (the assignment/option
+362
View File
@@ -0,0 +1,362 @@
#!/usr/bin/env python3
"""Render docs/reference/dns.md from live authoritative DNS zone transfers.
The authoritative servers are queried directly over AXFR. Records owned by Kea are
identified by DHCID records and omitted together with their forward and reverse data.
"""
import argparse
import ipaddress
import re
import socket
import sys
from dataclasses import dataclass
from pathlib import Path
import dns.exception
import dns.name
import dns.query
import dns.resolver
import dns.rdatatype
OUT = Path("docs/reference/dns.md")
DEFAULT_PORT = 53
IGNORED_TYPES = {"DHCID", "SOA"}
POWERDNS_ALIAS = 65401
POWERDNS_LUA = 65402
@dataclass(frozen=True)
class Record:
zone: str
owner: str
type: str
value: str
def normalize_name(name: str) -> str:
return name.rstrip(".").lower()
def record_data(rdtype: int, rdata) -> tuple[str, str]:
if rdtype == POWERDNS_ALIAS:
target, _used = dns.name.from_wire(rdata.data, 0)
return "ALIAS", target.to_text()
if rdtype == POWERDNS_LUA:
logical_type = int.from_bytes(rdata.data[:2], byteorder="big")
return "LUA", dns.rdatatype.to_text(logical_type)
return dns.rdatatype.to_text(rdtype), rdata.to_text()
def server_addresses(server: str, port: int) -> list[str]:
addresses = []
try:
for result in socket.getaddrinfo(server, port, type=socket.SOCK_STREAM):
address = result[4][0]
if address not in addresses:
addresses.append(address)
except socket.gaierror as error:
raise RuntimeError(f"cannot resolve nameserver {server}: {error}") from error
return addresses
def transfer(server: str, port: int, zone: str) -> list[Record]:
addresses = server_addresses(server, port)
errors = []
for address in addresses:
try:
records = []
for message in dns.query.xfr(
address, zone, port=port, lifetime=60, relativize=False
):
for rrset in message.answer:
for rdata in rrset:
record_type, value = record_data(rrset.rdtype, rdata)
records.append(
Record(
zone=normalize_name(zone),
owner=normalize_name(rrset.name.to_text()),
type=record_type,
value=value,
)
)
if not any(record.type == "SOA" for record in records):
raise RuntimeError("transfer returned no SOA record")
return records
except (dns.exception.DNSException, OSError, RuntimeError) as error:
errors.append(f"{address}: {error}")
raise RuntimeError(f"AXFR of {zone} from {server} failed ({'; '.join(errors)})")
def system_nameservers(domain: str) -> list[str]:
try:
answer = dns.resolver.resolve(domain, "NS", lifetime=30)
except dns.exception.DNSException as error:
message = f"cannot discover authoritative servers for {domain}: {error}"
raise RuntimeError(message) from error
return [rdata.target.to_text() for rdata in answer]
def nameservers_via(server: str, port: int, domain: str) -> list[str]:
errors = []
for address in server_addresses(server, port):
resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = [address]
resolver.port = port
try:
answer = resolver.resolve(domain, "NS", lifetime=15, search=False)
return [rdata.target.to_text() for rdata in answer]
except dns.exception.DNSException as error:
errors.append(f"{address}: {error}")
raise RuntimeError(f"NS query for {domain} via {server} failed ({'; '.join(errors)})")
def discover_nameservers(domains: list[str], port: int) -> dict[str, list[str]]:
discovered = {}
unresolved = {}
candidates = []
for domain in domains:
try:
servers = system_nameservers(domain)
discovered[domain] = servers
for server in servers:
if server not in candidates:
candidates.append(server)
except RuntimeError as error:
unresolved[domain] = [str(error)]
for domain, errors in list(unresolved.items()):
for server in candidates:
try:
discovered[domain] = nameservers_via(server, port, domain)
del unresolved[domain]
break
except RuntimeError as error:
errors.append(str(error))
if unresolved:
details = "; ".join(
f"{domain}: {'; '.join(errors)}" for domain, errors in unresolved.items()
)
raise RuntimeError(details)
return discovered
def transfer_domain(
port: int, domain: str, servers: list[str], fallback: list[str] = ()
) -> list[Record]:
# A zone may be delegated publicly to servers that refuse AXFR (e.g. HE serving
# reverse DNS) while our own authoritative servers, discovered for other zones,
# will transfer it. Try the delegated servers first, then fall back to those.
ordered = list(servers)
for server in fallback:
if server not in ordered:
ordered.append(server)
errors = []
for server in ordered:
try:
return transfer(server, port, domain)
except RuntimeError as error:
errors.append(str(error))
raise RuntimeError(f"no authoritative server allowed AXFR for {domain} ({'; '.join(errors)})")
def dynamic_names(records: list[Record]) -> set[str]:
return {record.owner for record in records if record.type == "DHCID"}
def dynamic_addresses(records: list[Record], names: set[str]) -> set[str]:
return {
record.value.rstrip(".").lower()
for record in records
if record.owner in names and record.type in {"A", "AAAA"}
}
def reverse_address(owner: str) -> str | None:
if owner.endswith(".in-addr.arpa"):
labels = owner.removesuffix(".in-addr.arpa").split(".")
if len(labels) != 4:
return None
try:
return str(ipaddress.IPv4Address(".".join(reversed(labels))))
except ValueError:
return None
if owner.endswith(".ip6.arpa"):
labels = owner.removesuffix(".ip6.arpa").split(".")
if len(labels) != 32:
return None
try:
value = int("".join(reversed(labels)), 16)
return str(ipaddress.IPv6Address(value))
except ValueError:
return None
return None
def static_records(records: list[Record]) -> list[Record]:
names = dynamic_names(records)
addresses = dynamic_addresses(records, names)
static = []
for record in records:
if record.type in IGNORED_TYPES or record.owner in names:
continue
if record.type == "PTR":
target = normalize_name(record.value.split()[0])
address = reverse_address(record.owner)
if target in names or address in addresses:
continue
static.append(record)
return static
def relative_name(owner: str, zone: str) -> str:
if owner == zone:
return "@"
suffix = f".{zone}"
return owner[: -len(suffix)] if owner.endswith(suffix) else owner
def markdown_code(value: str) -> str:
escaped = value.replace("|", "\\|")
return f"`{escaped}`"
def display_record(record: Record) -> tuple[str, str]:
if record.type != "LUA":
return record.type, record.value
return f"{record.value} (LUA)", "generated at query time"
def render_forward(domain: str, records: list[Record]) -> list[str]:
domain = normalize_name(domain)
rows = []
for record in records:
if record.zone != domain or record.type == "PTR":
continue
record_type, value = display_record(record)
rows.append((relative_name(record.owner, record.zone), record_type, value))
rows.sort(key=lambda row: (row[0] != "@", row[0], row[1], row[2]))
lines = ["| Name | Type | Value |", "|---|---|---|"]
lines.extend(
f"| {markdown_code(name)} | {markdown_code(record_type)} | {markdown_code(value)} |"
for name, record_type, value in rows
)
return lines
def render_reverse(domain: str, records: list[Record]) -> list[str]:
domain = normalize_name(domain)
rows = []
for record in records:
if record.zone != domain or record.type != "PTR":
continue
rows.append((reverse_address(record.owner) or record.owner, record.value))
rows.sort(key=lambda row: ipaddress.ip_address(row[0]))
lines = ["| Address | Name |", "|---|---|"]
lines.extend(
f"| {markdown_code(address)} | {markdown_code(name)} |" for address, name in rows
)
return lines
def is_reverse(domain: str) -> bool:
domain = normalize_name(domain)
return domain.endswith(".in-addr.arpa") or domain.endswith(".ip6.arpa")
def rendered_zones(transferred: list[tuple[str, list[Record]]]) -> dict[str, list[str]]:
records = static_records([record for _domain, zone in transferred for record in zone])
rendered = {}
for domain, _raw_records in transferred:
if is_reverse(domain):
rendered[normalize_name(domain)] = render_reverse(domain, records)
else:
rendered[normalize_name(domain)] = render_forward(domain, records)
return rendered
def update_target(transferred: list[tuple[str, list[Record]]], target: Path) -> bool:
rendered = rendered_zones(transferred)
text = target.read_text()
lines = text.splitlines()
marker_re = re.compile(r"^<!--\s*dns:\s*(\S+)\s*-->$")
found = set()
output = []
i = 0
while i < len(lines):
match = marker_re.match(lines[i].strip())
if not match or normalize_name(match.group(1)) not in rendered:
output.append(lines[i])
i += 1
continue
domain = normalize_name(match.group(1))
end = i + 1
while end < len(lines) and lines[end].strip() != "<!-- dns-end -->":
end += 1
if end >= len(lines):
raise RuntimeError(f"missing <!-- dns-end --> for {domain}")
output.extend(
[
lines[i],
"<!-- dns-start -->",
*rendered[domain],
"<!-- dns-end -->",
]
)
found.add(domain)
i = end + 1
missing = rendered.keys() - found
if missing:
raise RuntimeError(f"missing DNS markers for: {', '.join(sorted(missing))}")
new = "\n".join(output) + "\n"
if new == text:
return False
target.write_text(new)
return True
def main() -> int:
parser = argparse.ArgumentParser(prog="update-docs-dns", description=__doc__)
parser.add_argument("domain", nargs="+", help="DNS zone to transfer")
parser.add_argument("--port", type=int, default=DEFAULT_PORT)
parser.add_argument("--output", type=Path, default=OUT)
args = parser.parse_args()
try:
nameservers = discover_nameservers(args.domain, args.port)
fallback = []
for servers in nameservers.values():
for server in servers:
if server not in fallback:
fallback.append(server)
transferred = [
(domain, transfer_domain(args.port, domain, nameservers[domain], fallback))
for domain in args.domain
]
except RuntimeError as error:
print(f"update-docs-dns: {error}", file=sys.stderr)
return 1
try:
changed = update_target(transferred, args.output)
except (OSError, RuntimeError) as error:
print(f"update-docs-dns: {error}", file=sys.stderr)
return 1
if changed:
print(f"updated {args.output}")
return 0
if __name__ == "__main__":
sys.exit(main())
+4 -1
View File
@@ -12,6 +12,9 @@ in
NIX_USER_CONF_FILES = toString (pkgs.writeText "nix.conf"
''
experimental-features = nix-command flakes ca-derivations
lazy-trees = true
eval-cores = 0
accept-flake-config = true
connect-timeout = 5
fallback = true
${lib.my.c.nix.cache.conf}
@@ -22,7 +25,7 @@ in
packages = with pkgs; [
coreutils
nixVersions.stable
determinate-nix
rage
wireguard-tools
(pkgs.writeShellScriptBin "deploy" ''
+1 -1
View File
@@ -29,7 +29,7 @@ let
coreutils
gnugrep
openssh
nixVersions.stable
determinate-nix
jq
];
text =
+4 -1
View File
@@ -25,6 +25,9 @@ Not every box fits this pattern, but **colony** and **home** are organised this
- [`networking.md`](networking.md) — network assignments, domains, site topologies, router HA,
the AS211024 L2 mesh, BGP, WireGuard, Tailscale.
- [`deployment.md`](deployment.md) — deploy-rs, devshell commands, secrets workflow, CI.
- [`nixpkgs-upgrade.md`](nixpkgs-upgrade.md) — guided procedure for the periodic upgrade of the four
nixpkgs channels and home-manager (fork rebase, stable bumps, input review).
- [`reference/dns.md`](reference/dns.md) — generated forward and reverse DNS record reference.
- [`reference/nixos-options.md`](reference/nixos-options.md) — generated per-option reference for
the custom `my.*` NixOS modules.
@@ -63,7 +66,7 @@ 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")
│ └── sfh ──── container host VM ("shill from home")
│ ├── hass ── Home Assistant + Frigate + MQTT (container)
│ └── unifi ─ UniFi controller (container)
├── stream (physical secondary router — Virgin Media WAN)
+41 -7
View File
@@ -110,6 +110,31 @@ VM's unix sockets from `/run/vms/<vm>/` on `<host>` over SSH):
| `vm-monitor <host> <vm>` | QEMU monitor socket in `minicom`. |
| `vm-viewer <host> <vm>` | SPICE display in `virt-viewer` (not on Darwin). |
## Nix implementation
Every context uses **Determinate Nix** as its `nix.package`, for its performance features
(parallel evaluation and lazy trees) — not `determinate-nixd`; the daemon and `nix.conf` model
are unchanged, and the Determinate NixOS module is deliberately not imported.
- **Input and package.** The [`determinate-nix`](../flake.nix) input is the `nix-src` flake
(`flakehub.com/f/DeterminateSystems/nix-src`), with `nixpkgs.follows = "nixpkgs-unstable"`. We
build it ourselves against our pinned nixpkgs — FlakeHub's own cache needs authentication, so
there is nothing to gain from leaving it unpinned — and it then flows through the Harmonia cache
like everything else. `determinateOverlay` exposes it under the stable attr `determinate-nix`,
added to both the devshell `pkgs'` and the config `configPkgs'` overlay lists, so systems, homes
and the devshell all resolve the same package (`pkgs'.mine.determinate-nix`).
- **Settings.** `lib.my.c.nix.determinateSettings` (`lazy-trees`, `eval-cores = 0`) is merged into
`nix.settings` for systems and homes and into the devshell's `nix.conf`. These keys are only
understood by the Determinate binary.
- **Consumers follow automatically.** Everything that shells out to Nix references
`config.nix.package` (deploy-rs, containers, `build`, netboot, Harmonia), so they inherit
Determinate without further change.
- **`accept-flake-config`.** Set true only in the devshell `nix.conf`, `.envrc` (as
`--accept-flake-config`, for direnv) and CI — the contexts that build this flake — so its
`nixConfig` (the Harmonia cache) is trusted without an interactive prompt. It is deliberately not
set system-wide: boxes already trust that cache via `nix.settings`, so a global setting would only
blanket-trust every flake's `nixConfig` for no gain.
## Secrets
Secrets are age-encrypted files in [`secrets/`](../secrets), managed with **ragenix** (a fork
@@ -152,8 +177,10 @@ GitHub/Gitea Actions workflows live in [`.gitea/workflows/`](../.gitea/workflows
### `ci.yaml`
On pushes to `master`, this runs `nix flake check --no-build`, then builds every attribute of
`.#ci.x86_64-linux`: systems as `system-<name>`, homes as `home-<name>` (with `@` changed to
On pushes to `master`, this installs Determinate Nix on the runner (via
`DeterminateSystems/determinate-nix-action`, configured with the same performance settings and
Harmonia substituter as the boxes), runs `nix flake check --no-build`, then builds every attribute
of `.#ci.x86_64-linux`: systems as `system-<name>`, homes as `home-<name>` (with `@` changed to
`-at-`), packages as `package-<name>`, and the development `shell`. Each result is pushed to the
Harmonia cache with [`ci/push-to-cache.sh`](../ci/push-to-cache.sh).
@@ -170,14 +197,13 @@ Pushing the `installer` tag (refreshed by `update-installer`) builds `my.buildAs
### `update-docs.yaml`
On pushes to the docs branch, excluding its own commits, this runs
`nix run .#update-docs-assignments` and `nix run .#update-docs-options` and commits changed outputs
as `docs: update generated tables`.
On pushes to `master`, excluding its own commits, this runs the assignment, option and DNS
reference generators and commits changed outputs as `docs: Update generated references`.
### The docs generators
Both are registered in [`pkgs/default.nix`](../pkgs/default.nix) (`writeShellScriptBin`s wrapping
Python scripts under [`ci/`](../ci)). They leave the worktree unchanged when their output is current;
The generators are registered in [`pkgs/default.nix`](../pkgs/default.nix) as wrappers around
Python scripts under [`ci/`](../ci). They leave the worktree unchanged when their output is current;
the workflow stages `docs/` and uses `git diff --cached --quiet` to decide whether to commit.
`update-docs-assignments` ([`ci/update-docs-assignments.py`](../ci/update-docs-assignments.py))
@@ -197,3 +223,11 @@ apply to every box, so defaults don't pick up a real host's values. The renderer
[`docs/reference/nixos-options.md`](reference/nixos-options.md), one table per module file. The
whole file is generated; edit the option descriptions in the modules, not the reference. The
internal `asX` build-target options are marked `internal = true` so they're excluded.
`update-docs-dns` ([`ci/update-docs-dns.py`](../ci/update-docs-dns.py)) accepts forward and reverse
zone names, discovers their authoritative nameservers through NS queries, and transfers each zone
over AXFR. If a private reverse zone is not visible through the configured recursive resolver, it
asks the authoritative servers discovered for the other requested zones. It updates only the
matching `<!-- dns: <zone> -->` blocks in the [`DNS records`](reference/dns.md) reference; the page's
headings and prose remain handwritten. Kea-managed owners are identified by `DHCID` records and
removed together with their A, AAAA and PTR records; SOA records and TTLs are also omitted.
+11 -11
View File
@@ -6,17 +6,6 @@ Portable workstation — a Framework Laptop 13 (Intel), running the full GUI env
- **Host:** physical (laptop)
- **nixpkgs:** `mine`
## Role
- Personal portable workstation: `my.gui.enable`, with Sway managed by home-manager.
- Joins the tailnet through the headscale on [`britway`](../remote/britway.md) (fish abbr
`tsup` = `doas tailscale up --login-server=https://hs.nul.ie --accept-routes`).
## Network assignments
`tower` has no static assignment; it uses DHCP through NetworkManager and reaches the other boxes
over Tailscale.
## Hardware / platform
| Component | Inventory |
@@ -32,6 +21,17 @@ The configuration enables Intel microcode updates, `kvm-intel`, `intel_iommu=on`
`intel-media-driver` and the latest kernel (`lib.my.c.kernel.latest`). Thunderbolt security
(`bolt`), the fingerprint reader (`fprintd`) and `tlp` power management are also enabled.
## Role
- Personal portable workstation: `my.gui.enable`, with Sway managed by home-manager.
- Joins the tailnet through the headscale on [`britway`](../remote/britway.md) (fish abbr
`tsup` = `doas tailscale up --login-server=https://hs.nul.ie --accept-routes`).
## Network assignments
`tower` has no static assignment; it uses DHCP through NetworkManager and reaches the other boxes
over Tailscale.
## Storage
- Two LUKS-encrypted partitions, `persist` and `home` (both `allowDiscards`); `/nix` is a
+9 -1
View File
@@ -321,13 +321,21 @@ family move together.
serving disjoint pool halves. `radvd` advertises the v6 VIP as RDNSS (`untrusted` gets Cloudflare)
and keepalived's `notify_master`/`notify_backup` hooks ensure that only the master sends RAs.
Statically-addressed boxes (the servers on `hi`) don't run DHCP, so they'd otherwise learn a
resolver only from the v6 RA RDNSS — which vanishes when v6 is disabled, taking DNS with it. They
instead anchor DNS on the VIPs via the shared `lib.my.c.home.vlanDns "<vlan>"` fragment, which sets
`DNS` to `vips.<vlan>.{v4,v6}` and `Domains` to the advertised search list; the always-present
static v4 VIP keeps resolution working even with v6 down.
#### DNS binding
`pdns-recursor` binds the VIPs directly; see
[`routing-common/dns.nix`](../nixos/boxes/home/routing-common/dns.nix). The
`net.ipv4.ip_nonlocal_bind` / `net.ipv6.ip_nonlocal_bind` settings let the backup listen before it
owns the addresses, so failover does not depend on client resolver timeouts. The recursor forwards
the site's zones to authoritative PowerDNS on `127.0.0.1:5353`.
the site's zones to authoritative PowerDNS on `127.0.0.1:5353`. The generated
[DNS reference](reference/dns.md) lists the live forward and reverse records; the authoritative
servers allow its AXFRs from the shared internal prefixes and the colony site's egress address.
#### `wan-online.target`
+125
View File
@@ -0,0 +1,125 @@
# Upgrading nixpkgs
Procedure for the periodic upgrade of all four nixpkgs channels (`unstable`, `stable`, `mine`,
`mine-stable`) and home-manager. Written to be followed by a person or any coding agent; a
Claude Code entry point exists at `.claude/skills/upgrade-nixpkgs/` but the steps below are the
canonical source.
The upgrade is **guided, not automated**: do the mechanical and investigative steps, but stop at
the judgment points (marked ⏸) — pushing the fork, resolving rebase conflicts, editing the
`flake.nix` stable pins, and deleting version guards. Report findings and let the maintainer
decide. Keep a running summary and present it before any push or commit.
Work the phases in order; skip one only if explicitly scoped to a subset.
## Setup facts
- **Fork checkout:** `~/documents/projects/nixpkgs` — remotes `origin` (`devplayer0/nixpkgs`) and
`upstream` (`NixOS/nixpkgs`). Confirm the path exists; if not, ask.
- **Fork branches:** `devplayer0` (tracks `nixos-unstable`) and `devplayer0-stable` (tracks the
current NixOS stable). Each is a small stack of local patches rebased onto upstream.
- **Flake pins** in `flake.nix` (`inputs`):
- `nixpkgs-unstable.url = "nixpkgs/nixos-unstable"`
- `nixpkgs-stable.url = "nixpkgs/nixos-<STABLE>"` (e.g. `nixos-26.05`)
- `nixpkgs-mine.url = "github:devplayer0/nixpkgs/devplayer0"`
- `nixpkgs-mine-stable.url = "github:devplayer0/nixpkgs/devplayer0-stable"`
- `home-manager-unstable.url = "home-manager"`
- `home-manager-stable.url = "home-manager/release-<STABLE>"`
- **Devshell commands:** `update-nixpkgs` = `nix flake update nixpkgs-{unstable,stable,mine,mine-stable}`;
`update-home-manager` = `nix flake update home-manager-{unstable,stable}`.
- **Validation:** prefer `check-system <host>` and `nix flake check --no-build` over full builds.
## Phase 1 — Determine the current NixOS stable
Do this first: everything downstream (the fork's `devplayer0-stable` rebase target, the `flake.nix`
stable pins) has to agree on one NixOS stable release, so establish it up front.
1. Find the latest NixOS stable release branch — check `git branch -r` on `upstream` for the newest
`release-YY.NN`, or the NixOS release schedule.
2. Compare it to `<STABLE>` in the `flake.nix` `nixpkgs-stable` / `home-manager-stable` URLs.
3. **If they already match** (no new stable): note "stable is current" and carry `<STABLE>` into the
later phases.
4.**If a newer stable has cut:** stop and report the coordinated change set before proceeding —
the pieces must all move to the same release together:
- Rebase `devplayer0-stable` onto the new `upstream/release-YY.NN` (Phase 2 uses this target).
- Edit `flake.nix`: `nixpkgs-stable.url` and `home-manager-stable.url` → the new release.
- Bump each system's `stateVersion` / `home.stateVersion` only if the maintainer explicitly
wants to — that is a separate, deliberate decision; never auto-bump.
Don't edit `flake.nix` here without confirmation.
## Phase 2 — Rebase the nixpkgs fork
For **both** branches — `devplayer0` onto `upstream/nixos-unstable`, and `devplayer0-stable` onto
`upstream/release-<STABLE>` (the release established in Phase 1):
1. In `~/documents/projects/nixpkgs`, confirm a clean working tree (`git status`). If dirty, stop
and report — don't stash silently.
2. `git fetch upstream --prune` and `git fetch origin --prune`. If the checkout has been idle a
long time this fetch can be large and slow; let it finish.
3. Enumerate the patch stack before rebasing:
`git log --oneline upstream/nixos-unstable..origin/devplayer0` (and the stable equivalent
against `upstream/release-<STABLE>`). For each commit, check whether it has landed upstream or
been superseded — e.g. `git log --oneline upstream/nixos-unstable -- <path>` or grep the
upstream tree for the package/option. Note any patch that now looks redundant.
4. Rebase: `git switch devplayer0 && git rebase upstream/nixos-unstable` (and the stable branch
onto `upstream/release-<STABLE>`).
-**Conflicts:** stop. Report which patch conflicts and against what upstream change; let the
maintainer resolve, or drop the patch if it has been upstreamed.
- Clean rebase: continue.
5. Summarize: which patches still apply, which are now redundant (candidate to drop), which
conflicted.
6.**Push:** only after confirmation. `git push --force-with-lease origin devplayer0
devplayer0-stable` (force needed — rebase rewrites history).
## Phase 3 — Update the pinned inputs
Run together (they move as a set):
```
update-nixpkgs
update-home-manager
```
Then show the `flake.lock` diff for the nixpkgs/home-manager entries so the old→new revisions are
visible.
## Phase 4 — Sweep version-gated behavior
The repo carries branch-conditional logic and TODOs keyed to specific nixpkgs versions; some become
removable after an upgrade, especially after a stable bump. Surface them:
```
grep -rn "versionAtLeast\|versionOlder\|when 2[0-9]\.[0-9][0-9]\|TODO.*2[0-9]\.[0-9][0-9]" \
--include=*.nix nixos home-manager lib pkgs flake.nix
```
Known example: `nixos/modules/common.nix` carries a `# TODO: Remove if-else when 26.11 releases`
guard. For each hit, evaluate whether the now-current versions make the guard removable and list
candidates. ⏸ Don't delete guards without confirmation — some protect the still-supported stable.
## Phase 5 — Review remaining flake inputs
Don't blanket-update. Walk the other inputs deliberately:
1. List inputs and locked revisions from `flake.lock` (or `nix flake metadata`).
2. For each meaningful input (`libnetRepo`, `devshell`, `determinate-nix`, `ragenix`, `deploy-rs`,
`impermanence`, and the packaged apps like `boardie`, `harmonia`, `copyparty`, `sharry`, …),
compare the locked revision to upstream and summarize notable changes (breaking changes,
relevant fixes). Many inputs `follows` `nixpkgs-unstable` and already moved in Phase 3.
3. Propose a per-input update list with reasons; update the approved ones with targeted
`nix flake update <input>`, not a global update.
## Phase 6 — Validate
1. `nix flake check --no-build` (broad eval; reproduces CI's cheap checks).
2. `check-system <host>` on a representative box, and one exercising the stable channel if the
boxes mix channels.
3. Report eval/build results honestly. On failure, surface the error and stop rather than papering
over it.
## Wrap-up
Present a final summary: fork rebase outcome (patches kept/dropped/conflicted), whether a stable
bump is pending or was applied, the lock diff, version-gate cleanup candidates, inputs updated, and
validation results. Leave committing to the maintainer unless asked; if committing, follow the
repo's `area/scope: Capitalized summary` convention.
+319
View File
@@ -0,0 +1,319 @@
# DNS records
The tables on this page are generated from live authoritative zone transfers by
`nix run .#update-docs-dns -- <zones...>`; CI keeps them current. The Nix DNS configuration is
the source of truth. Edit the prose and headings here, but not content between the DNS markers.
DHCP-managed records are excluded. The generator identifies them by `DHCID` and removes the
corresponding forward and reverse records. SOA records and TTLs are also omitted because they are
operational metadata rather than useful inventory.
## Colony
These zones are served by [`estuary`](../sites/colony/estuary.md); their source configuration is
[`estuary/dns.nix`](../../nixos/boxes/colony/vms/estuary/dns.nix).
### Forward zone: `ams1.int.nul.ie`
<!-- dns: ams1.int.nul.ie -->
<!-- dns-start -->
| Name | Type | Value |
|---|---|---|
| `@` | `ALIAS` | `estuary-vm.ams1.int.nul.ie.` |
| `@` | `NS` | `ns.ams1.int.nul.ie.` |
| `_acme-challenge` | `TXT (LUA)` | `generated at query time` |
| `andrey-cust` | `A` | `94.142.242.254` |
| `chatterbox-ctr` | `A` | `10.100.2.5` |
| `chatterbox-ctr` | `AAAA` | `2a0e:97c0:4d2:12::5` |
| `colony` | `A` | `94.142.241.224` |
| `colony` | `AAAA` | `2a0e:97c0:4d2:10::2` |
| `colony-psql` | `CNAME` | `colony-psql-ctr.ams1.int.nul.ie.` |
| `colony-psql-ctr` | `A` | `10.100.2.4` |
| `colony-psql-ctr` | `AAAA` | `2a0e:97c0:4d2:12::4` |
| `colony-routing` | `A` | `10.100.0.2` |
| `colony-vms` | `A` | `10.100.1.1` |
| `colony-vms` | `AAAA` | `2a0e:97c0:4d2:11::1` |
| `ctr` | `CNAME` | `shill-vm.ams1.int.nul.ie.` |
| `darts-cust` | `A` | `94.142.242.255` |
| `darts-cust` | `AAAA` | `2a0e:97c0:4d2:2001::1` |
| `enshrouded` | `A` | `94.142.240.44` |
| `enshrouded-oci` | `A` | `10.100.3.5` |
| `enshrouded-oci` | `AAAA` | `2a0e:97c0:4d2:13::5` |
| `estuary-vm` | `A` | `94.142.240.44` |
| `estuary-vm` | `AAAA` | `2a02:898:0:20::329:1` |
| `estuary-vm-base` | `A` | `10.100.0.1` |
| `estuary-vm-base` | `AAAA` | `2a0e:97c0:4d2:10::1` |
| `fw` | `CNAME` | `estuary-vm.ams1.int.nul.ie.` |
| `gam-ctr` | `A` | `10.100.2.11` |
| `gam-ctr` | `AAAA` | `2a0e:97c0:4d2:12::b` |
| `git-vm` | `A` | `94.142.241.117` |
| `git-vm` | `AAAA` | `2a0e:97c0:4d2:11::4` |
| `git-vm-routing` | `A` | `10.100.1.4` |
| `graeme` | `A` | `94.142.240.44` |
| `graeme` | `AAAA` | `2a0e:97c0:4d2:13::8` |
| `graeme-oci` | `A` | `10.100.3.8` |
| `graeme-oci` | `AAAA` | `2a0e:97c0:4d2:13::8` |
| `hillcrest-tun` | `A` | `10.100.5.2` |
| `http` | `A` | `94.142.240.44` |
| `http` | `AAAA` | `2a0e:97c0:4d2:12::2` |
| `jackflix-ctr` | `A` | `10.100.2.6` |
| `jackflix-ctr` | `AAAA` | `2a0e:97c0:4d2:12::6` |
| `jam-cust` | `A` | `10.100.100.4` |
| `jam-cust` | `AAAA` | `2a0e:97c0:4d2:2002::1` |
| `jam-fwd` | `A` | `94.142.241.225` |
| `john-valorant-tun` | `A` | `10.100.5.6` |
| `kevcraft` | `A` | `94.142.240.44` |
| `kevcraft` | `AAAA` | `2a0e:97c0:4d2:13::6` |
| `kevcraft-oci` | `A` | `10.100.3.6` |
| `kevcraft-oci` | `AAAA` | `2a0e:97c0:4d2:13::6` |
| `kinkcraft` | `A` | `94.142.240.44` |
| `kinkcraft` | `AAAA` | `2a0e:97c0:4d2:13::7` |
| `kinkcraft-oci` | `A` | `10.100.3.7` |
| `kinkcraft-oci` | `AAAA` | `2a0e:97c0:4d2:13::7` |
| `librespeed` | `CNAME` | `http.ams1.int.nul.ie.` |
| `mail-vm` | `A` | `94.142.241.227` |
| `mail-vm` | `AAAA` | `2a0e:97c0:4d2:2000::1` |
| `middleman-ctr` | `A` | `10.100.2.2` |
| `middleman-ctr` | `AAAA` | `2a0e:97c0:4d2:12::2` |
| `ns` | `ALIAS` | `estuary-vm.ams1.int.nul.ie.` |
| `object-ctr` | `A` | `10.100.2.7` |
| `object-ctr` | `AAAA` | `2a0e:97c0:4d2:12::7` |
| `oci` | `CNAME` | `whale-vm.ams1.int.nul.ie.` |
| `qclk-ctr` | `A` | `10.100.2.10` |
| `qclk-ctr` | `AAAA` | `2a0e:97c0:4d2:12::a` |
| `shill-vm` | `A` | `94.142.241.225` |
| `shill-vm` | `AAAA` | `2a0e:97c0:4d2:11::2` |
| `shill-vm-ctrs` | `A` | `10.100.2.1` |
| `shill-vm-ctrs` | `AAAA` | `2a0e:97c0:4d2:12::1` |
| `shill-vm-routing` | `A` | `10.100.1.2` |
| `simpcraft` | `A` | `94.142.240.44` |
| `simpcraft` | `AAAA` | `2a0e:97c0:4d2:13::3` |
| `simpcraft-oci` | `A` | `10.100.3.3` |
| `simpcraft-oci` | `AAAA` | `2a0e:97c0:4d2:13::3` |
| `simpcraft-staging` | `A` | `94.142.240.44` |
| `simpcraft-staging` | `AAAA` | `2a0e:97c0:4d2:13::4` |
| `simpcraft-staging-oci` | `A` | `10.100.3.4` |
| `simpcraft-staging-oci` | `AAAA` | `2a0e:97c0:4d2:13::4` |
| `terraria` | `A` | `94.142.240.44` |
| `terraria` | `AAAA` | `2a0e:97c0:4d2:12::b` |
| `toot-ctr` | `A` | `10.100.2.8` |
| `toot-ctr` | `AAAA` | `2a0e:97c0:4d2:12::8` |
| `valheim` | `A` | `94.142.240.44` |
| `valheim` | `AAAA` | `2a0e:97c0:4d2:13::2` |
| `valheim-oci` | `A` | `10.100.3.2` |
| `valheim-oci` | `AAAA` | `2a0e:97c0:4d2:13::2` |
| `vaultwarden-ctr` | `A` | `10.100.2.3` |
| `vaultwarden-ctr` | `AAAA` | `2a0e:97c0:4d2:12::3` |
| `vm` | `CNAME` | `colony.ams1.int.nul.ie.` |
| `waffletail-ctr` | `A` | `10.100.2.9` |
| `waffletail-ctr` | `AAAA` | `2a0e:97c0:4d2:12::9` |
| `whale-vm` | `A` | `94.142.241.226` |
| `whale-vm` | `AAAA` | `2a0e:97c0:4d2:11::3` |
| `whale-vm-routing` | `A` | `10.100.1.3` |
<!-- dns-end -->
### IPv4 reverse zone: `100.10.in-addr.arpa`
<!-- dns: 100.10.in-addr.arpa -->
<!-- dns-start -->
| Address | Name |
|---|---|
| `10.100.0.1` | `estuary-vm-base.ams1.int.nul.ie.` |
| `10.100.0.2` | `colony-routing.ams1.int.nul.ie.` |
| `10.100.1.1` | `colony-vms.ams1.int.nul.ie.` |
| `10.100.1.2` | `shill-vm-routing.ams1.int.nul.ie.` |
| `10.100.1.3` | `whale-vm-routing.ams1.int.nul.ie.` |
| `10.100.1.4` | `git-vm-routing.ams1.int.nul.ie.` |
| `10.100.2.1` | `shill-vm-ctrs.ams1.int.nul.ie.` |
| `10.100.2.2` | `middleman-ctr.ams1.int.nul.ie.` |
| `10.100.2.3` | `vaultwarden-ctr.ams1.int.nul.ie.` |
| `10.100.2.4` | `colony-psql-ctr.ams1.int.nul.ie.` |
| `10.100.2.5` | `chatterbox-ctr.ams1.int.nul.ie.` |
| `10.100.2.6` | `jackflix-ctr.ams1.int.nul.ie.` |
| `10.100.2.7` | `object-ctr.ams1.int.nul.ie.` |
| `10.100.2.8` | `toot-ctr.ams1.int.nul.ie.` |
| `10.100.2.9` | `waffletail-ctr.ams1.int.nul.ie.` |
| `10.100.2.10` | `qclk-ctr.ams1.int.nul.ie.` |
| `10.100.2.11` | `gam-ctr.ams1.int.nul.ie.` |
| `10.100.3.2` | `valheim-oci.ams1.int.nul.ie.` |
| `10.100.3.3` | `simpcraft-oci.ams1.int.nul.ie.` |
| `10.100.3.4` | `simpcraft-staging-oci.ams1.int.nul.ie.` |
| `10.100.3.5` | `enshrouded-oci.ams1.int.nul.ie.` |
| `10.100.3.6` | `kevcraft-oci.ams1.int.nul.ie.` |
| `10.100.3.7` | `kinkcraft-oci.ams1.int.nul.ie.` |
| `10.100.3.8` | `graeme-oci.ams1.int.nul.ie.` |
<!-- dns-end -->
### IPv6 reverse zone: `2.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa`
<!-- dns: 2.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa -->
<!-- dns-start -->
| Address | Name |
|---|---|
| `2a0e:97c0:4d2:10::1` | `estuary-vm-base.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:10::2` | `colony.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:11::1` | `colony-vms.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:11::2` | `shill-vm.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:11::3` | `whale-vm.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:11::4` | `git-vm.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::1` | `shill-vm-ctrs.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::2` | `middleman-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::3` | `vaultwarden-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::4` | `colony-psql-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::5` | `chatterbox-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::6` | `jackflix-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::7` | `object-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::8` | `toot-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::9` | `waffletail-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::a` | `qclk-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:12::b` | `gam-ctr.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::2` | `valheim-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::3` | `simpcraft-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::4` | `simpcraft-staging-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::5` | `enshrouded-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::6` | `kevcraft-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::7` | `kinkcraft-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:13::8` | `graeme-oci.ams1.int.nul.ie.` |
| `2a0e:97c0:4d2:2000::1` | `mail.nul.ie.` |
| `2a0e:97c0:4d2:2001::1` | `darts-cust.ams1.int.nul.ie.` |
<!-- dns-end -->
## Home
These zones are served by [`river`](../sites/home/river.md) and
[`stream`](../sites/home/stream.md); their shared source configuration is
[`routing-common/dns.nix`](../../nixos/boxes/home/routing-common/dns.nix).
### Forward zone: `h.nul.ie`
<!-- dns: h.nul.ie -->
<!-- dns-start -->
| Name | Type | Value |
|---|---|---|
| `@` | `NS` | `ns1.h.nul.ie.` |
| `@` | `NS` | `ns2.h.nul.ie.` |
| `boot` | `CNAME` | `river-hi.h.nul.ie.` |
| `brian` | `A` | `192.168.64.13` |
| `castle` | `A` | `192.168.68.40` |
| `castle` | `AAAA` | `2a0e:97c0:4d0:1::3:1` |
| `cellar` | `A` | `192.168.68.80` |
| `cellar` | `AAAA` | `2a0e:97c0:4d0:1::4:1` |
| `dave` | `A` | `192.168.68.11` |
| `dave` | `AAAA` | `2a0e:97c0:4d0:1::1:2` |
| `dave-core` | `A` | `192.168.64.11` |
| `dave-lo` | `A` | `192.168.72.11` |
| `dave-lo` | `AAAA` | `2a0e:97c0:4d0:2::1:2` |
| `dyn` | `NS` | `ns1.dyn.h.nul.ie.` |
| `dyn` | `NS` | `ns2.dyn.h.nul.ie.` |
| `frigate` | `CNAME` | `hass-ctr.h.nul.ie.` |
| `hass-ctr` | `A` | `192.168.68.103` |
| `hass-ctr` | `AAAA` | `2a0e:97c0:4d0:1::5:3` |
| `hass-ctr-lo` | `A` | `192.168.72.103` |
| `hass-ctr-lo` | `AAAA` | `2a0e:97c0:4d0:2::5:3` |
| `jim` | `A` | `192.168.68.10` |
| `jim` | `AAAA` | `2a0e:97c0:4d0:1::1:1` |
| `jim-core` | `A` | `192.168.64.10` |
| `jim-lo` | `A` | `192.168.72.10` |
| `jim-lo` | `AAAA` | `2a0e:97c0:4d0:2::1:1` |
| `nixlight` | `A` | `192.168.72.46` |
| `ns1` | `ALIAS` | `river.h.nul.ie.` |
| `ns1.dyn` | `ALIAS` | `river.h.nul.ie.` |
| `ns2` | `ALIAS` | `stream.h.nul.ie.` |
| `ns2.dyn` | `ALIAS` | `stream.h.nul.ie.` |
| `palace` | `A` | `192.168.68.22` |
| `palace` | `AAAA` | `2a0e:97c0:4d0:1::2:1` |
| `palace-core` | `A` | `192.168.64.20` |
| `palace-kvm` | `A` | `192.168.72.21` |
| `reolink-living-room` | `A` | `192.168.72.45` |
| `river` | `A (LUA)` | `generated at query time` |
| `river` | `AAAA` | `2a0e:97c0:4df:0:1::1` |
| `river-core` | `A` | `192.168.64.1` |
| `river-hi` | `A` | `192.168.68.1` |
| `river-hi` | `AAAA` | `2a0e:97c0:4d0:1::1` |
| `river-lo` | `A` | `192.168.72.1` |
| `river-lo` | `AAAA` | `2a0e:97c0:4d0:2::1` |
| `river-ut` | `A` | `192.168.80.1` |
| `river-ut` | `AAAA` | `2a0e:97c0:4d0:3::1` |
| `router-hi` | `A` | `192.168.71.254` |
| `router-hi` | `AAAA` | `2a0e:97c0:4d0:1::ffff` |
| `router-lo` | `A` | `192.168.79.254` |
| `router-lo` | `AAAA` | `2a0e:97c0:4d0:2::ffff` |
| `router-ut` | `A` | `192.168.80.254` |
| `router-ut` | `AAAA` | `2a0e:97c0:4d0:3::ffff` |
| `sfh` | `A` | `192.168.68.81` |
| `sfh` | `AAAA` | `2a0e:97c0:4d0:1::4:2` |
| `shytzel` | `A` | `192.168.64.12` |
| `stream` | `A (LUA)` | `generated at query time` |
| `stream` | `AAAA` | `2a0e:97c0:4df:0:1::2` |
| `stream-core` | `A` | `192.168.64.2` |
| `stream-hi` | `A` | `192.168.68.2` |
| `stream-hi` | `AAAA` | `2a0e:97c0:4d0:1::2` |
| `stream-lo` | `A` | `192.168.72.2` |
| `stream-lo` | `AAAA` | `2a0e:97c0:4d0:2::2` |
| `stream-ut` | `A` | `192.168.80.2` |
| `stream-ut` | `AAAA` | `2a0e:97c0:4d0:3::2` |
| `unifi-ctr` | `A` | `192.168.68.100` |
| `unifi-ctr` | `AAAA` | `2a0e:97c0:4d0:1::5:1` |
| `unifi-ctr-core` | `A` | `192.168.64.21` |
| `ups` | `A` | `192.168.72.20` |
| `vibe` | `A` | `192.168.68.15` |
| `vibe` | `AAAA` | `2a0e:97c0:4d0:1::1:6` |
| `vibe-core` | `A` | `192.168.64.15` |
| `vibe-lo` | `A` | `192.168.72.15` |
| `vibe-lo` | `AAAA` | `2a0e:97c0:4d0:2::1:6` |
| `wave` | `A` | `192.168.72.14` |
| `wave` | `AAAA` | `2a0e:97c0:4d0:2::1:5` |
| `wave-core` | `A` | `192.168.64.14` |
<!-- dns-end -->
### IPv4 reverse zone: `168.192.in-addr.arpa`
<!-- dns: 168.192.in-addr.arpa -->
<!-- dns-start -->
| Address | Name |
|---|---|
| `192.168.64.1` | `river-core.h.nul.ie.` |
| `192.168.64.2` | `stream-core.h.nul.ie.` |
| `192.168.64.20` | `palace-core.h.nul.ie.` |
| `192.168.64.21` | `unifi-ctr-core.h.nul.ie.` |
| `192.168.68.1` | `river-hi.h.nul.ie.` |
| `192.168.68.2` | `stream-hi.h.nul.ie.` |
| `192.168.68.22` | `palace.h.nul.ie.` |
| `192.168.68.40` | `castle.h.nul.ie.` |
| `192.168.68.80` | `cellar.h.nul.ie.` |
| `192.168.68.81` | `sfh.h.nul.ie.` |
| `192.168.68.100` | `unifi-ctr.h.nul.ie.` |
| `192.168.68.103` | `hass-ctr.h.nul.ie.` |
| `192.168.71.254` | `router-hi.h.nul.ie.` |
| `192.168.72.1` | `river-lo.h.nul.ie.` |
| `192.168.72.2` | `stream-lo.h.nul.ie.` |
| `192.168.72.103` | `hass-ctr-lo.h.nul.ie.` |
| `192.168.79.254` | `router-lo.h.nul.ie.` |
| `192.168.80.1` | `river-ut.h.nul.ie.` |
| `192.168.80.2` | `stream-ut.h.nul.ie.` |
| `192.168.80.254` | `router-ut.h.nul.ie.` |
<!-- dns-end -->
### IPv6 reverse zone: `0.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa`
<!-- dns: 0.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa -->
<!-- dns-start -->
| Address | Name |
|---|---|
| `2a0e:97c0:4d0:1::1` | `river-hi.h.nul.ie.` |
| `2a0e:97c0:4d0:1::2` | `stream-hi.h.nul.ie.` |
| `2a0e:97c0:4d0:1::ffff` | `router-hi.h.nul.ie.` |
| `2a0e:97c0:4d0:1::2:1` | `palace.h.nul.ie.` |
| `2a0e:97c0:4d0:1::3:1` | `castle.h.nul.ie.` |
| `2a0e:97c0:4d0:1::4:1` | `cellar.h.nul.ie.` |
| `2a0e:97c0:4d0:1::4:2` | `sfh.h.nul.ie.` |
| `2a0e:97c0:4d0:1::5:1` | `unifi-ctr.h.nul.ie.` |
| `2a0e:97c0:4d0:1::5:3` | `hass-ctr.h.nul.ie.` |
| `2a0e:97c0:4d0:2::1` | `river-lo.h.nul.ie.` |
| `2a0e:97c0:4d0:2::2` | `stream-lo.h.nul.ie.` |
| `2a0e:97c0:4d0:2::ffff` | `router-lo.h.nul.ie.` |
| `2a0e:97c0:4d0:2::5:3` | `hass-ctr-lo.h.nul.ie.` |
| `2a0e:97c0:4d0:3::1` | `river-ut.h.nul.ie.` |
| `2a0e:97c0:4d0:3::2` | `stream-ut.h.nul.ie.` |
| `2a0e:97c0:4d0:3::ffff` | `router-ut.h.nul.ie.` |
<!-- dns-end -->
+8 -8
View File
@@ -7,6 +7,14 @@ narrower gateway role than [`britway`](britway.md) (no control plane, no BGP).
- **Host:** VPS (Birmingham, `bhx1`; provider uplink assignment `allhost`)
- **nixpkgs:** `mine`
## Platform
| Component | Allocation |
|---|---|
| Virtualisation | KVM/QEMU guest |
| Compute | 2 vCPUs and 2 GiB RAM |
| Storage | 32 GiB virtio disk with separate ext4 filesystems for `/boot`, `/nix` and `/persist`; root is tmpfs |
## Role
- **Tailscale exit node** — logs into the headscale on [`britway`](britway.md)
@@ -20,14 +28,6 @@ narrower gateway role than [`britway`](britway.md) (no control plane, no BGP).
See the consolidated [network assignments](../networking.md#box-assignments) table (this box: `britnet`).
## Platform
| Component | Allocation |
|---|---|
| Virtualisation | KVM/QEMU guest |
| Compute | 2 vCPUs and 2 GiB RAM |
| Storage | 32 GiB virtio disk with separate ext4 filesystems for `/boot`, `/nix` and `/persist`; root is tmpfs |
## Networking
- The provider interface is renamed to `veth0` by MAC. Its IPv6 default gateway sits off-subnet, so
+8 -8
View File
@@ -7,6 +7,14 @@ control plane, a tailnet exit node, and the BGP speaker for AS211024.
- **Host:** VPS at Vultr (London, `lon1`)
- **nixpkgs:** `mine`
## Platform
| Component | Allocation |
|---|---|
| Virtualisation | Vultr VC2 virtual guest on a QEMU-compatible platform |
| Compute | 2 vCPUs and 2 GiB RAM |
| Storage | 65 GiB virtio disk with separate ext4 filesystems for `/boot`, `/nix` and `/persist`; root is tmpfs |
## Role
- **Headscale** — the self-hosted Tailscale control plane at `hs.nul.ie`; every other box's
@@ -27,14 +35,6 @@ control plane, a tailnet exit node, and the BGP speaker for AS211024.
See the consolidated [network assignments](../networking.md#box-assignments) table (this box: `britway`).
## Platform
| Component | Allocation |
|---|---|
| Virtualisation | Vultr VC2 virtual guest on a QEMU-compatible platform |
| Compute | 2 vCPUs and 2 GiB RAM |
| Storage | 65 GiB virtio disk with separate ext4 filesystems for `/boot`, `/nix` and `/persist`; root is tmpfs |
## Networking
- Two assignments: `vultr` on the provider interface `veth0` (renamed by MAC), and `as211024`
+12 -12
View File
@@ -8,6 +8,18 @@ everything at the colony site.
- **Host:** bare metal (this *is* the physical box)
- **nixpkgs:** `mine-stable`
## Hardware
| Component | Inventory |
|---|---|
| Platform | ASRock Rack X570D4U server board |
| CPU | AMD Ryzen 9 5950X (16 cores / 32 threads) |
| Memory | 128 GiB |
| NVMe storage | Three 2 TB Samsung SSD 980 PRO devices providing the NVMe-backed LVM thin pool and data LVs |
| Bulk storage | Three 12 TB WD120EDBZ disks and one 18 TB WD180EDGZ disk for the bulk LVM volumes |
| Boot | SanDisk USB device holding the EFI system partition |
| Network / management | Two Intel I210 Gigabit Ethernet controllers, one passed through to `estuary`; ASPEED BMC graphics and console |
## Role
Bare-metal AMD host. It does little application work itself — its job is to run
@@ -55,18 +67,6 @@ Netdata uses FreeIPMI while ignoring the VCCM sensor. The box also runs `smartd`
See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `colony`).
## Hardware
| Component | Inventory |
|---|---|
| Platform | ASRock Rack X570D4U server board |
| CPU | AMD Ryzen 9 5950X (16 cores / 32 threads) |
| Memory | 128 GiB |
| NVMe storage | Three 2 TB Samsung SSD 980 PRO devices providing the NVMe-backed LVM thin pool and data LVs |
| Bulk storage | Three 12 TB WD120EDBZ disks and one 18 TB WD180EDGZ disk for the bulk LVM volumes |
| Boot | SanDisk USB device holding the EFI system partition |
| Network / management | Two Intel I210 Gigabit Ethernet controllers, one passed through to `estuary`; ASPEED BMC graphics and console |
## Networking
- Two bridges: `base` (the colony base network, shared with `estuary`) and
+4 -2
View File
@@ -75,7 +75,8 @@ Besides the forwards, `extraRules` defines:
## DNS
Both halves are PowerDNS ([`dns.nix`](../../../nixos/boxes/colony/vms/estuary/dns.nix)).
Both halves are PowerDNS ([`dns.nix`](../../../nixos/boxes/colony/vms/estuary/dns.nix)). The live
forward and reverse records are listed in the generated [DNS reference](../../reference/dns.md).
### Authoritative
@@ -86,7 +87,8 @@ 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`).
- AXFR is allowed to HE.net's secondary and the trusted internal/site-egress sources used by the
generated DNS reference.
- `_acme-challenge` is a LUA `TXT` record answered from a file (DNS-01 issuance).
- Reached publicly via the NAT redirect of port 53 → 5353; the `base` side also
accepts DNS directly.
+6
View File
@@ -77,6 +77,12 @@ Management uses host `.15`: `192.168.64.15` on native/core as a backup,
route through its VIP; `untrusted` has no address. With `l2mtu 9214`, `vibe` can use the jumbo
`hi` network unlike `wave`.
Like the switches, `vibe` is **pure L2 and must not route**`ip-forward=no`, IPv6 `forward=no`,
`accept-router-advertisements=no`, `ra-lifetime=0`. RouterOS ships these forwarding-on and then
advertises itself as an IPv6 router; re-check after any upgrade. See
[switches.md#switches-must-not-route](switches.md#switches-must-not-route) for the rationale and
commands.
## wave (Cudy AX3000, OpenWrt)
Single-port AP, so the port is a VLAN **trunk** carrying management + both SSIDs.
+11 -11
View File
@@ -7,6 +7,17 @@ root storage on NVMe-oF volumes from `cellar`.
- **Host:** physical
- **nixpkgs:** `mine`
## Hardware
| Component | Inventory |
|---|---|
| Platform | ASUS ProArt X670E-CREATOR WIFI |
| CPU | AMD Ryzen 9 7950X (16 cores / 32 threads) |
| Memory | 64 GiB |
| Graphics | Integrated AMD Radeon graphics |
| Network | Mellanox ConnectX-4 100G, Aquantia AQC113CS 10G, Intel I225-V 2.5G and MediaTek MT7922 Wi-Fi 6E controllers |
| System storage | No local root disk; the box netboots and uses the SPDK NVMe-oF namespace exported by `cellar` |
## Role
### Desktop
@@ -38,17 +49,6 @@ a `drm-amd-display` flicker patch remains commented out.
See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `castle`).
## Hardware
| Component | Inventory |
|---|---|
| Platform | ASUS ProArt X670E-CREATOR WIFI |
| CPU | AMD Ryzen 9 7950X (16 cores / 32 threads) |
| Memory | 64 GiB |
| Graphics | Integrated AMD Radeon graphics |
| Network | Mellanox ConnectX-4 100G, Aquantia AQC113CS 10G, Intel I225-V 2.5G and MediaTek MT7922 Wi-Fi 6E controllers |
| System storage | No local root disk; the box netboots and uses the SPDK NVMe-oF namespace exported by `cellar` |
## Networking
- `et100g` (100G, MTU 9000) carries `lan-hi` (the `hi` assignment, also pinned by a kea
+12 -12
View File
@@ -8,18 +8,6 @@ SR-IOV VFs, PCI NVMe drives and LVM disks.
- **Host:** physical
- **nixpkgs:** `mine-stable`
## Role
- Home hypervisor: VMs are declared in `my.vms.instances`
([`palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix)); disks are LVs in
the `main` thin pool (`services.lvm.boot.thin.enable`).
- AMD box (`kvm-amd`, `amd_iommu=on`, microcode updates); the kernel is built with
`ACPI_APEI_PCIEAER`/`PCIEAER` for the PCIe passthrough work below.
## Network assignments
See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `palace`).
## Hardware
| Component | Inventory |
@@ -32,6 +20,18 @@ See the consolidated [network assignments](../../networking.md#box-assignments)
| NVMe storage | Three 2 TB Samsung NVMe devices passed through to `cellar`; SPDK combines them as the `NVMeRaid` RAID 0 device |
| Network / graphics | Mellanox ConnectX-4 100G adapter with four SR-IOV VFs, two Intel I211 Gigabit Ethernet controllers, and an AMD Radeon RX 550/560-family GPU |
## Role
- Home hypervisor: VMs are declared in `my.vms.instances`
([`palace/vms/default.nix`](../../../nixos/boxes/home/palace/vms/default.nix)); disks are LVs in
the `main` thin pool (`services.lvm.boot.thin.enable`).
- AMD box (`kvm-amd`, `amd_iommu=on`, microcode updates); the kernel is built with
`ACPI_APEI_PCIEAER`/`PCIEAER` for the PCIe passthrough work below.
## Network assignments
See the consolidated [network assignments](../../networking.md#box-assignments) table (this box: `palace`).
## Networking
100G `et100g` (mlx5, MTU 9000) uplinks to the `dave` switch and carries `lan-hi` (VLAN 100, the
+1 -1
View File
@@ -1,6 +1,6 @@
# sfh
"Services for home" — the NixOS container host for the home site. A VM on `palace` that netboots
"Shill from 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)
+17 -18
View File
@@ -8,6 +8,18 @@ redundant router pair with [`river`](river.md) and is dual-homed to both switche
- **Host:** physical
- **nixpkgs:** `mine`
## Hardware
| Component | Inventory |
|---|---|
| Platform | BROUNION R86S |
| CPU | Intel Celeron N5105 (4 cores / 4 threads) |
| Memory | 16 GiB |
| Storage | 512 GB Samsung SSD 970 PRO NVMe containing `/boot`, `/nix` and `/persist`; integrated 128 GB eMMC is present but is not used by the declared filesystems |
| Network | Three Intel `igc` interfaces and a dual-port Mellanox `mlx4_en` adapter; `wan`, `lan-jim` and `lan-dave` use three of these ports |
The platform configuration enables `kvm-intel`, `intel_iommu=on` and Intel microcode updates.
## Role
At `routing-common` index 1, `stream` normally holds the secondary position in the router pair.
@@ -61,28 +73,15 @@ box sets:
reaching the modem subnet (needed only because it shares `wan`; WAN egress is otherwise
accepted).
## Platform
### Hardware
| Component | Inventory |
|---|---|
| Platform | BROUNION R86S |
| CPU | Intel Celeron N5105 (4 cores / 4 threads) |
| Memory | 16 GiB |
| Storage | 512 GB Samsung SSD 970 PRO NVMe containing `/boot`, `/nix` and `/persist`; integrated 128 GB eMMC is present but is not used by the declared filesystems |
| Network | Three Intel `igc` interfaces and a dual-port Mellanox `mlx4_en` adapter; `wan`, `lan-jim` and `lan-dave` use three of these ports |
The platform configuration enables `kvm-intel`, `intel_iommu=on` and Intel microcode updates.
### Switching (RSTP)
## Switching (RSTP)
`stream` is dual-homed to both switches: `lan-jim` (igc) and `lan-dave` (mlx4_en), both MTU 9000,
are enslaved to the `lan` bridge with `STP=true`. [`routing-common/mstpd.nix`](../../../nixos/boxes/home/routing-common/mstpd.nix)
runs a patched `mstpd` and forces RSTP on `lan` once it's routable, so exactly one uplink carries
are enslaved to the `lan` bridge with `STP=true`. The explicit bridge-port costs prefer
`lan-dave` at 10 over `lan-jim` at 100. [`routing-common/mstpd.nix`](../../../nixos/boxes/home/routing-common/mstpd.nix)
runs a patched `mstpd` and forces RSTP on `lan` once it is configured, so exactly one uplink carries
traffic at a time. (The remaining NICs are renamed `et2`/`et5` and left unconfigured.)
### Deployment
## Deployment
`my.deploy.node.hostname` is currently commented out.
+30 -1
View File
@@ -86,7 +86,9 @@ from a box that does not depend on it, or power `castle` off cleanly first.
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.
L3 interface. jim and dave carry a static IPv4 and **global IPv6** address on `hi`/`lo` (plus a
static default route on each stack) purely for management — they are **pure L2, never routers**. See
[Switches must not route](#switches-must-not-route).
## The Digiweb WAN path (trunked VLAN 10 + PVID 140)
@@ -147,6 +149,33 @@ this is plain tagged bridging.
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.
## Switches must not route
jim and dave (and the `vibe` AP) are **pure L2** — river/stream do all routing. Their per-stack
management addresses and static default routes exist only so the boxes themselves can be reached and
reach out; they must **never** forward traffic or advertise themselves as routers. RouterOS defaults
work against this: `ip-forward` and IPv6 `forward` ship **on**, and with IPv6 forwarding on RouterOS
also emits Router Advertisements (`ra-lifetime=30m`) on every L3 interface — so a switch silently
becomes a competing IPv6 default router. This surfaced after the 7.18 → 7.23 upgrade, when clients
picked up dave/jim as default routers alongside river.
The required config on each RouterOS box:
```
/ip settings set ip-forward=no
/ipv6 settings set forward=no accept-router-advertisements=no
/ipv6 nd set [find] ra-lifetime=0
```
- `ip-forward=no` / `forward=no` — no L3 forwarding on either stack; IPv6 `forward=no` also stops RA
emission at the source.
- `accept-router-advertisements=no` — with forwarding off RouterOS would otherwise start *accepting*
RAs; this keeps the box on its deterministic **static** default route.
- `ra-lifetime=0` — belt-and-suspenders: even if forwarding is ever re-enabled the box advertises
router-lifetime 0 (i.e. "not a default router"). Setting it also emits a withdrawal RA that
actively clears the rogue default from clients (they otherwise cache it for up to ~30 min).
**After any RouterOS upgrade, re-check `/ip settings` and `/ipv6 settings`** — an upgrade can reset
these to the forwarding-on defaults. brian (UniFi) is not a RouterOS box and was not affected.
## 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
Generated
+116
View File
@@ -163,6 +163,29 @@
"type": "github"
}
},
"determinate-nix": {
"inputs": {
"flake-parts": "flake-parts",
"git-hooks-nix": "git-hooks-nix",
"nixpkgs": [
"nixpkgs-unstable"
],
"nixpkgs-23-11": "nixpkgs-23-11",
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1785428605,
"narHash": "sha256-wfaiSRLM1wDb4MV+NEzbyheK9Y03/oe56NR2I84UF7E=",
"rev": "0ff46631f69584c9f76792cae595ea253bd482c3",
"revCount": 26288,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/nix-src/3.21.9/019fb409-4d6e-7243-8a88-23ceee2520e9/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/DeterminateSystems/nix-src/%2A"
}
},
"devshell": {
"inputs": {
"flake-utils": "flake-utils",
@@ -256,6 +279,42 @@
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"determinate-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1748821116,
"narHash": "sha256-F82+gS044J1APL0n4hH50GYdPRv/5JWm34oCJYmVKdE=",
"rev": "49f0870db23e8c1ca0b5259734a02cd9e1e371a1",
"revCount": 377,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/hercules-ci/flake-parts/0.1.377%2Brev-49f0870db23e8c1ca0b5259734a02cd9e1e371a1/01972f28-554a-73f8-91f4-d488cc502f08/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/hercules-ci/flake-parts/0.1"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
@@ -460,6 +519,30 @@
"type": "github"
}
},
"git-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat_2",
"gitignore": [
"determinate-nix"
],
"nixpkgs": [
"determinate-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"revCount": 1026,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/cachix/git-hooks.nix/0.1.1026%2Brev-80479b6ec16fefd9c1db3ea13aeb038c60530f46/0196d79a-1b35-7b8e-a021-c894fb62163d/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/cachix/git-hooks.nix/0.1.941"
}
},
"harmonia": {
"inputs": {
"crane": "crane",
@@ -655,6 +738,22 @@
"type": "github"
}
},
"nixpkgs-23-11": {
"locked": {
"lastModified": 1717159533,
"narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
}
},
"nixpkgs-mine": {
"locked": {
"lastModified": 1781356656,
@@ -687,6 +786,22 @@
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1780902259,
@@ -847,6 +962,7 @@
"borgthin": "borgthin",
"copyparty": "copyparty",
"deploy-rs": "deploy-rs",
"determinate-nix": "determinate-nix",
"devshell": "devshell_3",
"flake-utils": "flake-utils_6",
"harmonia": "harmonia",
+44 -1
View File
@@ -1,6 +1,19 @@
{
description = "System configs";
# Offer our Harmonia cache when building the flake itself, so `nix develop` / `nix build` don't
# rebuild from source. Nix reads `nixConfig` before the flake evaluates and rejects any computed
# value (imports/thunks), so these must stay literal — keep them in sync with `lib.my.c.nix.cache`.
# Consumers must trust these (accept-flake-config / a trusted user) for them to take effect.
nixConfig = {
extra-substituters = [
"https://nix-cache.nul.ie"
];
extra-trusted-public-keys = [
"nix-cache.nul.ie-1:BzH5yMfF4HbzY1C977XzOxoPhEc9Zbu39ftPkUbH+m4="
];
};
inputs = {
flake-utils.url = "github:numtide/flake-utils";
# libnet.url = "github:reo101/nix-lib-net";
@@ -21,6 +34,12 @@
home-manager-stable.url = "home-manager/release-26.05";
home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
# Determinate Nix, used as the common Nix implementation across systems, homes, the devshell and
# CI (see lib.my.c.nix). We build it ourselves against our pinned nixpkgs (FlakeHub's cache needs
# auth), so it flows through our own Harmonia cache like everything else.
determinate-nix.url = "https://flakehub.com/f/DeterminateSystems/nix-src/*";
determinate-nix.inputs.nixpkgs.follows = "nixpkgs-unstable";
# Stuff used by the flake for build / deployment
# ragenix.url = "github:yaxitech/ragenix";
ragenix.url = "github:devplayer0/ragenix/add-rekey-one-flag";
@@ -83,6 +102,22 @@
};
pkgsLibOverlay = final: prev: { lib = prev.lib.extend libOverlay; };
myPkgsOverlay = final: prev: import ./pkgs { lib = final.lib; pkgs = prev; };
# Exposes Determinate Nix under a stable attr name so systems, homes and the devshell all
# resolve the exact same package (referenced as `pkgs'.mine.determinate-nix` in configs).
# `nix-util`'s `readLinkAt.works` unit test creates PATH_MAX-length symlinks, which our CI
# runner's XFS-backed build filesystem rejects (XFS hard-caps symlink targets at 1024 bytes).
# Skip just that test via gtest's GTEST_FILTER so the rest of the suite still gates the build.
determinateOverlay = final: prev: {
determinate-nix =
(inputs.determinate-nix.packages.${prev.stdenv.hostPlatform.system}.default).overrideAttrs (o: {
checkInputs = map
(drv:
if (drv.name or "") == "nix-util-tests-run"
then drv.overrideAttrs (_: { GTEST_FILTER = "-readLinkAt.*"; })
else drv)
o.checkInputs;
});
};
# Override the flake-level lib since we're going to use it for non-config specific stuff
pkgsFlakes = mapAttrs (_: pkgsFlake: pkgsFlake // { lib = pkgsFlake.lib.extend libOverlay; }) {
@@ -111,6 +146,7 @@
pkgsLibOverlay
myPkgsOverlay
determinateOverlay
inputs.devshell.overlays.default
inputs.ragenix.overlays.default
inputs.deploy-rs.overlays.default
@@ -126,6 +162,7 @@
pkgsLibOverlay
myPkgsOverlay
determinateOverlay
];
config = {
@@ -187,7 +224,13 @@
nixosModules = nixfiles.config.nixos.modules;
homeModules = nixfiles.config.home-manager.modules;
nixosConfigurations = mapAttrs (_: s: s.rendered) nixfiles.config.nixos.systems;
# Containers and the installer override `rendered` with a bare `extendModules` config
# (`my.asContainer` / `my.asISO`) that lacks the `pkgs`/`lib` attrs `eval-config` exposes on a
# normal system. Determinate Nix's flake schemas read `machine.pkgs.stdenv.system` for every
# `nixosConfigurations` entry, so re-attach them from the full system eval (`configuration`).
nixosConfigurations = mapAttrs
(_: s: s.rendered // { inherit (s.configuration) pkgs lib; })
nixfiles.config.nixos.systems;
homeConfigurations = mapAttrs (_: s: s.configuration) nixfiles.config.home-manager.homes;
deploy = nixfiles.config.deploy-rs.rendered;
+4 -5
View File
@@ -2,7 +2,7 @@
let
inherit (builtins) listToAttrs mapAttrs readFile;
inherit (lib)
optionalString nameValuePair concatMapStrings concatStringsSep optionalAttrs versionAtLeast
optionalString nameValuePair concatMapStrings concatStringsSep optionalAttrs
mapAttrsToList mkMerge mkIf mkDefault mkOption;
inherit (lib.hm) dag;
inherit (lib.my) mkOpt' dummyOption;
@@ -50,8 +50,7 @@ in
};
nix = {
package = mkIf (!(versionAtLeast config.home.stateVersion "22.11")) pkgs.nix;
settings = with lib.my.c.nix; {
settings = with lib.my.c.nix; determinateSettings // {
experimental-features = [ "nix-command" "flakes" "ca-derivations" ];
max-jobs = mkDefault "auto";
@@ -257,13 +256,13 @@ in
ssh.authKeys.files = [ lib.my.c.sshKeyFiles.me ];
};
nix.package = mkIf (versionAtLeast config.home.stateVersion "22.05") pkgs.nix;
nix.package = pkgs'.mine.determinate-nix;
fonts.fontconfig.enable = true;
home = {
packages = with pkgs; [
pkgs'.mine.nix
pkgs'.mine.determinate-nix
];
# Without this, we are at the mercy of whatever version of nix is in $PATH...
+19
View File
@@ -111,6 +111,13 @@ rec {
extra-trusted-public-keys = ${concatStringsSep " " keys}
'';
};
# Determinate-specific settings enabling its performance features. Only understood by the
# Determinate Nix binary, so they must not be emitted for a base-Nix package.
determinateSettings = {
lazy-trees = true;
eval-cores = 0;
};
};
pubDomain = "nul.ie";
@@ -307,6 +314,8 @@ rec {
home = rec {
domain = "h.${pubDomain}";
# Search domains advertised to clients (radvd DNSSL / kea domain-search)
searchDomains = [ domain "dyn.${domain}" colony.domain britway.domain ];
vlans = {
hi = 100;
lo = 110;
@@ -383,6 +392,16 @@ rec {
};
};
# networkConfig fragment anchoring a VLAN client's DNS on the router pair's
# VRRP VIPs rather than the RA RDNSS. v6 addresses here are RA/token-derived,
# so when RA is absent (e.g. v6 disabled) there is no v6 and no RDNSS at all;
# the always-present static v4 VIP keeps name resolution working, with the v6
# VIP as a bonus when v6 is up. Merge into the VLAN network's networkConfig.
vlanDns = vlan: {
DNS = [ vips.${vlan}.v4 vips.${vlan}.v6 ];
Domains = searchDomains;
};
roceBootModules = [ "ib_core" "ib_uverbs" "mlx5_core" "mlx5_ib" ];
};
+5 -1
View File
@@ -107,7 +107,7 @@ rec {
then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
else showWarnings config.warnings res;
homeStateVersion' = hmBranch: (if (hmBranch == "stable" || hmBranch == "mine-stable") then "22.11" else "23.05");
homeStateVersion' = hmBranch: "23.05";
homeStateVersion = hmBranch: {
# The flake passes a default setting, but we don't care about that
home.stateVersion = mkForce (homeStateVersion' hmBranch);
@@ -149,6 +149,10 @@ rec {
(optional (a.ipv6.gateway != null) a.ipv6.gateway);
networkConfig = {
IPv6AcceptRA = a.ipv6.gateway == null || a.ipv6.iid != null;
# These are servers: temporary (privacy) addresses only rotate our stable source
# address out from under long-lived connected sockets (e.g. nginx's resolver, which
# wedges permanently when the address it bound to expires).
IPv6PrivacyExtensions = "no";
# NOTE: LLDP emission / reception is ignored on bridge interfaces
LLDP = true;
EmitLLDP = "customer-bridge";
+5 -1
View File
@@ -114,8 +114,12 @@ in
];
also-notify = [ "127.0.0.1" ];
allow-axfr-ips = [
"127.0.0.0/8" "::1/128"
"216.218.133.2" "2001:470:600::2"
];
]
++ lib.my.c.home.routersPubV4
++ lib.my.c.as211024.trusted.v4
++ lib.my.c.as211024.trusted.v6;
enable-lua-records = true;
#loglevel = 7;
#log-dns-queries = true;
+4 -2
View File
@@ -38,6 +38,8 @@ in
let
inherit (lib) mkMerge;
inherit (lib.my) networkdAssignment;
podmanSubnet = "10.88.0.0/16";
in
{
imports = [
@@ -158,7 +160,7 @@ in
oci-containers = {
backend = "podman";
};
containers.containersConf.settings.network.default_subnet = "10.88.0.0/16";
containers.containersConf.settings.network.default_subnet = podmanSubnet;
};
systemd.network = {
@@ -195,7 +197,7 @@ in
extraRules = ''
table inet filter {
chain forward {
ip saddr 10.88.0.0/16 accept
ip saddr ${podmanSubnet} accept
}
}
'';
@@ -1,7 +1,14 @@
{ lib, pkgs, config, ... }:
let
inherit (lib) mkForce;
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
# The podman bridge gateway (first host of the default subnet); job
# containers reach the runner's artifact cache server here, through a single
# fixed port opened in the firewall below.
podmanGateway = net.cidr.host 1 config.virtualisation.containers.containersConf.settings.network.default_subnet;
cachePort = 34567;
in
{
config = {
@@ -34,6 +41,11 @@ in
cache = {
enabled = true;
dir = "/var/cache/gitea-runner";
# Announce the podman bridge gateway rather than let act_runner
# autodetect the box's outbound address, which containers can't
# route back to.
host = podmanGateway;
port = cachePort;
};
};
};
@@ -73,6 +85,15 @@ in
group = "gitea-runner";
};
};
# Let job containers reach the runner's artifact cache server on the host.
firewall.extraRules = ''
table inet filter {
chain input {
iifname "podman0" tcp dport ${toString cachePort} accept
}
}
'';
};
};
}
@@ -48,8 +48,9 @@ in
"s3-secret-key.txt"
])
(_: with config.services.mastodon; {
owner = user;
inherit group;
# user doesn't exist any more, so this breaks on deploy
# owner = user;
# inherit group;
})) // {
"toot/pds.env" = {
owner = "pds";
+7 -3
View File
@@ -2,7 +2,7 @@
let
inherit (lib.my) net;
inherit (lib.my.c) networkd;
inherit (lib.my.c.home) domain vlans prefixes vips roceBootModules;
inherit (lib.my.c.home) domain vlans prefixes vips vlanDns roceBootModules;
in
{
nixos.systems.castle = {
@@ -189,8 +189,12 @@ in
};
"40-lan-hi" = mkMerge [
(networkdAssignment "lan-hi" assignments.hi)
# So we don't drop the IP we use to connect to NVMe-oF!
{ networkConfig.KeepConfiguration = "static"; }
{
networkConfig = vlanDns "hi" // {
# So we don't drop the IP we use to connect to NVMe-oF!
KeepConfiguration = "static";
};
}
];
"45-lan-lo" = {
matchConfig.Name = "lan-lo";
+5 -2
View File
@@ -2,7 +2,7 @@
let
inherit (lib.my) net mkVLAN;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain vlans prefixes vips hiMTU;
inherit (lib.my.c.home) domain vlans prefixes vips vlanDns hiMTU;
in
{
imports = [ ./vms ];
@@ -203,7 +203,10 @@ in
MACAddress=52:54:00:90:34:95
'';
};
"60-lan-hi" = networkdAssignment "lan-hi" assignments.hi;
"60-lan-hi" = mkMerge [
(networkdAssignment "lan-hi" assignments.hi)
{ networkConfig = vlanDns "hi"; }
];
"50-lan-core-phy" = {
matchConfig.Name = "lan-core-phy";
@@ -2,7 +2,7 @@
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
inherit (lib.my.c.home) domain prefixes vips vlanDns hiMTU;
in
{
nixos.systems.cellar = {
@@ -79,7 +79,10 @@ in
};
networks = {
"80-lan-hi" = networkdAssignment "lan-hi" assignments.hi;
"80-lan-hi" = mkMerge [
(networkdAssignment "lan-hi" assignments.hi)
{ networkConfig = vlanDns "hi"; }
];
};
};
@@ -2,7 +2,7 @@
let
inherit (lib.my) net;
inherit (lib.my.c) pubDomain;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
inherit (lib.my.c.home) domain prefixes vips vlanDns hiMTU;
in
{
nixos.systems.hass = { config, ... }: {
@@ -82,7 +82,10 @@ in
systemd = {
network.networks = {
"80-container-host0" = networkdAssignment "host0" assignments.hi;
"80-container-host0" = mkMerge [
(networkdAssignment "host0" assignments.hi)
{ networkConfig = vlanDns "hi"; }
];
"80-container-lan-lo" = networkdAssignment "lan-lo" assignments.lo;
};
};
@@ -1,7 +1,7 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
inherit (lib.my.c.home) domain prefixes vips vlanDns hiMTU;
in
{
nixos.systems.unifi = { config, ... }: {
@@ -58,7 +58,10 @@ in
systemd = {
network.networks = {
"80-container-host0" = networkdAssignment "host0" assignments.hi;
"80-container-host0" = mkMerge [
(networkdAssignment "host0" assignments.hi)
{ networkConfig = vlanDns "hi"; }
];
"80-lan-core" = networkdAssignment "lan-core" assignments.core;
};
};
+5 -3
View File
@@ -1,7 +1,7 @@
{ lib, ... }:
let
inherit (lib.my) net;
inherit (lib.my.c.home) domain prefixes vips hiMTU roceBootModules;
inherit (lib.my.c.home) domain prefixes vips vlanDns hiMTU roceBootModules;
in
{
imports = [ ./containers ];
@@ -134,8 +134,10 @@ in
networks = {
"30-lan-hi" = mkMerge [
(networkdAssignment "lan-hi" assignments.hi)
# So we don't drop the IP we use to connect to NVMe-oF!
{ networkConfig.KeepConfiguration = "static"; }
{
# So we don't drop the IP we use to connect to NVMe-oF!
networkConfig = vlanDns "hi" // { KeepConfiguration = "static"; };
}
];
"30-lan-hi-ctrs" = {
matchConfig.Name = "lan-hi-ctrs";
+6
View File
@@ -170,6 +170,12 @@ in
"0.0.0.0:5353" "[::]:5353"
];
also-notify = [ "127.0.0.1" ];
allow-axfr-ips = [
"127.0.0.0/8" "::1/128"
allAssignments.estuary.internal.ipv4.address
]
++ lib.my.c.as211024.trusted.v4
++ lib.my.c.as211024.trusted.v6;
enable-lua-records = true;
# loglevel = 7;
# log-dns-queries = true;
+3 -3
View File
@@ -1,8 +1,8 @@
index: { lib, pkgs, config, assignments, allAssignments, ... }:
let
inherit (lib) mkForce;
inherit (lib) mkForce concatStringsSep;
inherit (lib.my) net netbootKeaClientClasses;
inherit (lib.my.c.home) domain prefixes vips hiMTU;
inherit (lib.my.c.home) domain searchDomains prefixes vips hiMTU;
dns-servers = [
{
@@ -59,7 +59,7 @@ in
}
{
name = "domain-search";
data = "${domain}, dyn.${domain}, ${lib.my.c.colony.domain}, ${lib.my.c.britway.domain}";
data = concatStringsSep ", " searchDomains;
always-send = true;
}
];
+1 -1
View File
@@ -24,7 +24,7 @@ in
services = {
networkd-dispatcher.rules = {
configure-mstpd = {
onState = [ "routable" ];
onState = [ "configured" ];
script = ''
#!${pkgs.runtimeShell}
if [ "$IFACE" = "lan" ]; then
+3 -3
View File
@@ -1,8 +1,8 @@
index: { lib, pkgs, ... }:
let
inherit (lib) mkForce concatMapStringsSep;
inherit (lib) mkForce concatMapStringsSep concatStringsSep;
inherit (lib.my) net;
inherit (lib.my.c.home) domain prefixes vips;
inherit (lib.my.c.home) domain searchDomains prefixes vips;
# untrusted uses external (Cloudflare) resolvers, matching the v4 kea config;
# trusted VLANs use the internal recursor via its floating VRRP VIP
@@ -18,7 +18,7 @@ let
AdvLinkMTU ${toString prefixes."${name}".mtu};
prefix ${prefixes."${name}".v6} {};
RDNSS ${rdnss name} {};
DNSSL ${domain} dyn.${domain} ${lib.my.c.colony.domain} ${lib.my.c.britway.domain} {};
DNSSL ${concatStringsSep " " searchDomains} {};
};
'';
in
+3 -4
View File
@@ -115,10 +115,7 @@
Name = "lan";
Kind = "bridge";
};
extraConfig = ''
[Bridge]
STP=true
'';
bridgeConfig.STP = true;
};
};
links = {
@@ -175,10 +172,12 @@
"50-lan-jim" = {
matchConfig.Name = "lan-jim";
networkConfig.Bridge = "lan";
bridgeConfig.Cost = 100;
};
"50-lan-dave" = {
matchConfig.Name = "lan-dave";
networkConfig.Bridge = "lan";
bridgeConfig.Cost = 10;
};
"50-wan-ifb" = {
+2 -2
View File
@@ -50,9 +50,9 @@ in
};
nix = {
package = pkgs'.mine.nix;
package = pkgs'.mine.determinate-nix;
channel.enable = false;
settings = with lib.my.c.nix; {
settings = with lib.my.c.nix; determinateSettings // {
trusted-users = [ "@wheel" ];
experimental-features = [ "nix-command" "flakes" "ca-derivations" ];
extra-substituters = cache.substituters;
+8
View File
@@ -19,4 +19,12 @@ in
update-docs-options = pkgs.writeShellScriptBin "update-docs-options" ''
exec ${pkgs.python3}/bin/python3 ${../ci/update-docs-options.py} "$@"
'';
update-docs-dns =
let
python = pkgs.python3.withPackages (ps: [ ps.dnspython ]);
in
pkgs.writeShellScriptBin "update-docs-dns" ''
exec ${python}/bin/python3 ${../ci/update-docs-dns.py} "$@"
'';
}