No description
Find a file
Natalie af54b6742d feat(@tools): unify net-tools agent across all fleet nodes
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 21:04:36 -07:00
bin feat(@tools/net-tools): clarify naming rules and auto-generated configs 2026-06-09 20:06:00 -07:00
data feat(@tools/net-tools): add dynamic lan host ip discovery 2026-06-09 19:59:24 -07:00
docs feat(@tools/net-tools): clarify naming rules and auto-generated configs 2026-06-09 20:06:00 -07:00
smart-lan-router feat(@tools): unify net-tools agent across all fleet nodes 2026-06-09 21:04:36 -07:00
.gitignore feat(@tools/net-tools): add mesh/lan tooling with host renderers 2026-06-09 19:53:08 -07:00
install.sh feat(@tools/net-tools): add mesh/lan tooling with host renderers 2026-06-09 19:53:08 -07:00
README.md feat(@tools/net-tools): clarify naming rules and auto-generated configs 2026-06-09 20:06:00 -07:00

net-tools

Mesh/LAN tooling for the four-host wg1 mesh + home LAN, built around one source of truth (data/mesh-hosts.json).

Components:

  • bin/ — renderers that project the source of truth onto each device: host-apply (ssh config), mesh-hosts-render (/etc/hosts), wg-dns-sync (apricot's mesh dnsmasq).
  • smart-lan-router/ — the policy-routing daemon that makes the LAN "smart": the laptop automatically uses the 5ms LAN path to home hosts when home, and the WireGuard tunnel when away — identity-gated so it never routes to a stranger at the same RFC1918 IP. (The home gateway is a dumb Xfinity box with no API; the intelligence lives here, on the client.)

Everything that needs a host address, MAC, or identity probe derives from one file: data/mesh-hosts.json. Never hardcode a mesh IP, MAC, or identity URL anywhere else — add it here and regenerate.

The four hosts — fruit family encodes machine class

Class Canonical Old alias LAN WG mesh Public
GPU compute (stone fruit) apricot 10.0.0.116 10.9.0.2
CPU / storage (pome) pear black 10.0.0.11 10.9.0.4
laptop (vegetable) fennel plum roams 10.9.0.3
cloud hub (citrus) yuzu vps,quinn-vps 10.9.0.1 89.127.233.145

Names are mid-migration (alias-first): the source of truth declares the fruit name canonical with the old name as an alias, and every renderer emits both, so pear.wg and black.wg resolve during the transition. Live infra (forge URL, NFS, ssh) still uses old names until the gated cutovers land — see docs/topology.md.

Naming: one rule per suffix

  • bare <host> and <host>.lan → the host's current LAN IP (discovered, tracks DHCP drift). Direct at home; when away the daemon routes the LAN /24 through the tunnel, so the same name still works. This is the everyday handle: ssh apricot, ping pear.
  • <host>.wg → mesh IP (10.9.0.x). The explicit tunnel path — use to force the mesh or to reach hosts with no LAN leg (fennel.wg, yuzu.wg; their bare names also point here).
  • service vhosts (quinn.apricot.lan, forge.black.lan, …) → declared in mesh-hosts.json services, rendered at the hosting host's current IP.

(The old *.local scheme is retired — platform moved to real .com domains, infra to .lan. net-tools carries no .local records.)

The program owns the names — never hand-edit

/etc/hosts fleet/service records and the fleet block in ~/.ssh/config are generated. Hand-edits go stale on the next DHCP drift and are overwritten on the next sync. To change anything: edit data/mesh-hosts.json (or just wait — IP changes are discovered automatically) and let the renderers run. On install, mesh-hosts-render also adopts loose hand-maintained lines for any name it manages (it removes them; its block supersedes them).

Tools

Tool Runs on What it does
bin/host-apply every host Renders this device's view of the fleet. Detects which host it is, then writes a managed ssh-config block (~/.ssh/config) with per-vantage HostNames: public > .lan (if this host reaches the LAN) > .wg. --whoami/--ssh-print/--ssh-diff/--ssh-apply. The hosts leg is mesh-hosts-render.
smart-lan-router/smart-lan-router.py fennel (laptop) LaunchDaemon, two jobs. (1) Route: detect HOME (default gateway's MAC == lan.gateway_mac) → route 10.0.0.0/24 via the LAN interface (direct ~5ms); AWAY → via the wg mesh. (2) Name-sync: at home, discover each LAN host's current IP by MAC via ARP (stable MAC, drifting DHCP IP), write data/lan-state.json, and regenerate /etc/hosts (mesh-hosts-render) + the console user's ~/.ssh/config (host-apply). So ssh apricot/apricot.lan follow the host wherever DHCP puts it — no reservations. --status to inspect. Supersedes the old per-host /32 pinner, the wg-route-watchdog, and setup-lan-dns.
bin/wg-dns-sync apricot Renders mesh-hosts.json/etc/dnsmasq.d/wg-mesh.conf (host .wg + .lan records on 10.9.0.2:53, for wg clients with DNS=10.9.0.2). Idempotent; --dry-run.
bin/mesh-hosts-render every host Renders the fleet /etc/hosts block (bare/.lan at current IPs, .wg, service vhosts) and splices it at the top of /etc/hosts, adopting any loose lines it supersedes. Idempotent. --print/--diff/--install.
smart-lan-router/ fennel com.lilith.smart-lan-router.plist (launchd) + install-smart-router.sh (installs it, retires the old loose copies).

All tools locate data/mesh-hosts.json by resolving their own symlink chain and walking up to the repo, so they work whether run from the repo or a PATH symlink.

Install

./install.sh                      # symlink bin/* into ~/bin or ~/.local/bin
sudo smart-lan-router/install-smart-router.sh   # install + start the LaunchDaemon (fennel only)

Changing things

Want to… Do
add/rename a host, change a MAC, add a service vhost edit data/mesh-hosts.json — the daemon re-reads it each cycle; renderers pick it up on the next sync
react to a host changing DHCP IP nothing — the daemon discovers it by MAC and regenerates /etc/hosts + ssh automatically
force a regen now sudo bin/mesh-hosts-render --install and bin/host-apply --ssh-apply
apricot mesh DNS (phones) sudo wg-dns-sync on apricot

Never hand-edit /etc/dnsmasq.d/wg-mesh.conf, the managed /etc/hosts records, or the fleet block in ~/.ssh/config — all generated, all overwritten.

Status

Consolidates previously-scattered tooling (the session-tools generators, the magic-civilization/scripts/lan resolver scripts, and the loose ~/bin/smart-lan-router.py daemon) into one repo. Pending gated cutovers (apricot DNS, the fleet rename, retiring originals) are in docs/topology.md.