无头租赁云 Mac 上的 iOS / macOS 公证:notarytool 与 stapler(2026)
在 香港、日本、韩国、新加坡、美国 租用的 裸金属 Apple Silicon 云 Mac 上,团队通常用 SSH 跑 CI/CD。若要把签过名的 macOS .app、命令行工具或 .dmg 在 App Store 之外分发,仍要走 Apple 的公证(Notarization)。本文给出 2026 可落地流程:用登录钥匙串保存 notarytool 凭据、非交互提交、保留 JSON 日志与 submission id、成功后 stapler staple 并 stapler validate,以及在流水线里设硬门禁。可配合 远程 Archive 与导出、远程签名优化、仅 CLT 与完整 Xcode 一起阅读。
为什么在独立云 Mac 上做公证
- 更接近 Apple API 路径:把构建机放在目标区域,可缩短提交与拉取日志的尾延迟。
- 环境可复现:同一台 NVMe、同一 Xcode 补丁、同一套钥匙串布局,夜构建不会“因笔记本状态而漂移”。
- 与开发机解耦:公证不跟 Zoom/Slack 抢 CPU,也不污染个人钥匙串。
stapler validate 失败则整 job 失败。
无头环境常见痛点
- 钥匙串弹窗不会出现:纯 SSH 无 GUI;需
security unlock-keychain或使用已预授权 codesign/notarytool 的 专用 CI 用户。 - ZIP 结构错误:压扁 bundle、重复
MacOS、剥离扩展属性会导致日志里出现 “Invalid” 或难懂的签名错误。 - 忘了装订:Apple 侧已 Accepted,但未对下载件执行
stapler staple,新加坡/美国 用户仍可能遇到 Gatekeeper 告警。 - 并行任务抢带宽:三条发布分支各丢 400 MB zip,1 Gbps 出口会被打满,看起来像 “Apple 挂了”。
- 审计缺口:不保存 submission id 与 JSON,安全复盘无法对齐 2026-04-09 公证产物与对外发布物。
按产物类型选打包方式
macOS 分发不是 iOS TestFlight。为 .app / .dmg / .pkg 各写一条规则,避免值班同事在 港/日/韩 现场临时发明流程。
| 交付物 | 常见提交形态 | 装订对象 | 易错点 |
|---|---|---|---|
已签名 .app |
ditto -c -k --keepParent MyApp.app MyApp.zip |
受理后的 bundle 目录 | 强化运行时与 entitlement 必须匹配 Developer ID 证书 |
.dmg |
按工具链提交 DMG(或遵循 Apple 文档的容器流程) | 用户实际挂载的 DMG 文件 | 卷内布局与内含 app 签名必须一致 |
扁平 .pkg |
productbuild 等安装包产物 |
磁盘上的 .pkg |
组件包 ID / receipt 版本错误会直接变成工单量 |
云 Mac 前置条件
| 要求 | 说明 |
|---|---|
可用 notarytool |
随新版 Xcode/CLT 提供;用 xcrun notarytool --version 自检 |
| 签名身份 | Developer ID Application(macOS 分发)——与导出流水线一致 |
| App Store Connect API Key | Issuer ID、Key ID、.p8 —— 用钥匙串 profile 保存,勿明文进仓库 |
| 无人值守钥匙串 | SSH 用户需加载登录钥匙串,或在受控密钥步骤执行 security unlock-keychain |
为 notarytool 写入钥匙串配置
一次性(交互或安全引导)把凭据存成 profile,例如 ac-notary:
xcrun notarytool store-credentials "ac-notary" --key ~/AuthKey_XXXXXX.p8 --issuer <issuer-uuid> --key-id <key-id>
.p8 轮换周期与其他 ASC API Key 对齐。
提交 zip 并等待
压缩 .app(务必保留符号链接,常用 ditto -c -k --keepParent MyApp.app MyApp.zip):
xcrun notarytool submit MyApp.zip --keychain-profile "ac-notary" --wait
若在 CI 拆分等待与轮询,从 JSON 取 submission id,循环执行 xcrun notarytool log <id> --keychain-profile "ac-notary" 直到状态稳定,并把日志写入制品库。
解析建议:用 jq(或语言内置 JSON)断言每次绿构建都包含 id、status、message 摘要。Invalid 时日志常指向 zip 内具体路径——请保留原始 zip 与日志,便于和同一 Xcode 固定版本的“黄金包”做 diff。
限流:若出现 HTTP 429 或连续超时,可在单台上对公证阶段加互斥:例如 /tmp 文件锁,或每台 Mac 队列深度 1。
装订与校验
Apple 受理成功后:
xcrun stapler staple MyApp.app
xcrun stapler validate MyApp.app
对磁盘映像请遵循当前 Apple 文档;务必以 validate 收尾,避免 新加坡/美国 用户侧 Gatekeeper 意外。
顺序:仅在 Apple 报告成功之后装订。若本地再次重签或重打包,必须重新提交——stapler 不能“修复”已漂移的签名。
常见失败与优先排查
| 现象 | 先查 | 常见修复 |
|---|---|---|
notarytool 报 Unable to authenticate |
profile 名称拼写;钥匙串是否解锁;CI 用户能否读 .p8 |
重跑 store-credentials;API Key 过期则轮换 |
| 日志提示二进制签名无效 | zip 前对 app 做深度 codesign 验证;隔离属性(quarantine) | 用正确 Developer ID 重新导出;用 ditto 打 zip,避免 Finder“压缩” |
DMG 上 stapler validate 失败 |
是否装订错文件;装订时 DMG 仍挂载 | 卸载卷;对实际上传的 DMG 路径装订并再校验 |
| 轮询一直 In Progress | 机器时钟漂移;出口防火墙拦截 Apple 端点 | 同步 NTP;放行 notary 相关域名/IP;退避上限建议 15 分钟 |
CI 门禁清单
notarytool submit非零退出则失败。- 构建记录必须附带 submission id + 完整 JSON 日志(保留期 ≥ 合规要求)。
- 对即将发布的二进制执行
stapler validate,失败则失败。 - 在同一制品清单记录 Xcode build 字串 与 notarytool 版本。
- 上传 CDN/MDM 前对装订后产物做
shasum -a 256。 - 可选:在干净切片上跑 spctl 评估,模拟终端用户环境。
常见问题(表格式)
| 问题 | 回答 |
|---|---|
| 这和 iOS App Store 上传是一回事吗? | 不是——公证面向 Mac App Store 之外的 macOS 分发。iOS IPA 走不同路径;参见 Archive 实操。 |
| 公证任务该选 1TB 还是 2TB? | 大体积 .zip、日志、多份 Xcode 并存会吃满 NVMe;并行前扩容。见 套餐与节点。 |
| 只要 CLT 还是要完整 Xcode? | 新版 CLT 通常含 notarytool,但多数团队仍在同一台机做 Archive+导出——对比 CLT 与完整 Xcode。 |
公证能否与 xcodebuild 同机? |
可以,但应用工作区分隔 + 互斥,避免签名/打 zip 竞态;见 并行任务布局。 |
| 不用 VNC 怎么排障? | 先拉日志;确需 GUI 工具时短期开 VNC,再切回 SSH 自动化。 |
为何租赁 Mac mini M4 适合公证 CI
公证负载呈突发型:压缩数百 MB bundle、上传、轮询 API,再在快速盘上装订校验。Mac mini M4 提供原生 Apple Silicon 的 xcrun 行为、可预期的 NVMe 延迟,并可在 香港/日本/韩国/新加坡/美国 选址以贴合合规与 CDN 出口。按需租用避免一次性资本开支,同时保留与工位机一致的 SSH 与可选 VNC——适合 7×24 专线且不抢占笔记本登录钥匙串。
租用还便于镜像固定:每周同一 Xcode 与 notarytool 语义。Apple 调整策略时,先升一小撮节点验证绿构建,再全量推广。磁盘与节点规格见 定价;交互调试可偶尔用 VNC,生产仍以无头为主。
结语:只要把钥匙串与日志当作一等公民,无头租赁 Mac mini M4 就是按计划跑 notarytool + stapler 的合理位置。下一步:阅读 SSH 与部署帮助,或强化 并行构建隔离,避免公证与签名任务互相抢锁。