feat(@tools/net-tools): ✨ clarify naming rules and auto-generated configs
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
b8d41a9509
commit
98a0df2f41
3 changed files with 50 additions and 19 deletions
43
README.md
43
README.md
|
|
@ -32,15 +32,29 @@ 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`](docs/topology.md#fleet-rename).
|
||||
|
||||
## Naming: two views, one rule
|
||||
## Naming: one rule per suffix
|
||||
|
||||
The suffix is authoritative — a name is never ambiguous:
|
||||
- **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.
|
||||
|
||||
- **`<host>.wg`** → mesh IP (`10.9.0.x`). Works anywhere the tunnel is up.
|
||||
- **`<host>.lan`** → LAN IP (`10.0.0.x`). Home network only.
|
||||
(The old `*.local` scheme is **retired** — platform moved to real `.com` domains,
|
||||
infra to `.lan`. net-tools carries no `.local` records.)
|
||||
|
||||
(The old `*.local` scheme is **retired** — the platform moved to real `.com`
|
||||
domains and 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
|
||||
|
||||
|
|
@ -49,7 +63,7 @@ domains and infra to `.lan`. net-tools carries no `.local` records.)
|
|||
| `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 `HostName`s: `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` | any (esp. **fennel**) | Renders a static `/etc/hosts` block for roaming clients. `--print`/`--diff`/`--install`. |
|
||||
| `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
|
||||
|
|
@ -62,14 +76,17 @@ walking up to the repo, so they work whether run from the repo or a PATH symlink
|
|||
sudo smart-lan-router/install-smart-router.sh # install + start the LaunchDaemon (fennel only)
|
||||
```
|
||||
|
||||
## Changing addresses / hosts
|
||||
## Changing things
|
||||
|
||||
1. Edit [`data/mesh-hosts.json`](data/mesh-hosts.json).
|
||||
2. apricot: `sudo wg-dns-sync` · roaming clients: `sudo mesh-hosts-render --install`.
|
||||
3. The daemon re-reads the file each cycle — no restart needed.
|
||||
| Want to… | Do |
|
||||
|----------|----|
|
||||
| add/rename a host, change a MAC, add a service vhost | edit [`data/mesh-hosts.json`](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` or the managed `/etc/hosts` block —
|
||||
both are generated and overwritten on the next run.
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
# It writes a single managed block (Host <name> <aliases> → HostName/User) to the
|
||||
# invoking user's ~/.ssh/config, placed at the TOP so it wins first-match over
|
||||
# any hand-maintained stanzas. Old names are kept as Host aliases (alias-first).
|
||||
# Each stanza sets `CheckHostIP no` (host keys are stable, IPs drift — trust is
|
||||
# keyed on the name, so a DHCP move doesn't trip verification) and
|
||||
# `StrictHostKeyChecking accept-new` (TOFU within the private fleet, so
|
||||
# non-interactive/BatchMode hops to a freshly-moved host still work).
|
||||
#
|
||||
# Self is identified by matching the box's hostname/short-name or any local IPv4
|
||||
# (incl. the wg IP) against hosts[].{name,aliases,lan,wg}.
|
||||
|
|
@ -111,7 +115,7 @@ render_block() {
|
|||
| ( $h.public
|
||||
// (if $reachlan and $lan != null then $lan else null end)
|
||||
// $h.wg ) as $addr
|
||||
| "\nHost \(([$h.name] + $h.aliases) | join(" "))\n HostName \($addr)\n User \($h.ssh_user // "lilith")"
|
||||
| "\nHost \(([$h.name] + $h.aliases) | join(" "))\n HostName \($addr)\n User \($h.ssh_user // "lilith")\n CheckHostIP no\n StrictHostKeyChecking accept-new"
|
||||
' "$data_file"
|
||||
printf '\n%s\n' "$END"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ can *resolve* it):
|
|||
**These records are consumed only by clients whose WireGuard config sets
|
||||
`DNS=10.9.0.2` — i.e. phones.** The named hosts (apricot/pear/fennel) do *not*
|
||||
point their resolver at `10.9.0.2`, so for them dnsmasq does not answer.
|
||||
- **For the named hosts, `.wg`/`.lan` is delivered by the static `/etc/hosts`
|
||||
block** from `mesh-hosts-render --install`. Run it on every host that must
|
||||
resolve a peer's name. (Verified: before any install, `dscacheutil -q host -a
|
||||
name apricot.wg` on fennel returns nothing.)
|
||||
- **fennel** roams off-LAN where dnsmasq is unreachable, so the static
|
||||
- **For the named hosts, names are delivered by the managed `/etc/hosts` block**
|
||||
from `mesh-hosts-render --install` (bare + `.lan` at *current* IPs, `.wg`,
|
||||
service vhosts). On fennel the daemon regenerates it automatically on drift;
|
||||
run it once on every other host that must resolve peers by name.
|
||||
- **fennel** roams off-LAN where dnsmasq is unreachable, so the managed
|
||||
`/etc/hosts` block is its only resolution path then.
|
||||
|
||||
The old `*.local` platform scheme is **retired** (platform → `.com`, infra →
|
||||
|
|
@ -91,6 +91,16 @@ LAN interface (~5ms). (Measured: apricot 351ms via tunnel → 5.6ms via en0.)
|
|||
(direct); AWAY → via the wg mesh interface (so home stays reachable through the
|
||||
tunnel). Re-asserted every cycle, because `wg-quick` re-adds the tunnel `/24`
|
||||
on reconnect.
|
||||
3. **Name-sync (HOME only)** — keep ssh + hosts in sync with reality. Each LAN
|
||||
host's **MAC is stable while its DHCP IP drifts**, and ARP maps MAC↔IP. The
|
||||
daemon reads the ARP table (rate-limited ping-sweep of the `/24` to populate
|
||||
it when a host is missing), resolves every `hosts[]` entry with a `mac` to its
|
||||
*current* IP, and on any change writes `data/lan-state.json` ({name: ip},
|
||||
gitignored — volatile, per-device) and regenerates both views:
|
||||
`mesh-hosts-render --install` (`/etc/hosts`) and, as the console user,
|
||||
`host-apply --ssh-apply` (`~/.ssh/config`). Result: when apricot rebooted from
|
||||
`.116` to `.118`, `ssh apricot` and `quinn.apricot.lan` followed automatically
|
||||
— no DHCP reservations, no hand-edits.
|
||||
|
||||
**Why a subnet route, not per-host `/32` pins** (the old design): a `/32
|
||||
-interface` route on macOS creates a *self-MAC* ARP entry that blackholes the
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue