The bridge on the rack PC self-updates: it checks a release feed, tells you when a new version is out, and installs it on your click (or automatically, if you turn on auto-update). This doc covers how that works, the on-disk layout it relies on, and — separately — how to update the tray itself.

Status: LIVE. Deployed on the rack PC (DEN-WS-EDIT) and verified end-to-end (v0.1.0 → v0.1.1 installed cleanly, config preserved, bridge stayed healthy). See .context/self-update-system.md for the full record.

On-disk layout (why it’s split)

Windows won’t let you rename a folder that contains a running program, so the tray (which drives the update) must live outside the folder that gets swapped. The install is per-user (under the operator account, av-admin) so neither the deploy nor an update needs administrator elevation:

%LOCALAPPDATA%\Programs\BHAV Bridge\
  mrx7d-bridge-tray.exe     ← the tray: stable supervisor. NOT touched by updates.
  app\                      ← the bridge payload (dist + node_modules + package.json).
                              Swapped on update.
%LOCALAPPDATA%\BHAV Bridge\
  data\                     ← settings.json, mappings.json, aliases, dashboards.
                              NEVER touched by an update. BHAV_DATA_DIR points here.
  • The tray supervises the bridge and runs updates. It updates only the app\ payload — it stops the bridge, swaps app\, and relaunches it.
  • Runtime data lives in %LOCALAPPDATA%\BHAV Bridge\data, disjoint from app\. Both the bridge and the tray resolve the same path via the BHAV_DATA_DIR environment variable — the updater refuses to run if data sits inside app\ (it would be destroyed by the swap).

Environment the tray reads (set once, per-user)

The tray is configured entirely through user-level environment variables:

VariableValue on the rack PCPurpose
BRIDGE_DIR…\Programs\BHAV Bridge\appWhere the bridge payload lives (the swap target)
BRIDGE_CMDnode dist/index.jsRun the built bridge (not the dev tsx default)
BHAV_DATA_DIR…\BHAV Bridge\dataWhere settings/mappings live
UPDATE_FEED_URLhttps://git.kdsp.net/…/latest.jsonThe release feed the tray polls
FORGEJO_TOKEN(read-only feed token)Auth for the private release repo

Gotcha — session environment is a snapshot. Windows builds a process’s environment block once, at logon. If you set/change any of these variables while the operator is already logged in, a tray launched from the Startup shortcut (or Start-Process) in that stale session won’t see them — it falls back to the dev default (npx tsx src/index.ts) and the bridge fails to start with ERR_MODULE_NOT_FOUND … src\index.ts. Fix: log off/on (or reboot). A fresh logon rebuilds the environment from the registry and the Startup shortcut then launches the tray correctly. A reboot is the canonical way to apply env changes and doubles as the unattended-boot test.

Updating the bridge (the normal flow)

  1. A release is cut (maintainer): tag vX.Y.Z → CI/build-win-app.ps1 builds the payload → scripts/publish-release.ps1 zips the bridge payload + its SHA-256 and publishes a Forgejo Release with a latest.json manifest.
  2. The tray notices on its next check and shows “Update available (vX.Y.Z)” in the tray menu and the web UI’s Updates panel.
  3. You install it — click Install in the tray menu, or hit the button in the web UI (reachable over the mesh). Or turn on Auto-update to have it apply on detection. Auto-update self-disables after 2 consecutive failed attempts and falls back to manual + a notice, so a bad release can’t loop.
  4. What happens under the hood: download → verify SHA-256 → stop the bridge → back up app\ → extract the new payload → relaunch → health-check /api/status. Any failure after the backup rolls back to the previous version automatically. Your data in %ProgramData% is never touched.

Updating the tray (manual — rare)

The tray is deliberately outside the auto-update path (it’s the thing doing the updating). It changes rarely; update it by hand:

  1. Build the new tray on a Windows machine with Go + mingw-w64:
    cd tray
    .\build-win-app.ps1        # produces mrx7d-bridge-tray.exe (+ embedded icon/version)
  2. On the rack PC, quit the tray (right-click its icon → Quit — this also stops the bridge) so its .exe is no longer locked.
  3. Replace the binary:
    Copy-Item .\mrx7d-bridge-tray.exe "$env:LOCALAPPDATA\Programs\BHAV Bridge\mrx7d-bridge-tray.exe" -Force
  4. Relaunch the tray. The clean way is to reboot (or log off/on): the Startup shortcut fires in a fresh session with the full environment, brings the bridge back up, and doubles as the unattended-boot test. (Double-clicking the Startup shortcut in an already-running session also works, provided the env vars above were already in place at logon — see the session gotcha above.)

Cross-compiling the tray from a Mac (what was used for the live deploy): with Go

  • mingw-w64 installed, from tray/:
CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 \
  go build -ldflags="-H windowsgui -X main.Version=X.Y.Z" -o mrx7d-bridge-tray.exe .

This produces a working binary but with a blank icon and no embedded version resource — fine for a hotfix; use build-win-app.ps1 on Windows for a release build.

Because the tray lives outside app\, this never disturbs the bridge payload or your data. If you ever want the tray to self-update too, that needs a small detached helper (Option A in the design) — not built, and not needed for normal operation.

Troubleshooting

  • “tray is installed inside the bridge dir” on install → the layout is wrong: the tray.exe must be in the parent of app\, not inside it. Re-lay the install per the layout above.
  • Update fails immediately, no disruption → checksum mismatch (bad/corrupt download). The bridge was never stopped; just re-check and retry.
  • Update rolled back → a step after the swap failed (e.g. the new version didn’t come healthy). The previous version was restored and relaunched. Check the tray log.
  • Auto-update turned itself off → two consecutive auto attempts failed; it fell back to manual on purpose. Fix the release, then re-enable auto-update.
  • Tray is up but the bridge won’t start, bridge.log shows exec: npx tsx src/index.ts and ERR_MODULE_NOT_FOUND … src\index.ts → the tray didn’t see BRIDGE_DIR/BRIDGE_CMD (stale session env — see the gotcha above). Reboot / log off-on and let the Startup shortcut relaunch it.
  • Update /check returns 503 briefly → historically a too-short proxy timeout; fixed in v0.1.1 (bridge → tray proxy waits 30 s for the feed fetch). If you still see it, the tray control server is down — restart the tray.