跳转至

在 Anomaly 名下进行 macOS 签名

将 Hex 的 macOS Developer ID 签名和公证从 Kit 的个人团队(QC99C9JE59、他 Mac 上的本地钥匙串和 HEX_NOTARY_PROFILE)迁移到 GitHub Actions 中的 Anomaly 团队,复用 OpenCode 已经使用的凭据。

OpenCode 的做法

桌面端和 CLI(anomalyco/opencode,位于 dev 分支的 .github/workflows/publish.yml):

team         // Anomaly Innovations, Inc. (5NZ4Q7NXJ4); confirmed from the signed
             // opencode-darwin-arm64 npm binary, not written in the repo
runners      // macos-26 (arm64) and macos-26-intel, runner-default Xcode
certificate  // apple-actions/import-codesign-certs@8f3fb60 (v2.0.0), keychain "build"
             // p12-file-base64: APPLE_CERTIFICATE, p12-password: APPLE_CERTIFICATE_PASSWORD
             // no KEYCHAIN_PASSWORD secret: the action generates one
identity     // security find-identity -v -p codesigning build.keychain
             // | first "Developer ID Application: …"
cli          // job sign-cli-macos: codesign --force --timestamp --options runtime
             // --entitlements packages/cli/script/entitlements.plist (JIT, unsigned
             // memory, dyld env, no library validation: Bun needs them)
desktop      // electron-builder (packages/desktop/electron-builder.config.ts):
             // hardenedRuntime, entitlements resources/entitlements.plist
             // (allow-jit, audio-input), notarize: true, targets dmg + zip
notary       // App Store Connect API key through notarytool (electron-builder):
             // APPLE_API_KEY_PATH secret holds the .p8 *contents*, written to
             // $RUNNER_TEMP/apple-api-key.p8; APPLE_API_KEY holds the key ID;
             // APPLE_API_ISSUER the issuer ID
updates      // electron-updater generic feed; no separate update-signing key
             // (Squirrel.Mac trusts the code signature)
windows      // Azure Trusted Signing (AZURE_* secrets), separate from Apple

该仓库还有 APPLE_ID 和 APPLE_PASSWORD 机密,但没有任何工作流读取它们:使用 Apple ID 加 App 专用密码并不是当前采用的方法。

iOS(anomalyco/opencode-ios,私有):Xcode Cloud 在每次推送到 main 时生成归档并发布到 TestFlight;签名是自动的(project.yml:DEVELOPMENT_TEAM: 5NZ4Q7NXJ4、CODE_SIGN_STYLE: Automatic),不使用 fastlane 或 match。手动备用方案使用 asc CLI 和 scripts/testflight-upload.sh,配合来自 1Password 的 App Store Connect 密钥(docs/releasing.md)。可供 Hex 复用:团队,以及一个 App Store Connect API 密钥。带有 Developer 角色的团队密钥可以为 Mac 应用进行公证,因此 Hex 可以使用与 OpenCode 桌面版相同的密钥。

Hex 的做法

scripts/build-app.sh       // cargo build → Hex.app; signs Moonshine and ONNX dylibs,
                           // HexMicrophone.driver, then the app with --options runtime
                           // and .github/release/hex.entitlements (audio-input,
                           // apple-events; not sandboxed)
                           // HEX_TEST_BUILD=1: ad-hoc "Hex Test.app" (anomaly.hex.test)
scripts/macos-signing.sh   // identity must be "Developer ID Application: … (TEAM)"
scripts/release-app.sh     // clean pushed tree → notarized, stapled app and DMG
                           // (scripts/notarize-app.sh) → dist/hex-<v>-macos-arm64.dmg
                           // and .zip (root Hex.app, the updater's archive)
scripts/validate-app.sh    // bundle id anomaly.hex, team, Developer ID, entitlements,
                           // no Sparkle left
release.yml        // build-macos: HEX_RELEASE_UNSIGNED=1 build-app.sh, no secrets
                           // sign-macos (release env, .github/release only):
                           // macos-credentials.sh imports APPLE_SIGNING_CERTIFICATE and
                           // APPLE_NOTARY_KEY; sign-macos.sh signs inside-out,
                           // notarizes, staples, makes the ZIP and DMG
                           // verify-macos: Gatekeeper, staples, validate-app.sh, the
                           // updater's requirement (app/update-requirement.txt), launch
                           // A check run is the signed candidate; publishes nothing
test-builds.yml            // per-push ad-hoc Hex Test DMG (unsigned)

更新不再使用 Sparkle:应用内更新器(src/updater.rs、docs/plans/release-service.md)从 Anomaly 发布服务下载 ZIP,并在替换进去之前要求 app/update-requirement.txt:anchor apple generic、标识符 anomaly.hex、来自团队 5NZ4Q7NXJ4 的 Developer ID 叶证书,以及 notarized。Manifest 是未签名的,因此该代码签名是 HTTPS 和 manifest 的 SHA-256 之外的完整性检查;没有单独的更新签名密钥(与 OpenCode 的 electron-updater 相同)。

下一步:一旦 APPLE_* 机密就绪,运行一次暂存 Release,并根据更新器的要求检查签名后的构建(verify-macos 会这样做);然后可以选择使用同一个 Developer ID 对 Hex Test.app 签名,这样测试人员只需授予一次权限(需要 build-app.sh 在 HEX_TEST_BUILD=1 时接受团队)。

anomalyco/hex 的机密

添加到 release 环境(仅限于 dev 和 production)。名称与 OpenCode 保持一致;GitHub 无法复制机密值,因此需要管理员从来源(1Password)重新输入:

APPLE_CERTIFICATE           // base64 .p12 of the Anomaly "Developer ID Application" cert + key
APPLE_CERTIFICATE_PASSWORD  // the .p12 password
APPLE_API_KEY_PATH          // App Store Connect API key .p8 contents (OpenCode's naming)
APPLE_API_KEY               // that key's ID
APPLE_API_ISSUER            // the App Store Connect issuer ID

APPLE_ID、APPLE_PASSWORD 和 KEYCHAIN_PASSWORD 不需要。

新标识:anomaly.hex

首个由 Anomaly 签名的发布版本还会更改 bundle ID,并在同一次发布中完成,这样用户只需重新授权一次:

app            // com.kitlangton.hex2               -> anomaly.hex
SDK helper     // com.kitlangton.hex.service        -> anomaly.hex.service
mic driver     // com.kitlangton.hex.microphone.driver -> anomaly.hex.microphone.driver
mic UIDs       // com.kitlangton.hex.microphone[.sink] -> anomaly.hex.microphone[.sink]
test build     // com.kitlangton.hex2.test          -> anomaly.hex.test
Windows event  // Local\com.kitlangton.hex.app      -> Local\anomaly.hex.app

anomaly.hex 是 macOS 上的一款新应用。刻意没有提供迁移代码:旧的首选项域(Sparkle 状态、本地 DisableUpdates)不会被导入;受管理的 DisableUpdates 配置文件必须命名为 anomaly.hex;权限和登录项从头开始。hex Application Support 文件夹不依赖于 bundle ID,因此设置、历史记录、模型和会议会被共享;其实例锁可防止旧副本和新副本同时运行(Windows 上的 windows-listener.lock 也同样如此)。

  • 登录时启动。 SMAppService.mainAppService 按 bundle ID 注册:新应用以未注册状态启动,设置里会显示为关闭。旧的 com.kitlangton.hex2 注册项可能会残留在登录项(后台任务管理)中,直到 macOS 清理或用户手动移除;当对应应用被删除或替换后,它不会启动任何东西。未在真实 Mac 上验证。
  • Hex Microphone 驱动(实验性)。 它安装在固定路径 /Library/Audio/Plug-Ins/HAL/HexMicrophone.driver,因此永远不会出现第二份副本。位于该路径的旧版 com.kitlangton.hex.microphone.driver 无法通过 bundle-ID 检查,并显示为需要更新;新应用不会使用其设备(UID 不同),而“更新”或“卸载”会替换或移除它。如果用户从未打开 Hex 麦克风卡片,旧驱动会保持已安装状态,其“Hex Microphone”设备也会留在系统设备列表中。

现有安装

运行 2.3.1 或更早版本的 Mac 使用 com.kitlangton.hex2 运行 Sparkle;它们的 SUFeedURL 重定向到 https://anoma.ly/hex/files/appcast.xml。它们永远不会自动更新为 anomaly.hex:在服务上的首次发布时,Legacy appcast 工作流会用 packaging/macos/legacy-appcast.xml 替换该 appcast,其中只有一个信息条目(没有 enclosure),链接到 https://anoma.ly/hex。Sparkle 会显示其说明和下载按钮,但不会安装任何内容。它不需要 Sparkle EdDSA 密钥:该条目没有需要签名的归档文件,且 2.3.1 不设置 SURequireSignedFeed。发布前,请将该条目的版本设置为第一个 anomaly.hex 版本。

更换团队的影响

  • 用户必须重新授予权限。 TCC 会根据应用的指定要求记录每次授权,该要求中会注明签名团队。在第一次 Anomaly 签名的更新之后,麦克风、辅助功能、自动化、屏幕录制和系统音频录制(会议)将不再匹配;旧条目可能会保留在列表中但已失效,因此用户可能需要移除并重新添加 Hex。引导流程中的权限健康检查必须显示这一点;发布说明必须明确说明。
  • 登录时启动。 SMAppService.mainAppService 注册由后台任务管理按团队跟踪;切换后,预计该登录项需要重新启用或获得批准。Hex 会轮询 macOS 获取状态(不存储标记),因此设置会显示真实情况;请在真实 Mac 上验证。
  • HexMicrophone.driver。 捆绑的副本由 Anomaly 重新签名;安装在 /Library/Audio/Plug-Ins/HAL 中的副本会保留 Kit 的签名,直到应用内更新重新安装它为止。
  • 钥匙串条目和应用组。 Hex 不存储任何内容,因此没有其他东西会损坏。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 hex19/hex