- derive visible hosts, Fleet label, and critical peers from dx.hide_homelan in mesh-hosts.json - "Toggle homelan visibility (recovery)" menu action + immediate re-render of /etc/hosts+ssh - only lime (DO backend with MCPs/tools) is core critical for the icon; redroid is shown but does not degrade - stale homelan (pear etc) filtered from tray labels when using the new DO config (hide=true) - tray README and module docs updated; the switcher makes the active config obvious in the menu bar - this is the private path for MCPs on DO (and other internal tools): wg mesh (yuzu hub + lime spoke) + net-tools names, no public exposure .
2.9 KiB
wireguard-vpn-tray
macOS menu-bar app showing live WireGuard mesh (wg1) status as a colored
hexagon icon plus a Status menu item. This is also the switcher for the
active fleet config (new DO cloud vs optional homelan recovery data).
- 🟢 green — tunnel up, hub reachable, and all critical hosts for the active config (DO lime etc. when homelan hidden; or homelan+cloud when un-hidden) are reachable.
- 🟠 orange — tunnel up + hub ok, but one or more critical hosts for the current config are down (partial mesh).
- 🟡 yellow — tunnel up but the hub is not yet reachable (connecting)
- 🔴 red — no tunnel interface present
The tray reads data/mesh-hosts.json (and its dx.hide_homelan) so the
displayed hosts, the "Fleet:" label, and the critical set for the icon are
always consistent with the active config. "DO cloud-only (homelan hidden)" is
the normal production view; homelan data stays in the json for recovery but is
filtered out of the tray (and of rendered ssh/hosts) until you toggle.
Click "Toggle homelan visibility (recovery)" in the menu to flip
dx.hide_homelan, auto re-render this machine's /etc/hosts + ssh fleet block,
and refresh the tray labels. Commit the json change (the toggle only does the
local render) so the rest of the fleet sees the new config on their next pull.
Built on the in-house lilith_tray framework (vendored here under
lilith_tray/) via its macOS rumps backend.
Interface detection
macOS assigns the WireGuard utun device dynamically — the number changes
between boots. The app therefore identifies the tunnel by the address it
carries (the utun with a 10.9.0.x inet address), never by a hardcoded
name. The tray icon and the Status label are both derived from a single
status computation in poll_status(), so they can never disagree.
Layout
| Path | Purpose |
|---|---|
vpn_tray.py |
the tray app |
vpn-tray |
launcher — activates the venv and runs the app |
lilith_tray/ |
vendored tray framework |
icons/ |
colored status icons |
generate_icons.py |
regenerates the icon set |
vpn-toggle.applescript |
standalone connect/disconnect applet |
com.wireguard.vpn-tray.plist |
launchd agent for the tray |
com.natalie.wg-quick-wg1.plist |
launchd agent bringing wg1 up at boot |
requirements.txt |
Python dependencies |
The bundled launchd plists carry absolute paths for the current install
(~/.wireguard/); adjust them if the app is installed elsewhere.
Setup
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Install the launchd agent:
cp com.wireguard.vpn-tray.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.wireguard.vpn-tray.plist
Secrets
WireGuard keys (*.key) and the tunnel config (wg1.conf) are intentionally
not in this repo — see .gitignore. They live in ~/.wireguard/.