From 05708fc7e45efc861f093f56d60b5b91971561a0 Mon Sep 17 00:00:00 2001 From: Evan Senter Date: Sat, 24 Oct 2026 15:31:26 +0100 Subject: [PATCH 1/2] feat(bootstrap): open the Tailscale app at login on the gateway host Adds a gateway-only LaunchAgent that opens the Standalone Tailscale app at login, so the gateway's tailnet services come back after a reboot. The app can't start before login (only the open-source tailscaled can, and the two don't coexist), and FileVault blocks pre-login daemons on this host anyway, so login-time is the earliest achievable start. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LU6XPUUvj1XfSh2dQBV7wP --- CLAUDE.md | 2 +- LaunchAgents/com.evansenter.tailscale.plist | 27 +++++++++++++++++++++ bootstrap.sh | 9 +++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 LaunchAgents/com.evansenter.tailscale.plist diff --git a/CLAUDE.md b/CLAUDE.md index 8b80aac..16845ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -120,7 +120,7 @@ Files in `home/` are symlinked to `~` by `bootstrap.sh`. This allows version con LaunchAgent plists live in `~/Library/LaunchAgents/`. The external service repos (event-bus, session-analytics, memory-store) have their own `make install-server`; obsidian-mcp is set up by *this* repo's bootstrap (`claude mcp add` + plist in `LaunchAgents/`, wrapper in `home/.bin/obsidian-mcp-start`, requires Obsidian with the Local REST API plugin running). Reload with `launchctl unload` + `launchctl load`. -Note: `agent-memory-store` is documented infra but is **not** provisioned by bootstrap (no `claude mcp add`, no `AGENT_MEMORY_STORE_URL` in `settings.local.json`) — it's registered out-of-band where used. The repo-managed LaunchAgents are: `com.evansenter.obsidian-mcp` (gateway only), `com.evansenter.jellyfin` (gateway only, runs the server binary from the `jellyfin` cask headless with `DOTNET_USE_POLLING_FILE_WATCHER=1` — on macOS 27 its FSEvents config watcher blocks startup forever; don't also launch Jellyfin.app, which would fight it for port 8096), `com.evansenter.sysload` (zellij CPU/RAM widget, if zellij installed), and `com.user.cargo-sweep` (periodic `cargo sweep`, if cargo installed). +Note: `agent-memory-store` is documented infra but is **not** provisioned by bootstrap (no `claude mcp add`, no `AGENT_MEMORY_STORE_URL` in `settings.local.json`) — it's registered out-of-band where used. The repo-managed LaunchAgents are: `com.evansenter.obsidian-mcp` (gateway only), `com.evansenter.jellyfin` (gateway only, runs the server binary from the `jellyfin` cask headless with `DOTNET_USE_POLLING_FILE_WATCHER=1` — on macOS 27 its FSEvents config watcher blocks startup forever; don't also launch Jellyfin.app, which would fight it for port 8096), `com.evansenter.tailscale` (gateway only, opens the Standalone Tailscale app at login — the app can't run before login and doesn't coexist with the open-source `tailscaled`, and FileVault blocks pre-login daemons anyway), `com.evansenter.sysload` (zellij CPU/RAM widget, if zellij installed), and `com.user.cargo-sweep` (periodic `cargo sweep`, if cargo installed). **Adding new LaunchAgents:** Plists go in the top-level `LaunchAgents/` directory (NOT `home/Library/LaunchAgents/`). Use `__HOME__` as a placeholder for the user's home directory — `install_launch_agent()` in bootstrap does `sed` substitution at install time. Logs go to `~/.local/log/`. Add an `install_launch_agent` call in `install_launch_agents()`, gated on the binary being present. diff --git a/LaunchAgents/com.evansenter.tailscale.plist b/LaunchAgents/com.evansenter.tailscale.plist new file mode 100644 index 0000000..2ca8faf --- /dev/null +++ b/LaunchAgents/com.evansenter.tailscale.plist @@ -0,0 +1,27 @@ + + + + + Label + com.evansenter.tailscale + + + ProgramArguments + + /usr/bin/open + -g + -a + /Applications/Tailscale.app + + + RunAtLoad + + + StandardOutPath + __HOME__/.local/log/tailscale.out + + StandardErrorPath + __HOME__/.local/log/tailscale.err + + diff --git a/bootstrap.sh b/bootstrap.sh index 750902b..cbbe76b 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -398,6 +398,15 @@ install_launch_agents() { install_launch_agent "com.evansenter.jellyfin.plist" fi + # Install Tailscale LaunchAgent (gateway host, from the tailscale-app cask) + # Opens the Standalone app at login so the gateway's tailnet services come + # back after a reboot. The app can't run before login (only the open-source + # tailscaled can, and the two don't coexist); with FileVault on, nothing + # runs before the first unlock anyway. + if is_gateway_host && [[ -d /Applications/Tailscale.app ]]; then + install_launch_agent "com.evansenter.tailscale.plist" + fi + # Install sysload writer LaunchAgent (only if zellij is installed) # Caches `top` output to ~/.cache/sysload every 10s so the zjstatus # sysload widget can read it without spawning `top` per-tab-per-redraw. From 6a9f4f8da84f22d7dc124513b7c6f2d835290eca Mon Sep 17 00:00:00 2001 From: Evan Senter Date: Sun, 25 Oct 2026 16:21:50 +0000 Subject: [PATCH 2/2] docs(bootstrap): explain why the Tailscale agent overlaps the app's login toggle Addresses review feedback on #357. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LU6XPUUvj1XfSh2dQBV7wP --- bootstrap.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bootstrap.sh b/bootstrap.sh index cbbe76b..da17cd1 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -402,7 +402,9 @@ install_launch_agents() { # Opens the Standalone app at login so the gateway's tailnet services come # back after a reboot. The app can't run before login (only the open-source # tailscaled can, and the two don't coexist); with FileVault on, nothing - # runs before the first unlock anyway. + # runs before the first unlock anyway. Overlaps the app's own "Launch at + # login" toggle on purpose: that lives in app prefs and is lost on reinstall, + # while this is tracked here and restored by bootstrap (a double `open` is a no-op). if is_gateway_host && [[ -d /Applications/Tailscale.app ]]; then install_launch_agent "com.evansenter.tailscale.plist" fi