Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading