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.
This commit is contained in:
2026-08-02 01:07:27 +01:00
parent 8f9ca5e1c4
commit 88d0d19239
9 changed files with 714 additions and 16 deletions
+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
+8 -6
View File
@@ -66,8 +66,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`.
@@ -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
+347
View File
@@ -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"^<!--\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)
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())
+1
View File
@@ -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.
+12 -5
View File
@@ -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 `<!-- 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.
+3 -1
View File
@@ -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`
+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 -->
+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.
+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} "$@"
'';
}