在云端 Mac mini 上打通 notarytool 公证与 fastlane TestFlight 自动分发

CI/CD 实践 ·约 9 分钟阅读

在云端 Mac mini 上打通 notarytool 公证与 fastlane TestFlight 自动分发

凌晨两点,出海团队的运营在群里问"新版本什么时候能进 TestFlight",而你正对着终端里第三次报错的 codesign 发呆——公证链路只要有一个环节配置不对,整套流程就会在提交那一步安静地卡住,不报错也不通过,只留一句模糊的状态码。这篇文章记录我们在云端 Mac mini 节点上把签名、公证、上传 TestFlight 这三步彻底打通、跑成无人值守流水线的完整过程。

为什么公证是出海团队的隐形卡点

从 Xcode 13 起,altool 的公证接口已经废弃,苹果统一收口到 notarytool。很多团队的 CI 脚本还停留在老教程的写法,证书类型、时间戳参数、entitlements 配置任何一处不对,公证请求会被静默拒绝,错误信息藏在一份要单独拉取的 JSON 日志里。更麻烦的是,公证依赖苹果服务端排队,本地根本无法复现"为什么这次慢了 40 分钟",只能靠标准化流程减少变量。

把这套流程放在云端 Mac mini 上跑,好处是证书、API Key、keychain 状态都固化在一个长期在线的节点上,不会因为换了台笔记本或者团队成员离职就要重新导证书——这也是我们坚持"独享物理机、按周期租用"而不是每次现开临时环境的原因。

准备工作:证书与 API Key

先确认三件事都齐了:

  1. Apple Distribution 证书(用于 App Store 分发)+ 对应私钥,导出为 .p12 后导入节点 keychain。
  2. App Store Connect API Key:在 App Store Connect 的用户和访问页面生成,下载 .p8 私钥文件,记下 Key ID 和 Issuer ID。这是全程不需要登录任何 Apple 账号的关键——公证与上传都走 API Key 鉴权。
  3. provisioning profile:与 Bundle ID、证书匹配,建议用 fastlane match 统一托管,避免每台机器各自生成。

导入证书到节点 keychain:

security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security import DistributionCert.p12 -k build.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
security list-keychains -d user -s build.keychain login.keychain

.p8 文件放到节点固定路径,例如 ~/.appstoreconnect/private_keys/AuthKey_ABCDE12345.p8,权限设为 600,不要提交进任何代码仓库。

用 notarytool 完成签名到公证

签名时必须带时间戳和硬化运行时,否则公证会因为缺少 secure timestamp 直接失败:

codesign --sign "Apple Distribution: Your Company" \
  --timestamp --options runtime \
  --entitlements App.entitlements \
  build/App.app

打包成 .pkg 或直接用 xcodebuild -exportArchive 产出 .ipa 后,提交公证:

xcrun notarytool submit build/App.ipa \
  --key ~/.appstoreconnect/private_keys/AuthKey_ABCDE12345.p8 \
  --key-id ABCDE12345 \
  --issuer 69a6de7x-xxxx-47e3-e053-5b8c7c11a4d1 \
  --wait

--wait 会阻塞直到苹果服务端返回结果,通常 3~8 分钟,偶尔遇到排队高峰会到 20 分钟以上。拿到 submissionId 后,如果状态是 Invalid,一定要拉详细日志再排查,别凭猜测重签:

xcrun notarytool log <submissionId> \
  --key ~/.appstoreconnect/private_keys/AuthKey_ABCDE12345.p8 \
  --key-id ABCDE12345 --issuer 69a6de7x-xxxx-47e3-e053-5b8c7c11a4d1

我们踩过的一个坑:entitlements 里开了 App Sandbox 但某个第三方动态库没有对应签名,notarytool 的日志只会提示 "The binary is not signed with a valid Developer ID certificate",看起来像证书问题,实际是子模块漏签——用 codesign --verify --deep --verbose=4 逐层检查才能定位到具体文件。

用 fastlane pilot 自动分发到 TestFlight

公证通过后,用 fastlane pilot 上传到 TestFlight,同样走 API Key 鉴权,不需要交互式登录:

lane :beta do
  api_key = app_store_connect_api_key(
    key_id: ENV["ASC_KEY_ID"],
    issuer_id: ENV["ASC_ISSUER_ID"],
    key_filepath: ENV["ASC_KEY_PATH"]
  )

  build_app(scheme: "App", export_method: "app-store")

  pilot(
    api_key: api_key,
    skip_waiting_for_build_processing: true,
    changelog: "自动化分发:修复启动崩溃与网络重试逻辑"
  )
end

skip_waiting_for_build_processing: true 让脚本上传完就退出,不阻塞节点上的其他任务;处理进度改用轮询或者等苹果的邮件通知即可,没必要把 CI 任务耗在等待上。

常见报错与排查清单

报错关键词 大概率原因 处理方式
signature does not include a secure timestamp codesign 没加 --timestamp 重新签名并加上 --timestamp --options runtime
The binary is not signed with a valid Developer ID 某个内嵌 framework/动态库未签名 codesign --verify --deep 逐层排查后单独签名
Missing Info.plist key: ITSAppUsesNonExemptEncryption 使用了加密相关 API 但未声明 Info.plist 加对应键值,或按实际情况声明豁免
Invalid Provisioning Profile profile 与证书/Bundle ID 不匹配 fastlane match 重新同步证书与 profile
提交后长时间无响应 苹果服务端排队 不要重复提交,等待或查询 submissionId 状态

把这张表贴进团队的排障文档,能省下大量"到底是谁的锅"的来回沟通。

把流程钉进云端 Mac mini 的定时任务

最后一步是让流水线自动跑起来,而不是靠人手动触发。在云端 Mac mini 上用 launchd 注册一个响应 Git webhook 或定时轮询的任务,签名、公证、上传三步串联执行,失败时通过邮件或工单系统通知负责人,而不是让脚本静默退出。由于节点是独享物理机、常驻在线,keychain 状态和证书不会像临时容器那样每次重新初始化,公证请求的成功率和排队时间也更稳定。

常见问题

notarytool 提示 The signature does not include a secure timestamp 怎么办?

这是签名时缺少时间戳导致,重新用 codesign --timestamp --options runtime 对二进制签名后再提交公证,不要用旧版 codesign 参数覆盖。

fastlane pilot 上传后 TestFlight 长时间显示处理中正常吗?

苹果处理构建一般 15~60 分钟,超过 2 小时建议查 App Store Connect 活动日志的拒绝邮件,常见原因是 Info.plist 缺少 ITSAppUsesNonExemptEncryption 声明触发人工审核。

在云端 Mac mini 上跑这套流程需要登录 Apple 账号吗?

不需要,只需把 Developer ID/Apple Distribution 证书导入节点 keychain,再把 App Store Connect API Key 的 .p8 文件与环境变量放到节点上即可全程无人值守。

需要一台独享 Mac mini 跑构建?

新加坡/东京/韩国/香港/美西五地物理节点,按天起租,10 分钟内收到 VNC/SSH 凭据。

立即租用 Mac mini