在 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