Skip to content

macOS desktop: install + in-app self-update#35607

Merged
OutThisLife merged 5 commits into
bb/guifrom
bb/gui-mac-desktop-update
May 31, 2026
Merged

macOS desktop: install + in-app self-update#35607
OutThisLife merged 5 commits into
bb/guifrom
bb/gui-mac-desktop-update

Conversation

@OutThisLife

@OutThisLife OutThisLife commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Makes the macOS desktop install/update path work end to end. Targets bb/gui.

Changes

Installer/runtime path alignment

  • paths.rs: the Tauri installer computed macOS HERMES_HOME as ~/Library/Application Support/hermes, but Python (hermes_constants.get_hermes_home()), install.sh, and the Electron app all use ~/.hermes. The drift meant the installer wrote to one dir and the desktop read from another. Aligned to ~/.hermes.

install.sh stage protocol

  • Run each --stage body in a subshell so a stage helper that calls exit 1 still emits its JSON result frame instead of terminating before it (the Rust/Electron parser expects a {ok:false,…} frame, not a missing one).

git auto-provision (parity with install.ps1)

  • install.ps1 downloads PortableGit on Windows; install.sh previously just printed a "please install git" hint and exited, so a fresh machine with no developer tools couldn't clone. check_git now provisions git first: macOS → Homebrew if present, else xcode-select --install; Linux → apt/dnf/pacman. Falls back to manual instructions only if that fails.

In-app self-update (macOS/Linux)

  • With no staged updater binary, "Update now" previously only printed hermes update. POSIX has no venv-shim file lock, so the desktop now drives the update itself: hermes update --yes [--branch <current>] (backend) → hermes desktop --build-only (OS-aware GUI rebuild, with the managed Node + venv on PATH) → a detached swapper waits for the app to exit, dittos the rebuilt Hermes.app over the running bundle, clears quarantine, and relaunches. Degrades to "backend updated — restart to load the new GUI" if the rebuild fails or there's no bundle to swap (dev run / AppImage).

Verified locally

  • Drag-install .dmg (npm run dist:mac:dmg): mounts to a volume with Hermes.app + /Applications shortcut and the correct icon on both the app and the volume.
  • install.sh --manifest/--stage JSON contract: valid manifest, unknown-stage → exit 2 + frame, failure still emits a frame; check_git happy path.
  • .app swap mechanism in isolation (old bundle replaced, no leftover temp dirs).
  • main.cjs parses + lints clean.

Not in this PR / follow-ups

  • Code signing + notarization (Apple Developer ID) — required for a downloaded dmg to open without the Gatekeeper override.
  • End-to-end run on a clean Mac: drag-install → first-launch bootstrap → "Update now" rebuild/swap/relaunch.

paths.rs computed the macOS Hermes home as ~/Library/Application Support/
hermes, but nothing else does: hermes_constants.get_hermes_home() (Python),
scripts/install.sh, and the Electron desktop's resolveHermesHome() all use
~/.hermes on macOS. The drift meant the Tauri installer wrote the install to
one directory and the desktop looked for it in another, so a fresh GUI
install never found its backend (the file's own comment warned this exact
drift would break things). Use ~/.hermes on macOS to match.
Stage helpers (clone_repo, install_deps, check_python, …) were written for
the monolithic flow and call `exit 1` on failure. Under `--stage`, that
terminated the process before the JSON result frame was printed, so the
installer's parse_stage_result saw "no frame" instead of a clean
{ok:false,...} contract response. Run the stage body in a subshell so an
`exit` only unwinds the subshell and the parent still emits the frame.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 31, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: Addresses HERMES_HOME drift described in #32741 (macOS LaunchAgent resets HERMES_HOME on reinstall). Competing fix: #32795.

Note: This PR targets bb/gui feature branch, not main.

…all.ps1)

install.ps1 downloads PortableGit on Windows, but install.sh just printed a
"please install git" hint and exited — so a fresh Mac with no developer tools
(no Xcode CLT → no git) couldn't get past the clone step. check_git now tries
to install git before bailing:
  - macOS: Homebrew if present (headless), else `xcode-select --install`
    (the CLT prompt also provides the compiler some wheels need), polling for
    git to appear.
  - Linux: apt/dnf/pacman via sudo when available.
Falls back to the manual instructions only if auto-provision fails.
On Windows the staged Hermes-Setup binary drives updates (quit → hermes
update → hermes desktop --build-only → relaunch). The mac drag-install has no
such binary, so "Update now" previously just printed `hermes update`.

Since there's no venv-shim file lock on POSIX, the desktop can drive the whole
update itself. applyUpdates now, when no staged updater exists on mac/linux:
  1. runs `hermes update --yes [--branch <current>]` (backend git pull + deps),
  2. runs `hermes desktop --build-only` (OS-aware GUI rebuild) with the
     Hermes-managed Node + venv on PATH,
  3. spawns a detached swapper that waits for this process to exit, dittos the
     freshly built Hermes.app over the running bundle, clears quarantine, and
     relaunches.
Degrades to "backend updated — restart to load the new GUI" if the rebuild
fails or there's no .app bundle to swap (dev run, Linux AppImage).
@OutThisLife

Copy link
Copy Markdown
Collaborator Author

Pushed two more commits toward true normie-mac happy-path:

feat(install.sh): auto-provision git — parity with install.ps1's PortableGit. check_git now installs git before bailing: macOS → Homebrew (headless) else xcode-select --install (also gives the compiler some wheels need); Linux → apt/dnf/pacman. Removes the "fresh Mac has no git → clone fails" blocker.

feat(desktop): in-app GUI+backend self-update on macOS/Linux — "Update now" with no staged Hermes-Setup now drives it directly (no venv-lock on POSIX): hermes update (backend) → hermes desktop --build-only (OS-aware GUI rebuild) → detached swapper dittos the rebuilt Hermes.app over the running bundle, clears quarantine, relaunches. Degrades to "backend updated — restart to load the new GUI" if rebuild fails / no bundle (dev/AppImage).

Tested locally: install.sh syntax + git happy-path; .app swap mechanism in isolation (v1→v2, no leftover temp dirs); main.cjs parse + lint clean.

Still needs real-install E2E (can't fully exercise here): dragged /Applications/Hermes.app → first-launch bootstrap on a bare Mac (git auto-provision via CLT) → "Update now" → rebuild+swap+relaunch. And signing/notarization remains the separate prerequisite for a downloaded dmg to open without the Gatekeeper override.

@OutThisLife OutThisLife changed the title fix(mac): align installer HERMES_HOME + harden install.sh stage protocol (mac go-live) macOS desktop: install + in-app self-update May 31, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Review

Fixes macOS installer HERMES_HOME drift (Tauri was using ~/Library/Application Support/hermes while everything else uses ~/.hermes) and hardens install.sh stage protocol for subshell failure-frame handling.

✅ Looks Good

  • Correct fix: Removes the macOS cfg branch in paths.rs so it falls through to existing ~/.hermes path.
  • Subshell fix: Stage body in subshell so exit 1 only unwinds the subshell and the parent emits {ok:false,...}.
  • Well-documented: Clear explanation of the two macOS distribution models (drag .dmg vs Tauri installer) with open decision.
  • Verified: Drag-install .dmg works with proper icon.

Reviewed by Hermes Agent (cron job)

@OutThisLife

Copy link
Copy Markdown
Collaborator Author

Local verification of the in-app update path

Ran the exact commands applyUpdatesPosixInApp invokes against a real install (5aade4bc5-era checkout + venv + apps/desktop):

  • hermes update --yes --branch <its-branch> (normal same-branch update): HEAD fast-forwarded to the branch tip, dependencies refreshed, "Update complete." ✅
  • hermes desktop --build-only: rebuilt apps/desktop/release/mac-arm64/Hermes.app. ✅
  • .app bundle swap (ditto over the running bundle): verified in isolation (old→new, no leftover temp dirs). ✅

So the three steps the mac updater chains all work. Not yet exercised: the full Electron click-through (the "Update now" button driving these + the detached swap/relaunch) on a live dragged app — components proven, glue not GUI-tested.

Caveat worth a follow-up (pre-existing hermes update, shared with Windows — not this PR's code)

hermes update --branch <DIFFERENT-branch> on a dirty tree silently did not switch branches and reported "Already up to date" while staying on the old commit. The dirtiness is self-inflicted: the install/build step modifies tracked package-lock.json + ui-tui/package-lock.json, so every update has to autostash. The normal same-branch case works (above); the cross-branch case is fragile. Likely worth either committing the lockfile churn out of the install or hardening hermes update's branch-switch.

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

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants