|
|
||
|---|---|---|
| bin | ||
| data | ||
| docs | ||
| smart-lan-router | ||
| .gitignore | ||
| install.sh | ||
| README.md | ||
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/24through 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 inmesh-hosts.jsonservices, 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.