fix(desktop): make locally-built macOS app relaunchable after in-place self-update (#36198)
On macOS the desktop app is built locally and ad-hoc signed (no Developer ID on the user's machine). An ad-hoc bundle has no stable Designated Requirement, so when the self-updater rebuilds it in place with a fresh build (new cdhash) — plus the com.apple.quarantine flag inherited from the downloaded installer process chain — Gatekeeper/LaunchServices treats the changed code as tampering and macOS reports "Hermes is damaged and can't be opened," and the app fails to relaunch. First launch works (fresh registration); the in-place update relaunch is what breaks. Fix: after building the desktop app locally, strip quarantine xattrs and re-apply a clean deep ad-hoc signature (omitting the hardened-runtime flag, which an ad-hoc build can't satisfy). Applied in both build entry points: - hermes_cli/main.py cmd_gui (the `hermes desktop --build-only` path the updater drives) — so the fix ships via `hermes update` (git), no installer re-download needed. - scripts/install.sh install_desktop (first install) for parity. Both are no-ops on non-macOS and when a real signing identity (CSC_LINK / APPLE_SIGNING_IDENTITY) is configured, so signed/notarized builds are untouched.
This commit is contained in:
@ -6891,6 +6891,43 @@ def _desktop_packaged_executable(desktop_dir: Path) -> Optional[Path]:
|
||||
return max(existing, key=lambda p: p.stat().st_mtime)
|
||||
|
||||
|
||||
def _desktop_macos_relaunchable_fixup(desktop_dir: Path) -> None:
|
||||
"""Make a locally-built (unsigned) macOS desktop app survive in-place self-update.
|
||||
|
||||
An ad-hoc-signed .app has no stable Designated Requirement (no Team ID), so
|
||||
when the self-updater rebuilds the bundle in place with a fresh build (a new,
|
||||
different cdhash) Gatekeeper/LaunchServices treats the changed code as
|
||||
tampering and macOS reports "Hermes is damaged and can't be opened." The
|
||||
bundle also inherits the com.apple.quarantine flag from the downloaded
|
||||
installer process chain. Both make the relaunch fail.
|
||||
|
||||
Clearing the quarantine xattrs and re-applying a clean deep ad-hoc signature
|
||||
(omitting the hardened-runtime flag, which is meaningless without a real
|
||||
Developer ID) lets the rebuilt app relaunch. No-op when a real signing
|
||||
identity is configured (CSC_LINK / APPLE_SIGNING_IDENTITY) so a properly
|
||||
signed/notarized build is never clobbered. Best-effort: never raises.
|
||||
"""
|
||||
if sys.platform != "darwin":
|
||||
return
|
||||
if os.environ.get("CSC_LINK") or os.environ.get("APPLE_SIGNING_IDENTITY"):
|
||||
return
|
||||
exe = _desktop_packaged_executable(desktop_dir)
|
||||
if exe is None:
|
||||
return
|
||||
# exe = .../Hermes.app/Contents/MacOS/Hermes -> app bundle = .../Hermes.app
|
||||
app = exe.parents[2]
|
||||
if not str(app).endswith(".app") or not app.is_dir():
|
||||
return
|
||||
codesign = shutil.which("codesign")
|
||||
if not codesign:
|
||||
return
|
||||
try:
|
||||
subprocess.run(["xattr", "-cr", str(app)], check=False)
|
||||
subprocess.run([codesign, "--force", "--deep", "--sign", "-", str(app)], check=False)
|
||||
except Exception as exc:
|
||||
print(f" (warning: macOS relaunch fixup skipped: {exc})")
|
||||
|
||||
|
||||
def cmd_gui(args):
|
||||
"""Build and launch the native Electron desktop GUI."""
|
||||
desktop_dir = PROJECT_ROOT / "apps" / "desktop"
|
||||
@ -6964,6 +7001,11 @@ def cmd_gui(args):
|
||||
print(f" Run manually: cd apps/desktop && npm run {build_script}")
|
||||
sys.exit(build_result.returncode or 1)
|
||||
packaged_executable = _desktop_packaged_executable(desktop_dir)
|
||||
if not source_mode:
|
||||
# Locally-built apps are ad-hoc signed; make them relaunchable after
|
||||
# an in-place self-update (otherwise macOS reports "Hermes is
|
||||
# damaged"). No-op on non-macOS and on real-identity builds.
|
||||
_desktop_macos_relaunchable_fixup(desktop_dir)
|
||||
|
||||
# --build-only: produce the artifact but do NOT launch. The installer's
|
||||
# --update flow drives the rebuild headlessly and then launches the desktop
|
||||
|
||||
@ -2390,6 +2390,18 @@ install_desktop() {
|
||||
fi
|
||||
log_success "Desktop app built: $app"
|
||||
|
||||
# macOS: make the locally-built (ad-hoc) app relaunchable after an in-place
|
||||
# self-update. An ad-hoc bundle has no stable Designated Requirement, so a
|
||||
# later in-place rebuild (new cdhash) plus the inherited quarantine flag
|
||||
# trips Gatekeeper's tamper check ("Hermes is damaged and can't be opened").
|
||||
# Strip quarantine + re-apply a clean deep ad-hoc signature (no
|
||||
# hardened-runtime flag, which an ad-hoc build can't satisfy). Skipped when a
|
||||
# real signing identity is configured so a signed build isn't clobbered.
|
||||
if [ "$OS" = "macos" ] && [ -z "${CSC_LINK:-}" ] && [ -z "${APPLE_SIGNING_IDENTITY:-}" ] && command -v codesign >/dev/null 2>&1; then
|
||||
xattr -cr "$app" 2>/dev/null || true
|
||||
codesign --force --deep --sign - "$app" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# `npm install` + `npm run pack` rewrite lockfiles; restore them so the
|
||||
# checkout stays clean for the next `hermes update`.
|
||||
restore_dirty_lockfiles "$INSTALL_DIR"
|
||||
|
||||
Reference in New Issue
Block a user