Resolve
sanctum-endpoints discovers live addresses and writes them to a generated file every service reads. The map is never hand-edited.
A hardcoded IP address is a time bomb with a long fuse. It works perfectly until the day the LAN shifts subnets, the router is swapped, or a Tailscale node re-authenticates with a new address — and then a dozen scripts fail at once, quietly, at 2 a.m. Sanctum has been burned by this enough times that it now treats a literal IP in code the way it treats a hardcoded password.
The doctrine is one sentence: every address a Sanctum service contacts is resolved at runtime from a single source of truth — never written as a literal in code or config.
192.168.1.x to a new subnet. Screen-time enforcement contacted a hardcoded gateway and silently went offline for 24+ hours. Nobody noticed until a kid did.10.0.0.0/24. Overnight the Mac Mini’s LAN IP drifted again (10.0.0.248 → 10.0.0.10). Every 192.168.1.x literal was already wrong; cert SANs asserted addresses the servers no longer bound to; a mDNS proxy was advertising a dead host. In both cases the failure was invisible because the literal was plausible — a real IP, just the wrong one. The resolver makes the wrong-but-plausible state impossible.
Resolve
sanctum-endpoints discovers live addresses and writes them to a generated file every service reads. The map is never hand-edited.
Enforce
The no-hardcoded-ip lint fails any commit that reintroduces a literal. Discovery without enforcement just rots again.
sanctum-endpoints~/.sanctum/bin/sanctum-endpoints reads instance.yaml for names and virtual anchors only, then resolves them to live addresses. Every source is dynamic:
| Address | How it’s resolved |
|---|---|
| Own LAN IP | the default-route interface (route get default → ipconfig getifaddr) — never a hardcoded interface |
| Firewalla box | the bridge’s own discovery endpoint (GET :1984/health → .discovery.ip) |
| Tailscale peers | tailscale status --json, matched by MagicDNS DNSName stem (not the messy display name), gated on Online — an offline node resolves to null, not a stale address |
| Loopback / co-resident services | 127.0.0.1 (stable by definition) |
| vmnet bridge / VM | the 10.10.10.x anchors from instance.yaml — addresses we assign on a private vmnet, so they never drift |
It writes two artifacts, atomically and content-hash idempotent (identical inputs produce a byte-identical file, so re-running never churns):
~/.sanctum/endpoints.json — structured, for Python / Rust / TypeScript consumers.~/.sanctum/endpoints.env — flat KEY=value, sourced by shell.Commands: generate (default), check (exit 1 on drift vs live), get <KEY>. A launchd job regenerates on network change (WatchPaths) and every ten minutes, so the map self-heals.
# shell — source the helper, resolve with a stable-anchor fallbacksource ~/.sanctum/lib/config.shVM=$(sanctum_endpoint SANCTUM_VM_BRIDGE) # 10.10.10.10 (ip-allow: doc example output)TS=$(sanctum_endpoint SANCTUM_MANOIR_TS) # 100.x, currentimport json, osep = json.load(open(os.path.expanduser("~/.sanctum/endpoints.json")))manoir_lan = ep["hosts"]["manoir"]["lan_ip"]Plists can’t call a resolver from a string argument, so a launch wrapper sources endpoints.env and execs the binary — the plist itself carries no literal. Agent prompts get a generated <!-- AUTO:network --> block so the council never reasons on a stale address.
no-hardcoded-ip~/.sanctum/sentinels/no-hardcoded-ip.py scans tracked files for infra-IP and MagicDNS literals (10.0.0.x, legacy 192.168.x, vmnet 10.10.10.x, Tailscale 100.64–127.x, *.tail*.ts.net) and exits non-zero on any violation, so it can gate pre-commit and CI. It is the half that makes this productized rather than a one-time cleanup.
What it does not flag, because those are correct by construction:
instance.yaml) and the generated endpoints.*ip-allow comment — or, for declarative formats like systemd units that forbid trailing comments, ip-allow on the preceding line<!-- AUTO:network --> block (machine-maintained from the SoT)A justified ip-allow (a stable vmnet anchor used as a documented example, a dated historical runbook) is legitimate. A stale 192.168.x never is — the lint treats those as drift, not exceptions.
Roughly 920 literal sites across the Sanctum repos were moved onto the resolver in a single pass, dispatched as parallel review-and-fix agents — one per file, each syntax-checked and reverted if it broke. Drift-prone LAN and Tailscale literals became resolver lookups; stable vmnet anchors became lookups-with-fallback or justified ip-allows; dated docs and runbooks were ip-allow’d rather than rewritten. No services were restarted during the sweep; the changes take effect the next time each consumer runs.
instance.yaml describes.