macOS 应用发布到 GitHub 的完整流程:DMG、签名、公证与 Release
面向独立分发 macOS 应用的端到端发布手册:准备 Developer ID 与公证凭据、构建和验证可安装 DMG、先打版本 tag,再用 GitHub Draft Release 上传、回下载验收并正式发布。
适用范围:本文面向 不经过 Mac App Store、通过 GitHub Release 直接分发的 macOS 应用。最终产物是用户可直接安装的 DMG,而不是开发测试包或 GitHub 自动生成的源码压缩包。本文按 2026-08-02 可用的 Apple 与 GitHub 官方流程整理。
先理解这条发布链路
一次可信的直接分发,不是“把 App 压进 DMG 后上传”这么简单,而是让版本、代码来源、签名身份、公证结果和公开资产形成一条可追溯的链:
- 确定版本号和最终提交。
- 把最终提交推送到 GitHub。
- 先创建并推送版本 tag。
- 从这个 tag 对应的干净源码构建 Release 版本。
- 使用 Developer ID Application 签名 App 及其所有嵌套组件。
- 制作并签名 DMG。
- 把最终 DMG 提交给 Apple 公证,等待 Accepted。
- 将公证票据 staple 到 DMG。
- 对最终字节做签名、Gatekeeper、磁盘镜像和 SHA-256 验证。
- 创建 GitHub Draft Release,上传 DMG 与校验文件。
- 从 Draft 回下载资产,重新验收。
- 发布 Release,并从公开下载入口再做一次真实安装测试。
这里有五个容易混淆的概念:
- Developer ID 签名:证明软件由哪个 Apple 开发团队发布,并保护签名后的内容不被篡改。
- Apple 公证:Apple 的自动化恶意软件与签名检查,不等同于 App Review。
- Stapling:把公证票据附着到 DMG,让离线 Mac 也能确认公证结果。
- Git tag:固定仓库历史中的某个提交。
- GitHub Release:基于 tag 的公开发布页,可附带说明和 DMG 等二进制资产。
一、开始前需要准备什么
1. Apple Developer Program 会员资格
为什么需要
免费 Apple 开发者账号可以本机开发测试,但不能完成面向公众的 Developer ID 直接分发。Developer ID 证书和 Apple 公证服务属于 Apple Developer Program 的分发能力。
在哪里获取
在 Apple Developer Program 注册页面以个人或组织身份加入。Apple 当前公开价格为每年 99 美元,实际金额、税费和本地货币以注册页为准。个人账号需要开启双重认证;组织账号还需要组织身份和 D-U-N-S 等材料。
2. Apple Account、Team ID、Bundle ID 与能力配置
为什么需要
- Apple Account 用于登录开发者服务。
- Team ID 标识签名所属团队。
- Bundle ID 唯一标识 App;扩展、Helper 通常有各自的 Bundle ID。
- App Group、iCloud、推送、File Provider 等能力需要在 App、扩展、Identifier 和 provisioning profile 之间保持一致。
在哪里查看或配置
- Team ID:登录 Apple Developer AccountSign In - Apple。idmsa.apple.com。Sign in to your Apple Account 后查看 Membership details。
- Identifiers 与能力:Apple Developer 的 Certificates, Identifiers & Profiles。
- Xcode:Target → Signing & Capabilities。
如果应用包含扩展,必须逐个核对主 App 与扩展的 Team ID、Bundle ID、App Group 和 entitlement。只检查宿主 App 不够。
3. Developer ID Application 证书和匹配私钥
为什么需要
Developer ID Application 是直接分发 macOS App、扩展、框架、Helper 和 DMG 的签名身份。Gatekeeper 会据此确认开发团队与文件完整性。
不要选错证书
- Developer ID Application:签 App、可执行代码和 DMG,本流程需要。
- Developer ID Installer:签直接分发的 flat PKG;纯 DMG 发布不需要。
- Apple Development:仅用于开发测试,不能作为公开 DMG 的正式身份。
- Mac App Distribution / Mac Installer Distribution:用于 Mac App Store,不是 GitHub 直接分发。
在哪里获取
方式一是让 Xcode 管理:Xcode → Settings → Accounts → 选择团队 → Manage Certificates。
方式二是手动创建:
- 打开“钥匙串访问”。
- 选择“证书助理 → 从证书颁发机构请求证书”,生成 CSR。
- 在 Certificates, Identifiers & ProfilesSign In - Apple。idmsa.apple.com。Sign in to your Apple Account 新建 Developer ID Application。
- 上传 CSR,下载 .cer 并双击安装。
Apple 的详细步骤见 Developer ID certificates 与 创建 CSR。
CSR 生成时,机器会创建私钥;下载回来的 .cer 只有证书,不包含私钥。因此“钥匙串里能看到证书”不代表能签名,必须在“我的证书”中看到证书下面展开出的匹配私钥。
security find-identity -v -p codesigning输出中应存在目标团队的 Developer ID Application 且被识别为 valid identity。换 Mac 或在 CI 构建时,需要安全迁移包含证书和私钥的密码保护 .p12;不要把 .p12、私钥或密码提交到仓库。
4. Apple 公证凭据
公证凭据只负责让 notarytool 登录 Apple 公证服务,它不能代替 Developer ID 私钥。反过来,Developer ID 证书也不能代替公证凭据。
方案 A:Apple Account + App 专用密码
这是个人开发者最直观的方式。
为什么不是 Apple Account 主密码
Apple Account 开启双重认证后,命令行工具使用单独的 App 专用密码,避免主密码被脚本保存或收集。
在哪里获取
登录 account.apple.comApple Account。Apple Account。Your account you use for all Apple services → 登录与安全 → App 专用密码 → 生成。生成后立即放进密码管理器;修改或重置 Apple Account 主密码会使现有 App 专用密码全部失效。Apple 官方说明见 App 专用密码Sign in to apps with your Apple Account using app-specific passwords - Apple Support。Apple Support。App-specific passwords allow you to sign in to your Apple Account in apps made by developers other than Apple.。
不要把密码直接写进脚本。让 notarytool 安全提示输入,并保存到 macOS 钥匙串:
xcrun notarytool store-credentials "notary-prod" --apple-id "YOUR_APPLE_ACCOUNT" --team-id "YOUR_TEAM_ID"其中 notary-prod 是本机自定义的 profile 名,不是 Apple 创建的资源。命令会验证凭据并把它存进钥匙串。之后先做连通性检查:
xcrun notarytool history --keychain-profile "notary-prod"方案 B:App Store Connect API Key
更适合 CI 或不希望绑定个人 Apple Account 的自动化环境。
在哪里获取
App Store Connect → Users and Access → Integrations → App Store Connect API。团队首次使用 API 时可能需要 Account Holder 申请访问。创建后保存:
- Key ID
- Issuer ID
- 只能下载一次的 AuthKey_*.p8 私钥文件
入口和角色说明见 App Store Connect API。p8 文件丢失后不能再次下载;泄露时应立即吊销并换新。
xcrun notarytool store-credentials "notary-ci" --key "/SECURE_PATH/AuthKey_KEY_ID.p8" --key-id "KEY_ID" --issuer "ISSUER_UUID"无论使用哪种方案,都不要把 App 专用密码、p8、p12、解密密码或真实凭据写进 README、Release notes、构建日志或 Git 仓库。
5. Xcode、命令行工具和 GitHub 权限
需要:
- 当前受支持的 Xcode 和 Xcode Command Line Tools。
- Git。
- GitHub 仓库的 write 权限。
- GitHub CLIGitHub CLI。Take GitHub to the command line;macOS 可通过 Homebrew 安装,也可按官网方式安装。
- 已完成 GitHub CLI 登录。
xcodebuild -version
xcrun notarytool --version
git --version
gh --version
gh auth login
gh auth statusgh auth login 的浏览器授权通常比手工创建长期 PAT 更省心。确需 PAT 时优先使用权限和仓库范围更小、带过期时间的 fine-grained token,并像密码一样保管。
二、把项目配置成可直接分发
1. 版本号与构建号
macOS App 通常至少有两组版本:
- Marketing Version / CFBundleShortVersionString:用户看到的版本,例如 1.2.3。
- Build Number / CFBundleVersion:每次构建递增的整数,例如 42。
主 App、扩展和 Helper 的版本应一致或遵循项目明确约定。DMG 文件名、tag 和 Release 标题也应对应同一 Marketing Version。
推荐规则:
- tag:v1.2.3
- Release 标题:AppName v1.2.3
- DMG:AppName-1.2.3.dmg
- 校验文件:AppName-1.2.3.dmg.sha256
GitHub 不会把名为“1”的 tag 自动显示成应用版本号。页面显示什么,取决于实际创建的 tag 名。如果显示“1”,就说明 tag 本身叫“1”,应在发布前删除错误 tag 并按统一规则重建;已经公开或启用不可变发布后,不应移动或复用 tag,而应发布新版本。
2. Release 配置、Hardened Runtime 与 entitlement
逐个 Target 检查:
- 构建配置为 Release。
- 签名团队正确。
- 直接分发使用 Developer ID Application。
- Hardened Runtime 已开启。
- 所有嵌套组件都能独立签名。
- entitlement 只包含实际需要的能力。
- 正式包不存在
com.apple.security.get-task-allow=true。 - 受限能力有匹配的 Developer ID provisioning profile。
- 最低 macOS 版本和 CPU 架构符合对外说明。
Apple 只公证启用 Hardened Runtime 的 macOS 应用。它与 App Sandbox 是两项不同能力:直接分发必须满足 Hardened Runtime 要求,但并非所有直接分发 App 都统一要求开启 App Sandbox。参见 Configuring the hardened runtimeConfiguring the hardened runtime | Apple Developer Documentation。Apple Developer Documentation。Protect the runtime integrity of your macOS app by restricting access to sensitive resources and preventing common exploits.。
查看最终签名里的 entitlement,而不是只看源码中的 entitlements 文件:
codesign -d --entitlements :- "/PATH/AppName.app" 2>/dev/null如果手工签名,要从最深层的 Framework、XPC、Helper、Extension 开始,逐层向外签,最后签主 App。不要用 codesign --deep 代替正确的签名顺序;--deep 可以用于最终验证,但不适合拿来“自动修复”所有嵌套代码。
三、冻结最终提交并先打 tag
1. 为什么 tag 必须先于 Release
GitHub Release 建立在 tag 上。若直接运行 gh release create 而远端没有这个 tag,CLI 可以从默认分支自动创建 tag,容易把 Release 指向错误提交。正式流程应自己先创建、推送并核对 tag,然后使用 --verify-tag 阻止自动创建。
2. 版本 tag 规则
建议采用语义化版本:
- 正式版:v1.2.3
- 预览版:v1.2.3-beta.1,并把 Release 标为 prerelease
tag、版本号、DMG 文件名和 Release 标题必须互相匹配。新发布推荐 annotated tag:
VERSION="1.2.3"
TAG="v$VERSION"
git status --short
git push origin HEAD
git tag -a "$TAG" -m "$TAG"
git push origin "$TAG"发布前确认工作区没有未纳入版本的发布代码。一个常见稳妥做法是从 tag 建立独立 worktree 构建,避免本地未提交文件混进产物:
git fetch origin --tags
git worktree add "../AppName-release-$TAG" "$TAG"核对 tag 最终指向的 commit:
git rev-parse HEAD
git rev-parse "$TAG^{commit}"
git ls-remote origin "refs/tags/$TAG" "refs/tags/$TAG^{}"annotated tag 在远端可能显示两行:第一行是 tag object 的 SHA,带 ^{} 的一行才是被 tag 指向的 commit SHA。这不是 tag 错乱。
不要把 Git tag 签名与 App 的 Developer ID 签名混为一谈。 Git tag 可以再使用 GPG、SSH 或 S/MIME 签名,证明 Git 元数据来源;Developer ID 签名保护的是 macOS 可执行产物。二者解决的问题不同。
四、构建 Developer ID 版本
推荐由 Xcode Archive + developer-id export 生成正式 App,而不是把 DerivedData 中的开发包直接拿来发布。
xcodebuild -project "AppName.xcodeproj" -scheme "AppName" -configuration Release -destination "generic/platform=macOS" -archivePath "build/AppName.xcarchive" archive
xcodebuild -exportArchive -archivePath "build/AppName.xcarchive" -exportPath "build/export" -exportOptionsPlist "ExportOptions.plist"developer-id 导出配置的核心字段如下,实际项目还应按 Xcode 当前版本和签名方式调整:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>developer-id</string>
<key>signingStyle</key>
<string>automatic</string>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
</dict>
</plist>构建后,先验最内层扩展,再验宿主 App:
codesign --verify --strict --verbose=2 "AppName.app/Contents/PlugIns/Extension.appex"
codesign --verify --deep --strict --verbose=2 "AppName.app"
codesign -dv --verbose=4 "AppName.app" 2>&1输出应能确认:
- Authority 是 Developer ID Application。
- TeamIdentifier 正确。
- Runtime Version 存在。
- 主 App 与扩展的版本、Team ID 和能力符合预期。
如果看到 Apple Development、adhoc,或缺少匹配私钥,应立即停止,不能继续制作公开 DMG。
五、制作并签名 DMG
一个面向普通用户的安装型 DMG 通常包含:
AppName.app- 指向 /Applications 的快捷方式
- 可选的 Finder 背景、图标和窗口布局
先把这些内容放入 staging 目录,再创建压缩磁盘镜像:
mkdir -p "build/dmg-root"
ditto "build/export/AppName.app" "build/dmg-root/AppName.app"
ln -s /Applications "build/dmg-root/Applications"
hdiutil create -volname "AppName" -srcfolder "build/dmg-root" -format UDZO -ov "dist/AppName-1.2.3.dmg"对最终 DMG 使用同团队的 Developer ID Application 签名:
codesign --force --timestamp --sign "Developer ID Application: YOUR NAME (YOUR_TEAM_ID)" "dist/AppName-1.2.3.dmg"
codesign --verify --verbose=2 "dist/AppName-1.2.3.dmg"DMG 内部的 App 必须在打包前已经完成签名;给外层 DMG 签名不会自动修复内部 App。
六、提交 Apple 公证并 staple
Apple 已停止接受 altool 公证,应使用 notarytool。官方命令行流程见 Customizing the notarization workflowCustomizing the notarization workflow | Apple Developer Documentation。Apple Developer Documentation。Notarize your app from the command line to handle special distribution cases.。
xcrun notarytool submit "dist/AppName-1.2.3.dmg" --keychain-profile "notary-prod" --wait --output-format json需要保存:
- submission ID
- status
- 提交时间
- 公证日志
只有 status 为 Accepted 才能继续。即便 Accepted,也建议下载日志检查 warning:
xcrun notarytool log "SUBMISSION_ID" --keychain-profile "notary-prod" "notary-log.json"Apple 通过后,把票据附着到实际发布的 DMG:
xcrun stapler staple "dist/AppName-1.2.3.dmg"
xcrun stapler validate "dist/AppName-1.2.3.dmg"顺序必须是:签名 App → 制作并签名 DMG → 公证 DMG → staple DMG → 最终校验 → 生成 SHA-256。Staple 会修改 DMG 的最终字节,因此 checksum 不能提前生成。
七、在上传前做完整验证
1. 容器和签名验证
hdiutil verify "dist/AppName-1.2.3.dmg"
codesign --verify --verbose=2 "dist/AppName-1.2.3.dmg"
xcrun stapler validate "dist/AppName-1.2.3.dmg"挂载 DMG 后继续验证里面的 App:
MOUNT_DIR="$(mktemp -d)"
hdiutil attach -nobrowse -mountpoint "$MOUNT_DIR" "dist/AppName-1.2.3.dmg"
codesign --verify --deep --strict --verbose=2 "$MOUNT_DIR/AppName.app"
spctl --assess --type execute --verbose=2 "$MOUNT_DIR/AppName.app"
hdiutil detach "$MOUNT_DIR"在支持的系统上还可用:
syspolicy_check distribution "/PATH/AppName.app"理想结果应表明 App 被接受,来源为 Notarized Developer ID。
2. Gatekeeper 的真实首次启动测试
命令行验证不能完全替代真实用户路径。最可靠的验收方式是:
- 使用从未运行过该版本的干净 Mac、干净用户或 VM 快照。
- 从最终 GitHub 公开链接用 Safari 或 Chrome 下载,确保产生真实 quarantine 属性。
- 可在断网后挂载 DMG,以确认 stapled ticket 可离线工作。
- 把 App 拖到 Applications。
- 首次启动并实际使用核心功能。
- 每次重测恢复 VM 快照,避免 Gatekeeper 缓存造成假通过。
gh release download、curl、scp 下载的文件不一定带浏览器路径中的 quarantine,因此“CLI 下载后能打开”不能单独证明普通用户首次下载体验正常。
3. 对最终字节生成 SHA-256
必须在公证、staple 和所有最终修改完成后执行:
cd dist
shasum -a 256 "AppName-1.2.3.dmg" > "AppName-1.2.3.dmg.sha256"
shasum -a 256 -c "AppName-1.2.3.dmg.sha256"校验文件应只记录相对文件名,避免把本机绝对路径写进公开资产。
4. Mirage 项目的现成命令
Mirage 当前的发布脚本已经串联 Archive、Developer ID 导出、App 与 File Provider 验签、DMG 制作、DMG 签名、公证、staple、Gatekeeper 检查、hdiutil verify 和 SHA-256:
NOTARY_KEYCHAIN_PROFILE="MirageNotary" RELEASE_MODE="developer-id" ./Scripts/build_dmg.shprofile 名不是秘密,但必须换成当前 Mac 实际保存的 keychain profile。development 模式只能供本机测试,不能作为 GitHub 的正式可安装资产。
八、编写 Release notes
Release notes 应只描述当前版本真实存在并经过验证的内容。推荐固定结构:
## Summary
一句话说明本版本解决什么问题。
## Changelog
- 新增或调整的真实功能
- 修复的真实问题
- 必要的兼容性变化
## Installation
1. 下载 AppName-1.2.3.dmg。
2. 打开 DMG。
3. 将 AppName 拖到 Applications。
4. 从 Applications 启动。
## Requirements
- macOS 最低版本
- 支持的 CPU 架构
- 首次使用需要的系统权限或网络条件
## Assets
- AppName-1.2.3.dmg:已签名、公证并 stapled 的安装镜像。
- AppName-1.2.3.dmg.sha256:DMG 的 SHA-256 校验文件。不要把计划中的功能、未验证的兼容性、没有实现的自动更新或“绝对安全”等表述硬写进 Release notes。
九、创建 GitHub Draft Release
1. 先确认 GitHub CLI 指向正确仓库
git remote -v
gh repo view
gh auth status2. 创建 Draft 并上传资产
TAG="v1.2.3"
gh release create "$TAG" \
--verify-tag \
--draft \
--title "AppName v1.2.3" \
--notes-file "release-notes.md" \
"dist/AppName-1.2.3.dmg#AppName 1.2.3 for macOS" \
"dist/AppName-1.2.3.dmg.sha256#SHA-256 checksum"--verify-tag 会在远端 tag 不存在时直接失败,避免 CLI 自动创建错误 tag。Draft 的意义是先把页面和资产完整组装好,再做回下载验收;它不是最终公开状态。
GitHub 每个 Release 最多可附加 1000 个上传资产,每个文件必须小于 2 GiB。对普通 DMG 足够,但仍应确认上传完成而不是只看命令退出。
3. 检查 Draft 元数据
gh release view "$TAG" --json tagName,targetCommitish,isDraft,isPrerelease,assets,url
gh api "repos/OWNER/REPO/releases/tags/$TAG" --jq '{tag_name,draft,prerelease,immutable,assets:[.assets[]|{name,size,state,content_type,digest,browser_download_url}]}'至少核对:
- tagName 与目标版本一致。
- isDraft 为 true。
- prerelease 状态符合版本类型。
- DMG 与 sha256 两个资产都存在。
- 资产 state 为 uploaded。
- 文件名、大小、内容类型和服务端 digest 合理。
- tag 的 peeled commit 与本地最终 commit 一致。
十、从 Draft 回下载并独立验收
上传成功不代表 GitHub 上的字节一定正确。应下载到空临时目录:
VERIFY_DIR="$(mktemp -d)"
gh release download "$TAG" --pattern "*.dmg" --pattern "*.sha256" --dir "$VERIFY_DIR"
(cd "$VERIFY_DIR" && shasum -a 256 -c "AppName-1.2.3.dmg.sha256")
cmp "dist/AppName-1.2.3.dmg" "$VERIFY_DIR/AppName-1.2.3.dmg"
hdiutil verify "$VERIFY_DIR/AppName-1.2.3.dmg"
codesign --verify --verbose=2 "$VERIFY_DIR/AppName-1.2.3.dmg"
xcrun stapler validate "$VERIFY_DIR/AppName-1.2.3.dmg"然后挂载回下载的 DMG,重复 App 的 codesign、spctl 和核心功能检查。正式验收必须始终显式指定 tag;省略 tag 时,gh release download 可能下载当前 Latest,而不是正在检查的版本。
如果环境中安装了可复用发布 skill,也可以用独立验证脚本对最终 DMG 做一次与构建脚本分离的检查:
$HOME/.agents/skills/publish-macos-github-release/scripts/verify_macos_dmg.sh "/PATH/AppName-1.2.3.dmg"独立验证的价值在于避免“生成产物的脚本和验证产物的脚本犯了同一个错误”。
十一、正式发布并做公开验证
Draft 验收通过后发布:
gh release edit "$TAG" --draft=false --latest稳定版可显式设为 Latest;预览版应使用 prerelease,不应设为 Latest。GitHub 也可以按日期和语义化版本自动判断 Latest,但正式流程最好检查结果,而不是依赖列表顺序。
发布后继续验证:
gh release view "$TAG" --json tagName,targetCommitish,isDraft,isPrerelease,isImmutable,assets,url
gh release list --json tagName,isDraft,isPrerelease,isLatest,isImmutable还要完成两项 Draft 阶段无法完全替代的检查:
- 使用未登录 GitHub 的浏览器确认 Release 页面和 DMG 可匿名下载。
- 在干净 Mac 或 VM 中通过浏览器下载、安装并首次启动。
GitHub 会自动在每个 Release 下生成 Source code (zip) 和 Source code (tar.gz)。它们是 tag 对应的源码快照,不是可安装程序,也不需要手工上传。面向普通用户的安装说明必须明确指向 DMG。
十二、建议开启不可变 Release
在仓库 Settings 的 Releases 区域可启用 release immutability。它只保护启用之后发布的 Release。发布后:
- 关联 tag 不能移动或删除。
- Release assets 不能修改或删除。
- GitHub 自动生成包含 tag、commit SHA 和资产摘要的 release attestation。
- 标题和 Release notes 仍可编辑。
GitHub 官方推荐的流程正是:先建 Draft → 上传全部资产 → 验证 → 发布。参见 Immutable releasesImmutable releases - GitHub Docs。GitHub Docs。Learn about immutable releases and how they can help you maintain the integrity of your software supply chain. 与 Preventing changes to your releasesPreventing changes to your releases - GitHub Docs。GitHub Docs。You can enforce immutable releases for a repository or organization to prevent potential vulnerabilities.。
启用后可以验证发布证明与本地资产:
gh release verify "$TAG"
gh release verify-asset "$TAG" "$VERIFY_DIR/AppName-1.2.3.dmg"不要把 gh release upload --clobber 纳入正式流程。它会先删除同名旧资产再上传新文件;新上传失败时,旧资产已经丢失。公开资产有误时,正确做法是发布新的 patch 版本,而不是静默替换同名 DMG。
十三、用户升级时出现“与之前打开的版本不同”是什么意思
macOS 可能提示:
“App 与之前打开的版本不同。打开将允许它访问之前版本中的数据。”
这通常表示新 App 的代码签名 designated requirement 或开发团队身份与本机之前运行过的同 Bundle ID 版本不同。常见情况:
- 以前安装的是 Apple Development 签名,后来换成 Developer ID Application。
- 以前是 ad-hoc 或未签名构建。
- Team ID、Bundle ID 或签名链发生变化。
- App 被第三方重新签名,或文件被修改。
它不是“公证失败”的固定提示,而是 macOS 在提醒用户:一个签名身份不同的程序正在尝试继承旧版本的数据访问关系。首次从开发版切换到稳定 Developer ID 版时可能只出现一次;以后持续使用同一团队、同一 Bundle ID 和稳定的 Developer ID 分发身份,通常不会反复出现。若正式版本之间仍频繁出现,应立即比较新旧 App 的签名详情、TeamIdentifier、Identifier 和 designated requirement。
codesign -dv --verbose=4 "/Applications/AppName.app" 2>&1
codesign -dr - "/Applications/AppName.app" 2>&1十四、常见失败与定位方法
找不到 Developer ID identity
现象:security find-identity 没有目标证书,或 codesign 报 identity not found。
检查:证书是否过期或撤销;钥匙串“我的证书”中是否有匹配私钥;Team ID 是否正确。只有 .cer、没有私钥时不能签名。
notarytool 凭据无效
检查:
- App 专用密码是否输错。
- Apple Account 主密码是否刚被修改,导致 App 专用密码被全部撤销。
- Team ID 是否属于该账号。
- keychain profile 名是否拼写一致。
- API Key 的 Key ID、Issuer ID 和 p8 是否匹配且未吊销。
先运行 notarytool history,确认凭据可用,再开始耗时构建。
公证状态为 Invalid
使用 submission ID 下载日志,不要只看终端最后一行。常见原因包括嵌套组件未签名、Hardened Runtime 缺失、secure timestamp 缺失、get-task-allow=true、entitlement 不匹配和签名后文件被修改。
Accepted 但用户仍被 Gatekeeper 拒绝
Accepted 只证明公证服务接受提交,不代表所有终端环境都一定通过。继续检查:
- 是否把票据 staple 到实际上传的 DMG。
- 上传后是否又修改或重签了 DMG。
- App 和 DMG 的 codesign 是否都通过。
- spctl 或 syspolicy_check 是否通过。
- 浏览器下载的 quarantine 路径是否通过。
- 用户下载到的是否真是当前 tag 的 DMG,而不是旧缓存或错误资产。
checksum 不一致
最常见原因是 checksum 在 staple 之前生成,或生成后 DMG 又被重签、重建、替换。删除错误的未公开 Draft 资产,重新从最终字节生成;已经公开时发布新 patch 版本,不覆盖原资产。
GitHub 上只有 Source code,没有可安装文件
GitHub 自动生成的 ZIP/TAR 只是源码快照。必须把最终 DMG 作为 Release asset 明确上传,并在 Release notes 的 Assets 与 Installation 中说明下载哪个文件。
tag 指向错误提交
如果还在未公开 Draft 阶段,停止发布,删除错误 Draft 和未公开 tag,再从正确提交重新创建。如果已公开,尤其启用了不可变发布,不要移动旧 tag;修正版本号并发布新 Release。
十五、可选的 CI 自动化
自动化不能降低签名和凭据安全要求。典型 CI 还需要:
- 将 Developer ID 证书和私钥导出为强密码保护的 p12。
- 把 p12、p12 密码和公证凭据放进 GitHub Actions encrypted secrets,而非仓库。
- 在临时 keychain 中导入 identity,构建结束后清理 runner。
- 公证优先使用可吊销、可独立管理的 App Store Connect API Key。
- 使用最小 GitHub token 权限。
- 让 workflow 只从受保护的版本 tag 触发。
- 上传 Draft,完成独立验证后再发布。
- 日志中屏蔽所有秘密,不输出 security dump 或环境变量全集。
CI 只是把同一发布契约自动化,不能省略 Developer ID、Hardened Runtime、公证、staple、checksum、回下载验收和真实首次启动测试。
官方资料
Apple
- Apple Developer Program
- Developer ID certificates
- Notarizing macOS software before distributionNotarizing macOS software before distribution | Apple Developer Documentation。Apple Developer Documentation。Give users even more confidence in your macOS software by submitting it to Apple for notarization.
- Customizing the notarization workflowCustomizing the notarization workflow | Apple Developer Documentation。Apple Developer Documentation。Notarize your app from the command line to handle special distribution cases.
- Configuring the hardened runtimeConfiguring the hardened runtime | Apple Developer Documentation。Apple Developer Documentation。Protect the runtime integrity of your macOS app by restricting access to sensitive resources and preventing common exploits.
- App 专用密码Sign in to apps with your Apple Account using app-specific passwords - Apple Support。Apple Support。App-specific passwords allow you to sign in to your Apple Account in apps made by developers other than Apple.
- App Store Connect API
GitHub
- About releasesAbout releases - GitHub Docs。GitHub Docs。You can create a release to package software, along with release notes and links to binary files, for other people to use.
- Managing releasesManaging releases in a repository - GitHub Docs。GitHub Docs。You can create releases to bundle and deliver iterations of a project to users.
- gh release createGitHub CLI。GitHub CLI。Take GitHub to the command line
- gh release uploadGitHub CLI。GitHub CLI。Take GitHub to the command line
- gh release downloadGitHub CLI。GitHub CLI。Take GitHub to the command line
- gh release editGitHub CLI。GitHub CLI。Take GitHub to the command line
- Immutable releasesImmutable releases - GitHub Docs。GitHub Docs。Learn about immutable releases and how they can help you maintain the integrity of your software supply chain.
- Verifying release integrityVerifying the integrity of a release - GitHub Docs。GitHub Docs。You can avoid tampering and accidental changes by ensuring the releases you use have not been modified after publication.