fix: self_dnsname shells out to node, but Collie requires bun#22
Merged
Conversation
jz-wilson
added a commit
to jz-wilson/collie
that referenced
this pull request
Jul 21, 2026
…er shortcut AltanS#22 called for a Scheduled Task (the direct mirror of the systemd --user self-install), and that's what landed first. It doesn't work: `start` runs as a Herdr plugin action — non-elevated, InteractiveToken — and registering a NEW ONLOGON-trigger task is blocked by a UAC filtered-token wall for admin-class accounts. Live-confirmed independently by two people: the byte-identical, currently-working caddy-frontdoor task XML re-imported under a throwaway name gets the same "Access is denied"; the same account's `/SC ONCE` task creation succeeds. A one-time elevated step would fix this one box but leave every fresh install with a manual prerequisite systemd doesn't have, so `start` could never genuinely self-install. The Architect chose the Startup-folder shortcut AltanS#18 already listed as the alternative — no elevation needed anywhere, so `start` actually self-installs. Replaces winRegisterTask/winStartTask/winStopTask/winUninstallTask and the schtasks/XML machinery with: installWinShortcut (a real .lnk via WScript.Shell COM, not a dropped .cmd, so it can run minimized with no console flash) and a generated PowerShell launcher loop that `start` both installs to the Startup folder AND launches immediately (not deferred to next logon). `stop` writes a deliberate-stop marker then kills the current bridge child by its exact tracked pid (never a blanket bun.exe taskkill), which the loop notices and breaks on instead of restarting; `uninstall` additionally removes the shortcut and launcher script. `start` is idempotent by checking the launcher's own live pid, not just "is the port answering" (which would miss the narrow window where the previous child just crashed and a restart is about to happen). `status`/supervisorLabel report shortcut-present and bridge-pid-alive independently, honestly. One consequence made explicit in comments: the Scheduled Task's RestartOnFailure was a SECOND restart layer on top of the launcher's own loop; a shortcut has no equivalent, so supervision is single-layer on win32 — if the launcher process itself dies (not just the bridge), nothing brings it back until the next logon. Flagged for AltanS#29 to weigh as a residual risk, not solved here (no second watchdog — out of scope). Two bugs live-verified and fixed during this work, both only visible under a real `start` (not from typecheck or unit tests): - A first attempt captured the bridge's output via .NET Register-ObjectEvent on the child process. It's fragile in a way that matters here: `$proc.WaitForExit()` blocks the runspace so queued event actions never fire until the child exits (fixable by polling `-not $proc.HasExited` instead) — but even polling, under the exact hidden, non-interactive `-WindowStyle Hidden -File` launch this actually runs under, the launcher process itself silently died after starting the bridge, ORPHANING it: a live, unsupervised bridge, invisible to `stop`/`status`. Replaced with Start-Process + per- iteration temp redirect files flushed into the log on exit — the cost is real (`logs` only gets fresh content on a restart cycle, not a live tail while healthy) but a supervisor that can vanish mid-loop is a worse trade. - `start` originally launched the persistent loop via `Bun.spawn(...). unref()` directly. Live-verified that child dies the MOMENT the parent bun.exe process exits — Bun doesn't expose a way to detach from whatever job object it puts its own children in on Windows, and `.unref()` only stops Bun's own event loop from waiting on it, it doesn't survive the parent's exit (confirmed with a minimal repro: spawn + unref a 60s sleep, and it's gone within 2s of the parent returning). PowerShell's own Start-Process, invoked via one short- lived intermediate powershell.exe, reliably breaks away instead. Milestone verified live end to end on a throwaway port/config dir/ shortcut name against the real (read-only) Herdr socket: `start` → `/api/snapshot` returns real 200 with live pane/agent data → idempotent re-`start` (same launcher pid, no duplicate spawned) → `stop` (port stops answering, both launcher and bridge pids gone, shortcut preserved) → `uninstall` (shortcut and launcher script removed). Test shortcut and every pidfile/log confirmed cleaned up; caddy-frontdoor/ caddy-watchdog confirmed untouched and healthy throughout. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01GAWBGnnEdCGmYLPqSuwkY2
Owner
|
Thanks @jz-wilson 🙏 shipped in v0.14.1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
self_dnsname()inscripts/collie-ctl.shparsestailscale status --jsonby piping into
node -e '...'. Collie is a Bun project (bun is required torun the bridge, build the web UI, etc.) — Node isn't a stated dependency
anywhere, so on any host without Node on PATH this silently breaks and
bridge_url()falls back to the "Tailscale name unavailable" branch.Swap
node -eforbun -e; the one-liner is unchanged and worksidentically since it only touches Node-compatible
process.stdin/JSON.parse, both of which Bun implements faithfully.This is a latent correctness bug, not specific to any one platform —
found it while auditing the script for an unrelated port, but it's
independent of that and worth fixing on its own.
Verified against a live
tailscale status --json: returns the expectedDNSName with the trailing dot stripped. Empty and malformed stdin both
silently no-op, matching the existing try/catch and the caller's fallback.