diff --git a/.gitea/workflows/update-docs.yaml b/.gitea/workflows/update-docs.yaml index 6c8b5bf..32b7cde 100644 --- a/.gitea/workflows/update-docs.yaml +++ b/.gitea/workflows/update-docs.yaml @@ -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 diff --git a/AGENTS.md b/AGENTS.md index dcbf607..65afaaa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,8 +66,8 @@ Use the narrowest relevant evaluation while iterating: `check-system ` 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 @ A`. @@ -249,10 +249,12 @@ gets 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 diff --git a/ci/update-docs-dns.py b/ci/update-docs-dns.py new file mode 100644 index 0000000..621a037 --- /dev/null +++ b/ci/update-docs-dns.py @@ -0,0 +1,347 @@ +#!/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]) -> list[Record]: + errors = [] + for server in servers: + 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"^$") + 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() != "": + end += 1 + if end >= len(lines): + raise RuntimeError(f"missing for {domain}") + + output.extend( + [ + lines[i], + "", + *rendered[domain], + "", + ] + ) + 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) + transferred = [ + (domain, transfer_domain(args.port, domain, nameservers[domain])) + 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()) diff --git a/docs/README.md b/docs/README.md index 3993435..87d2850 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,7 @@ 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. +- [`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. diff --git a/docs/deployment.md b/docs/deployment.md index 0417c71..416f1ff 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -170,14 +170,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 +196,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 `` 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. diff --git a/docs/networking.md b/docs/networking.md index fa1bc49..9239df6 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -327,7 +327,9 @@ and keepalived's `notify_master`/`notify_backup` hooks ensure that only the mast [`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` diff --git a/docs/reference/dns.md b/docs/reference/dns.md new file mode 100644 index 0000000..9c27a7c --- /dev/null +++ b/docs/reference/dns.md @@ -0,0 +1,319 @@ +# DNS records + +The tables on this page are generated from live authoritative zone transfers by +`nix run .#update-docs-dns -- `; 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` + + + +| 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` | + + +### IPv4 reverse zone: `100.10.in-addr.arpa` + + + +| 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.` | + + +### IPv6 reverse zone: `2.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa` + + + +| 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.` | + + +## 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` + + + +| 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` | + + +### IPv4 reverse zone: `168.192.in-addr.arpa` + + + +| 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.` | + + +### IPv6 reverse zone: `0.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa` + + + +| 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.` | + diff --git a/docs/sites/colony/estuary.md b/docs/sites/colony/estuary.md index 7acdfd7..cf3b1f4 100644 --- a/docs/sites/colony/estuary.md +++ b/docs/sites/colony/estuary.md @@ -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. diff --git a/pkgs/default.nix b/pkgs/default.nix index c16576c..7866d60 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -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} "$@" + ''; }