Skip to content

fix: self_dnsname shells out to node, but Collie requires bun#22

Merged
AltanS merged 1 commit into
AltanS:mainfrom
jz-wilson:fix/self-dnsname-use-bun
Jul 22, 2026
Merged

fix: self_dnsname shells out to node, but Collie requires bun#22
AltanS merged 1 commit into
AltanS:mainfrom
jz-wilson:fix/self-dnsname-use-bun

Conversation

@jz-wilson

Copy link
Copy Markdown
Contributor

self_dnsname() in scripts/collie-ctl.sh parses tailscale status --json
by piping into node -e '...'. Collie is a Bun project (bun is required to
run 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 -e for bun -e; the one-liner is unchanged and works
identically 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 expected
DNSName with the trailing dot stripped. Empty and malformed stdin both
silently no-op, matching the existing try/catch and the caller's fallback.

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
@AltanS
AltanS merged commit c228c15 into AltanS:main Jul 22, 2026
1 check passed
@AltanS

AltanS commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Thanks @jz-wilson 🙏 shipped in v0.14.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants