feat(dx): add dx.hide_homelan to hide homelan config while DO-only

- data/mesh-hosts.json: "dx": {"hide_homelan": true} (with note). Data for apricot/pear/fennel/lan/services fully preserved for recovery.
- bin/mesh-hosts-render + bin/host-apply: respect the flag — filter to .class=="cloud" hosts only (yuzu, lime), emit dx mode note in headers, services filtered too.
- When true: generated /etc/hosts mesh-block and ~/.ssh/config net-tools fleet block only contain DO/cloud (homelan names like apricot.lan, bare fennel etc. hidden). dx-forges (ctforge/mcforge) unaffected at bottom.
- `net sync` (and direct renderers) now produce clean DO-only configs.
- README updated. To recover: set false + net sync.

Fulfills "hide the homelan config... now only use DO... may try to recover homelan so dont delete it".
This commit is contained in:
Natalie 2026-06-28 10:50:51 -04:00
parent c78e7cde1f
commit c3d788e20a
4 changed files with 27 additions and 8 deletions

View file

@ -90,6 +90,7 @@ manages (it removes them; its block supersedes them).
| `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`. |
| `bin/forge-dns-render` | **laptop/dev machines** | DX-only: renders cloud Forgejo shortcuts (mcforge, ctforge, ...) from `~/.vault/*_forge_creds` into a managed block at the bottom of `/etc/hosts`. Used by `net sync` and per-project `./run forge:dns`. Adopts loose entries. `--print`/`--diff`/`--install`. |
| dx config in mesh-hosts.json | data-driven | `"dx": { "hide_homelan": true }` hides homelan hosts (apricot/pear/fennel + LAN names/services) from all generated /etc/hosts + ssh fleet blocks (only cloud/DO remain). Full data preserved for recovery (set false + `net sync`). Used while DO-only. |
| `smart-lan-router/` | **fennel** | `com.lilith.smart-lan-router.plist` (launchd) + `install-agent.sh` (one installer: launchd or systemd) + `smart-lan-router.service.tmpl`. |
| [`tray/`](tray/) | **fennel** (menu bar) | The fleet tray (absorbed from the old `wireguard-vpn-tray` repo). Icon = tunnel state (green/yellow/red); menu = live fleet view from `data/agent-status.json`: agent freshness, HOME/AWAY + route, discovered host IPs, repo HEAD. Connect/disconnect actions. Install: `bash tray/install-tray.sh` (as the user, no sudo). |
@ -129,7 +130,7 @@ its own code changes — fleet updates propagate by pushing to the forge.
| add/rename a host, change a MAC, add a service vhost or phone | edit [`data/mesh-hosts.json`](data/mesh-hosts.json), let autocommit push — **every agent pulls, restarts on code change, and converges (incl. its OS hostname) within minutes** |
| react to a host changing DHCP IP | nothing — agents discover it by MAC and regenerate `/etc/hosts` + ssh automatically |
| rename a node's OS hostname | nothing by hand — `fleet.enforce_hostname` makes the node's own agent do it |
| force a regen now | `net sync` (mesh-hosts + forge-dns + ssh) or the individual `sudo ... --install` |
| force a regen now | `net sync` (mesh-hosts + forge-dns + ssh) or the individual `sudo ... --install`. Toggle dx.hide_homelan in data/mesh-hosts.json then sync to hide/show homelan. |
| apricot mesh DNS (phones) | `sudo wg-dns-sync` on apricot |
| enroll a phone | `wg-phone-add -d <device>` then add a `class: phone` entry |

View file

@ -60,6 +60,8 @@ data_file="$root/data/mesh-hosts.json"
[ -f "$data_file" ] || { echo "host-apply: cannot locate data/mesh-hosts.json" >&2; exit 1; }
command -v jq >/dev/null || { echo "host-apply: jq not installed" >&2; exit 1; }
hide_homelan=$(jq -r '.dx.hide_homelan // false' "$data_file")
# Overlay: current LAN IPs discovered by the daemon (data/lan-state.json, a
# {name: ip} map) override the static `lan` seed, so ssh tracks DHCP drift.
overlay='{}'
@ -105,12 +107,17 @@ reachlan=$(jq -r --arg s "$self" '
# --- render this device's ssh block --------------------------------------------
render_block() {
printf '%s\n' "$BEGIN"
printf '# rendered for: %s (vantage: %s)\n' "$self" \
"$( [ "$reachlan" = "true" ] && echo 'LAN-capable → prefer .lan' || echo 'mesh-only → prefer .wg' )"
jq -r --arg s "$self" --argjson reachlan "$reachlan" --argjson ov "$overlay" '
if [ "$hide_homelan" = "true" ]; then
printf '# rendered for: %s (dx/cloud-only; homelan hidden — set dx.hide_homelan=false to recover)\n' "$self"
else
printf '# rendered for: %s (vantage: %s)\n' "$self" \
"$( [ "$reachlan" = "true" ] && echo 'LAN-capable → prefer .lan' || echo 'mesh-only → prefer .wg' )"
fi
jq -r --arg s "$self" --argjson reachlan "$reachlan" --argjson ov "$overlay" --argjson hide "$hide_homelan" '
.hosts[]
| select(.name != $s)
| select(.ssh_user != null)
| select( if $hide then (.class == "cloud") else true end )
| . as $h
| (($ov[$h.name]) // $h.lan) as $lan
| ( $h.public

View file

@ -71,6 +71,8 @@ data_file="$root/data/mesh-hosts.json"
command -v jq >/dev/null || { echo "mesh-hosts-render: jq not installed" >&2; exit 1; }
jq empty "$data_file" || { echo "mesh-hosts-render: invalid JSON in $data_file" >&2; exit 1; }
hide_homelan=$(jq -r '.dx.hide_homelan // false' "$data_file")
# Overlay: current LAN IPs discovered by the daemon (data/lan-state.json, a
# {name: ip} map) override the static `lan` seed, so records track DHCP drift.
overlay='{}'
@ -101,10 +103,14 @@ render_block() {
printf '%s\n' "$BEGIN"
printf '# Auto-generated from net-tools/data/mesh-hosts.json + lan-state.json — re-run to update.\n'
printf '# bare/<host>.lan = current LAN IP (direct at home, tunnel when away) · <host>.wg = mesh IP\n'
if [ "$hide_homelan" = "true" ]; then
printf '# dx mode: homelan hidden (only cloud/DO hosts rendered). Data preserved for recovery.\n'
fi
# LAN records — current discovered IP (overlay) over the static seed. Bare
# names live here only when THIS node can reach LAN IPs (vantage).
jq -r --argjson ov "$overlay" --argjson reachlan "$reachlan" '
jq -r --argjson ov "$overlay" --argjson reachlan "$reachlan" --argjson hide "$hide_homelan" '
.hosts[]
| select( if $hide then (.class == "cloud") else true end )
| . as $h
| (($ov[$h.name]) // $h.lan) as $lan
| select($lan != null)
@ -114,8 +120,9 @@ render_block() {
' "$data_file"
# Mesh (.wg) records — explicit tunnel path. Bare names land here for
# LAN-less hosts always, and for ALL hosts when this node is mesh-only.
jq -r --argjson reachlan "$reachlan" '
jq -r --argjson reachlan "$reachlan" --argjson hide "$hide_homelan" '
.hosts[]
| select( if $hide then (.class == "cloud") else true end )
| . as $h
| "\($h.wg)\t"
+ ((([$h.name] + ($h.aliases // [])) | map(. + ".wg")) | join(" "))
@ -124,12 +131,12 @@ render_block() {
' "$data_file"
# Service vhosts — the hosting host'\''s current LAN IP, or its wg IP from a
# mesh-only vantage.
jq -r --argjson ov "$overlay" --argjson reachlan "$reachlan" '
jq -r --argjson ov "$overlay" --argjson reachlan "$reachlan" --argjson hide "$hide_homelan" '
. as $d
| ($d.services // {}) | to_entries[]
| select(.key != "_note")
| .key as $hname
| ($d.hosts[] | select(.name == $hname)) as $h
| ($d.hosts[] | select(.name == $hname) | select( if $hide then (.class == "cloud") else true end )) as $h
| (if $reachlan then (($ov[$hname]) // $h.lan) else $h.wg end) as $addr
| select($addr != null)
| "\($addr)\t\(.value | join(" "))"

View file

@ -33,6 +33,10 @@
"gateway_mac": "c4:4f:d5:5a:61:6f",
"gateway_note": "Xfinity broadband gateway. gateway_mac is the home-LAN fingerprint: the smart-lan-router daemon treats the laptop as 'home' only when the default gateway on the LAN interface has this MAC — distinguishes the real home LAN from any visited 10.0.0.0/24 network. DHCP reservations only via xFi/web UI, no scriptable API."
},
"dx": {
"hide_homelan": true,
"_note": "When true, renderers (mesh-hosts-render, host-apply) omit homelan hosts (apricot/pear/fennel + their LAN/.lan/bare names and services on them) from generated /etc/hosts and ~/.ssh/config. Only cloud/DO hosts (yuzu, lime + their public/WG) and dx-forges (separate) are shown. Full homelan data preserved in 'hosts'/'lan'/'services' for easy recovery — set false and re-render (net sync). We are currently DO-only for DX."
},
"hosts": [
{
"name": "apricot",