Files
hermes-agent/docs/macos-bundle-updates.md
ethernet da236308fd feat(desktop): wire macOS bundle updates and guarded feed publication
Route packaged macOS bundles and Light through the updater strategy.
Use electron-updater 6.8.9 and wait for native signature acceptance before
backend teardown. Keep checkout and Store ownership separate.

Share Darwin feed paths between packaging, runtime and publication.
Validate both native feeds, verify streamed artifact hashes, prevent
same-tag artifact replacement, and conditionally update the channel
pointer. Protect live feed references during canary retention.

Use one notarization owner. Require publishing credentials and validate
the stapled app. Keep Windows, Linux and Termux jobs unchanged.

Verified with updater/feed unit and transport tests, release-helper tests,
desktop typechecks, the desktop JS build, and workflow lint. No E2E,
native macOS install, release dispatch or public publication was run.
2026-09-06 21:27:58 -04:00

3.1 KiB

macOS bundle updates

The packaged macOS app uses electron-updater. The bundled and Light stamps name that owner. Development and bootstrap installs keep checkout updates. Windows App Installer and Store ownership are unchanged.

Feed contract

apps/desktop/update-feed.cjs defines each channel directory and filename. The builder writes that URL into app-update.yml. The client uses this file unless updates.desktop_feed_base_url supplies an explicit bucket-base URL.

  • Stable: releases/darwin/stable/stable-mac.yml
  • Canary: releases/darwin/canary/canary-mac.yml
  • Light: the same paths with light/ between darwin/ and the channel.
  • Artifacts: releases/tag/TAG/FILENAME, shared by download links and feeds.

The current workflow builds the bundled variant, on ARM64 and Intel runners. Light has separate client/feed routing but no release matrix leg in this change.

r2-release.mjs finalize requires one metadata file for each architecture, named arm64-CHANNEL-mac.yml and x64-CHANNEL-mac.yml. It rejects wrong versions, variants, architectures, hashes and inconsistent legacy path fields. Each referenced ZIP/DMG is streamed back and checked against its SHA-512 and size. Publication checks the live version, conditionally replaces its ETag, and reads back the resulting feed. Same-tag macOS artifacts cannot be overwritten with different bytes. Mutable feeds use Cache-Control: no-store. Canary retention protects the artifacts and blockmaps referenced by live feeds. An unreadable feed prevents pruning.

Client lifecycle

Checks never download automatically. Apply rechecks the release, downloads it, and waits for Squirrel.Mac to accept the signed app. Only then does Hermes stop its app-owned backends and request installation/relaunch. Unrelated quits do not trigger installation. Downloads and native-verification failures leave backends running. Concurrent checks cannot replace an apply operation's target. The existing checkout updater never mutates the sealed app bundle.

Release environment

The existing release-signing environment supplies:

  • CSC_LINK and CSC_KEY_PASSWORD: Developer ID Application signing identity.
  • APPLE_API_KEY_P8, APPLE_API_KEY_ID, APPLE_API_ISSUER: notarization.
  • CLOUDFLARE_R2_ACCOUNT_ID, CLOUDFLARE_R2_ACCESS_KEY_ID, CLOUDFLARE_R2_SECRET_ACCESS_KEY: bucket access secrets.
  • CLOUDFLARE_R2_BUCKET, CLOUDFLARE_R2_PUBLIC_URL: repository/environment vars.

Publishing requires the Apple credentials. The existing after-sign hook owns notarization, so electron-builder's second notarization path is disabled. The publish gate verifies the signature, stapled ticket and Gatekeeper assessment. The Darwin publish job waits for both native builds and serializes channel writes.

Verification limits

Local tests exercise the strategy, native-event ordering, feed validation, conditional publication and retention with injected OS/network boundaries. The desktop TypeScript and JavaScript build run on the development host. These checks are not proof of a signed macOS install or an actual app replacement. No E2E work, release dispatch or public feed publication is included here.