无自有硬件:在租用云端 Mac 上完成提交、日志、装订与 Gatekeeper 自检的可复现 Runbook
当你要把 DMG、PKG 或命令行工具交给客户或同事「直接双击就能跑」,而你的主力机是 Windows 或 Linux、手边没有一台可随时开盖的 Mac 时,Apple Notarization(公证)往往成为最后一道门槛:它不只是上传一个 zip,而是一串对签名、Hardened Runtime、票据与装订(staple)的组合校验。更麻烦的是,许多失败并不会在第一次 notarytool submit 的退出码里完整展开,而是藏在 notarytool log 的详情 JSON 里;而另一类失败则来自钥匙串访问、开发者账号会话与系统时间这类「更适合在图形会话里目击」的环境问题。本文面向租用云端 Mac 的读者:先拆痛点,再给「仅 SSH / SSH+VNC / 以 VNC 为主」的决策矩阵,随后给出八步 Runbook、四条可写进变更单的结论,以及二十分钟同用户 VNC 验收表。文中与《首次使用清单》、《系统时间与证书核对》、《Fastlane Match 与 SSH/VNC》、《紧急小版本上架》互链,便于你把「能编译」与「能分发」放在同一工单里闭环。
在 App Store 体系之外分发 macOS 二进制时,Gatekeeper 与公证策略会把问题前移到构建机环境是否可信、签名是否一致、是否有未声明的敏感权限。租用的云端 Mac 解决了「我没有 Apple Silicon」的硬约束,但引入了四类常见工程痛点:(1)会话分裂——你在 SSH 里用用户 A 跑构建,在另一个会话里用用户 B 跑 notarytool,钥匙串项与 Xcode 账户缓存看似都存在,实则不在同一安全域;(2)时间漂移——证书「尚未生效」类报错在远程机上比本地更常见,尤其在节点休眠、快照恢复或手动改过时区之后;(3)网络路径——企业出口或跨境代理会打断对 Apple 公证服务的 TLS 长连接,表现为间歇 503 或握手超时;(4)租期与磁盘——公证产物与中间 zip 往往体积大,若未同步到对象存储或你的主力机,容易在「租期结束 / 换节点」时丢失可复现证据。
误以为「Accepted」就万事大吉:对 DMG 等载体,若跳过 stapler staple 或未按 Apple 要求验证装订结果,用户侧仍可能遇到「无法验证开发者」或首次打开时的额外网络回退。
日志未存档就反复重传:notarytool log 中的 UUID 与 issue 列表是排障第一现场,丢失会导致团队在同一误构建上循环浪费配额。
把公证与 App Store 上架混为一谈:Organizer 上传解决的是商店通道;公证解决的是商店外分发通道,二者凭证与失败码并不一一对应。
SSH 硬扛钥匙串弹窗:没有图形会话时,部分凭证写入看似成功,但后续批处理任务读到的却是空项或过期项。
忽略 Hardened Runtime 与 entitlements 边界:公证拒绝里大量是「启用了不该启用的能力」或「缺少必要声明」,这类问题应回到 Xcode 构建设置而不是反复改命令行参数。
未把「同用户 VNC」写进变更单:远程团队扯皮时,最常见的一句话是「我这边明明能跑」——本质是没有把用户、会话与监听端口钉死在同一张证据表上。
| 步骤 / 证据 | 仅 SSH | SSH 构建 + VNC 收尾 | 全程同用户 VNC 主导 |
|---|---|---|---|
| Archive / 导出签名的 .app | 通常可行 | 可行 | 可行,适合新手目击每一步 |
| 创建 zip / 提交 notarytool | 可行 | 可行 | 可行 |
| 轮询 notarytool info / log | 可行 | 可行 | 可行 |
| 钥匙串访问 / Apple ID 会话 / 2FA | 高风险 | 推荐 | 推荐 |
| 装订 stapler staple 与本地 spctl 自检 | 部分可行 | 推荐 | 推荐 |
| 向非技术同事「演示为什么不过」 | 弱 | 强 | 最强 |
这张表刻意把「命令能不能跑」与「证据能不能被第三者复现」拆开:对独立开发者而言,很多时候混合路径性价比最高——编译与压缩在 SSH 里完成,涉及账户、钥匙串与「第一次装订结果」的确认,则切到与构建同一用户的 VNC 桌面会话里完成。这样你既保留脚本化流水线,又避免在排障阶段陷入「终端里看不见弹窗」的黑盒状态。若你完全零基础,则建议直接以 VNC 为主,把首次清单里的连接、剪贴板与分辨率设置先跑通,再进入公证命令链,否则会在第一步就被「无法把 zip 拷到正确目录」这类低级问题消耗半天。
公证链上的硬通货只有两个:UUID + 日志 JSON,以及同用户钥匙串里可验证的凭证。
冻结指纹:在工单首行写下 sw_vers、xcodebuild -version、notarytool 版本、节点租约 ID;若你刚处理过时钟问题,交叉引用系统时间表把「自动设定日期与时间」截屏存档。
确认签名链:对将要提交的 .app 使用 codesign -dv --verbose=4 与 spctl -a -vv 做本地预检;若团队使用 Match,先对照Match 文确认描述文件与钥匙串 bootstrap 在同用户下完成。
制作可提交工件:按 Apple 要求打包 zip(注意符号链接、扩展属性与深层目录);大体积工件建议先计算 SHA256 写入变更单,避免传输后半包。
提交:notarytool submit ./Your.zip --apple-id ... --team-id ... --password ... --wait 或拆分「提交 + 轮询」;把返回的 submission id / UUID 原样粘贴到工单附件。
拉取日志:对非 Accepted 状态,立即执行 notarytool log <submission-id> 并保存 JSON;不要在未读日志的情况下改 entitlements 盲重传。
修复构建问题:若日志指向 hardened runtime、沙箱或签名不一致,回到 Xcode 构建设置与 Archive 产物重新导出,而不是仅重打外层 zip。
装订:对需要 staple 的载体执行 xcrun stapler staple,随后用 stapler validate 或 Apple 文档推荐的验证步骤确认票据附着。
烟测与归档:在干净用户或另一台 Mac 上做最小双击打开测试;把最终 DMG、日志 JSON、命令行输出与 stapler 结果打包进版本库旁的 release 证据目录。若你同时在做紧急上架,可把本步与热修手术室文的 Organizer 段落交叉执行,但注意商店通道与公证通道的凭证不要混用同一套「临时口令」。
xcodebuild -version notarytool --version codesign -dv --verbose=4 "Your.app" ditto -c -k --keepParent "Your.app" "Your.zip" notarytool submit "Your.zip" --apple-id "$APPLE_ID" --team-id "$TEAM_ID" \ --password "$APP_SPECIFIC" --wait notarytool log "$SUBMISSION_ID" > notary-log.json xcrun stapler staple "Your.dmg" xcrun stapler validate "Your.dmg"
上述命令中的环境变量命名仅作示例:生产环境应使用 CI 密钥管理或 xcrun notarytool store-credentials 一类「不在 shell 历史里明文回放」的方式;在多人共用租用节点时,还要额外约定谁有权旋转 App 专用密码,并把旋转事件记入与 Match 仓库同级的审计日志。若你在远程机上同时跑自动化与手工公证,建议把「提交 zip 的目录」与「日常开发 DerivedData」分盘或分路径,避免误把调试符号或未剥离调试器的构建混进提交包——这类错误在日志里常表现为体积异常或签名嵌套不一致,排障时优先对照 Archive 配置而非网络层。
当日志提示与网络相关时,不要立刻怀疑 Apple 服务端:先在 VNC 会话里打开「网络实用工具」或最小 curl -v 验证 TLS 握手,再核对企业代理是否对长连接做了中间断开;若你使用分流策略,确保 notary 流量没有误走需要人机二次认证的代理页。对于要在多个地理区域分发的团队,还应记录「公证提交地」与「目标用户主要时区」,以便在偶发区域性故障时快速判断是否属于局部网络问题而非构建缺陷。
| 核对项 | VNC 侧证据 | SSH / 终端证据 | 通过标准 |
|---|---|---|---|
| 用户一致 | 菜单栏显示的用户名与工单一致 | id -un 与构建脚本一致 | 无 sudo 混跑 |
| 时间与 TLS | 日期与时间面板截图 | sntp / curl -I 关键头 | 与 NTP 表一致 |
| 提交成功 | (可选)浏览器仅用于读文档 | submission id 行 | UUID 已存档 |
| 日志无未关闭 issue | 打开 JSON 目视关键字段 | jq 过滤 issue 数组为空 | Accepted 且可复验 |
| 装订与验证 | 双击 DMG 烟测录屏(可选) | stapler validate 输出成功 | 离线打开无额外警告 |
这张表的价值在于把「时间戳对齐」写进习惯:当你向客户解释「为什么昨晚的包今天才可用」时,submission 时间、装订时间与 NTP 截图能构成一条短证据链,避免口头扯皮。若团队并行维护多条产品线,还应把「产品 bundle id + 公证 UUID」做成可检索索引,而不是散落在个人下载目录里。对于需要长期保存的日志 JSON,建议同步到你方对象存储并设置保留周期,与租用节点的磁盘清理策略解耦——否则很容易在磁盘清理类操作中被误删。
本文不替代 Apple Developer Documentation 对 notarytool flags 的逐项说明——那些应以发行版为准;此处专注租用远程 Mac 场景下的证据链与 VNC 强制步骤。若你还同时在跑 iOS 上架流水线,请把公证与 Organizer 上传拆成两张变更单,以免在凭证轮换窗口把 App 专用密码与商店会话混在一个密钥桶里。
对常见 DMG/ZIP 分发,装订能显著改善离线双击体验;具体载体是否必须装订请以 Apple 当前文档为准,并在变更单记录 validate 结果。
提交与轮询通常可以;涉及钥匙串、账户会话或需要目击弹窗时,应切到与构建同用户的 VNC。
时间漂移、代理打断、租期回收与多用户钥匙串域不一致;先用本文第五节表做快检再读日志。
固定 submission id,完整保存 notarytool log JSON,再对照签名与 entitlements;避免盲重传。
把公证链放在工程视角下审视时,你会发现真正的成本不在「多等几分钟上传」,而在证据是否能在第三台机器上被复现:同用户钥匙串、可检索的 UUID、可 diff 的日志 JSON,以及装订后可通过 validate 的载体。纯 SSH 的租用节点非常适合脚本化构建,但一旦进入「账户会话 + 钥匙串 + 用户双击体验」的交界,继续堆远程命令往往不如开一条可图形登录的 macOS 会话来得便宜——这与我们在多篇 Xcode / 证书长文里反复强调的 VNC 价值是同一结论。
自有 Mac 路线则要求你自己承担硬件折旧、办公室出口带宽、以及「谁坐在屏幕前点授权」的排班成本;对以 Windows 为主力机、只在发版周需要 macOS 的团队来说,这些隐性成本常常比按小时租机更难预测。相比之下,租用带 Apple Silicon 的远程 Mac可以把在线率与基线镜像交给专业服务商,同时保留你对签名与公证命令链的控制,并把 notarytool log 与系统设置里的时间、网络项放在同一桌面会话里对齐。
若你需要一台便于完成本文第五节同款同用户 VNC 验收的远程 Mac,可通过 VNCMac 下单:主按钮进入中文站购买页;连接方式与 SSH-VNC 说明见帮助中心。