net-tools/tray
Natalie 6e6512abf6 fix(tray): own the menu-bar tray with a RunAtLoad+KeepAlive LaunchAgent
The tray's Quit handler already boots out com.wireguard.vpn-tray, but install-tray.sh
had retired that launchd job and relied on the fleet agent to nohup it — which never
ran the tray reliably at boot (no GUI session yet). Restore the LaunchAgent (same
pattern as com.lilith.mac-sync): RunAtLoad starts it at login in the GUI session,
KeepAlive relaunches on crash. ensure_tray() now defers to launchd when the agent is
installed (Popen path kept as fallback). Removes the dead standalone plist.
2026-06-22 22:39:11 -04:00
..
icons feat(@tools/net-tools): add icon generation tool 2026-06-10 05:35:11 -07:00
lilith_tray feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
.gitignore feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
com.natalie.wg-quick-wg1.plist feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
generate_icons.py feat(@tools/net-tools): add icon generation tool 2026-06-10 05:35:11 -07:00
install-tray.sh fix(tray): own the menu-bar tray with a RunAtLoad+KeepAlive LaunchAgent 2026-06-22 22:39:11 -04:00
README.md feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
requirements.txt feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
vpn-toggle.applescript feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
vpn-tray feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00
vpn_tray.py feat(@tools/net-tools): add tray icon system 2026-06-10 02:20:23 -07:00

wireguard-vpn-tray

macOS menu-bar app showing live WireGuard mesh (wg1) status as a colored hexagon icon plus a Status menu item.

  • 🟢 green — tunnel up and the mesh hub (10.9.0.1) is reachable
  • 🟡 yellow — tunnel up but the hub is not yet reachable (connecting)
  • 🔴 red — no tunnel interface present

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/.