CI 显示上传成功,发布包却还没经过最终验证。
最快解法:macOS 应用公证可以接入远程 Mac CI;先完成 Developer ID 签名和产物准备,再用 notarytool 提交、复核处理结果,并按分发格式装订票据。提交成功不是交付验收,签名、票据和最终分发包都要检查。
流程时间表:签名与导出 → 提交 → 结果复核 → 票据处理 → 发布验收。
本周建议动作:先用一条隔离的真实发布任务跑完整链路;在产物、日志和票据能够相互追溯前,保留人工发布确认。
这篇适合准备在 Mac App Store 之外分发应用、希望把手动公证改成可复现流程的独立开发者。
也适合维护远程 Mac Archive、导出和发布流水线的构建工程师,以及负责 CI 凭据与日志审计的 DevOps 工程师。
准备阶段:划清公证交付范围
本文讲的是使用 Apple Developer ID 向 Mac App Store 之外分发 macOS 应用,不是 App Review,也不包含 Mac App Store 上传流程。公证是自动检查软件的过程;Apple 对它的说明明确指出,公证不等同于 App Review。(Apple 的 macOS 软件公证说明)
完整交付链应当能回答:送去公证的是什么文件、处理结果是什么、票据如何取得或装订、最后发给用户的文件是否经过验证。只保存“上传成功”的一行日志,会漏掉后续结果和实际分发包这两段证据。
先为流水线里的关键对象建立可追踪记录:
- 源码提交标识,例如
<SOURCE_REVISION>。 - Archive、导出文件和最终分发文件各自的文件名与摘要,例如
<ARTIFACT_SHA256>。 - 公证提交标识,例如
<SUBMISSION_ID>,以及对应的状态和日志。 - 签名检查、票据验证与发布验收结果。
这些占位符应由流水线在运行时填入真实记录。不要把团队账户、Team ID、证书名称、Bundle Identifier 或凭据值硬编码进公开脚本和文章示例中。
签名阶段:从 Archive 导出可提交产物
Archive、导出应用和最终分发包是流水线不同阶段的文件,不应混成一个模糊的“构建产物”。Apple 的签名说明区分 Mac App Store 的签名身份与 Developer ID 分发身份;Developer ID 应用还要满足 Hardened Runtime 和安全时间戳等要求。嵌套的可执行代码也要纳入签名检查,不能只看最外层 .app。可对照Apple 的 macOS 分发签名说明和前述公证前的软件准备要求。
远程 Mac CI 上先确认当前选中的 Xcode 或 Command Line Tools,再检查导出文件;如果节点并行安装多个 Xcode,不能只凭“机器上装过某版本”推定 xcrun 会调用它。Archive 可保留为构建追踪对象,但实际提交文件应是经过导出和打包的待分发产物。Apple 的自定义工作流也说明,.app 需要先放进合适的压缩包或容器再提交。(Apple 的自定义公证工作流)
# 以下均为占位路径;实际选择值应由流水线配置提供
xcode-select -p
codesign --verify --deep --strict "$APP_PATH"
codesign -dv --verbose=4 "$APP_PATH" 2>&1
保存命令退出状态和必要的签名检查输出,同时避免把无关环境变量或秘密值写入日志。--deep 可作为验证手段,但不能替代对嵌套代码签名结构和签名顺序的正确处理;对于复杂应用,应检查每个应签名的代码对象。
提交阶段:选定工具并安全提供凭据
在远程节点确认 xcrun 能找到 notarytool,并将实际使用的 Xcode 路径纳入构建记录。Apple 自 2023 年 11 月 1 日起不再接受通过 altool 或 Xcode 13 及更早版本上传的公证提交,因此旧流水线若仍依赖这条路径,应先迁移到受支持的提交方式。相关变更见Apple 的公证工具迁移说明。
凭据保存和注入需要分清职责:
- Keychain profile:保存供
notarytool调用的认证信息,适合在受控运行账户中使用。 - CI 秘密管理:控制谁能读取凭据、在哪些任务中注入,以及如何轮换或撤销。
- 流水线脚本:只引用凭据配置,不存放认证值;提交日志也不应回显秘密。
Apple 文档展示了 Keychain profile 的用法,但 CI 团队仍需确认执行账户、Keychain 可用性和任务隔离方式。首次接入时可参考前述自定义工作流;如使用 Notary API 而非命令行工具,则应单独按照Apple 的 Notary API 文档实现认证与上传。
命令示例只展示调用关系,不放入密码、私钥或真实账户信息:
xcrun notarytool submit "$SUBMISSION_PACKAGE" \
--keychain-profile "$NOTARY_PROFILE" \
--wait
将标准输出和错误输出存入受控流水线日志,并单独解析提交标识。需要避免的是认证值落入命令回显或日志,而不是为了隐藏凭据连提交结果也不记录。
结果复核:保存状态、标识和失败日志
提交动作结束后,先判断任务处于上传完成、处理中还是已有最终处理结果;上传命令返回成功,并不能单独证明公证处理已结束。保存 <SUBMISSION_ID>,再用它查询状态和取得对应日志。Apple 的自定义工作流提供 notarytool 查询与下载日志的方式,并建议检查成功提交中仍可能存在的警告。
xcrun notarytool info "$SUBMISSION_ID" \
--keychain-profile "$NOTARY_PROFILE"
xcrun notarytool log "$SUBMISSION_ID" \
--keychain-profile "$NOTARY_PROFILE" \
"$NOTARY_LOG_PATH"
出现失败或警告时,按证据分类,不凭单条报错就认定远程节点或网络故障:
- 签名证据:检查证书身份、签名完整性、时间戳、Hardened Runtime 和嵌套代码。
- 产物证据:确认提交的文件与流水线预期一致,格式受支持,容器没有损坏。
- 权限证据:确认凭据属于预期团队,运行账户可读取 Keychain profile。
- 服务与网络证据:结合命令返回、提交状态和对应日志判断;没有这些证据时,不把问题归因于节点网络。
日志中若列出具体文件路径,应回到同一构建产物定位问题,修正后重新生成并追踪新文件。常见签名与公证错误的处理边界,可查阅Apple 的公证问题排查说明。
常见问题
远程 Mac 上的 CI 如何完成公证?
先准备好 Developer ID 签名的应用及待分发容器,再由选定的 Xcode 工具调用 notarytool。流水线保存提交标识和日志,复核处理结果,最后按交付格式装订票据并验证最终文件,而不是把上传步骤当作完整发布流程。
提交显示 Accepted 后,还要核对什么?
核对该状态对应的提交标识和日志,确认流水线准备发布的正是本次提交关联的产物;再检查签名、票据和安装或启动结果。Accepted 不能替代最终文件验收,也不意味着流水线导出的下一份文件自动带有票据。
CI 凭据如何提供给远程 Mac 任务?
通过受控秘密注入或适当配置的 Keychain profile 提供,限制可读取任务和运行账户,并检查日志是否会打印敏感内容。对于短生命周期执行环境,还要验证 Keychain 在任务运行时确实可用;不要把凭据写入仓库或脚本参数。
如何验证 ZIP 中的应用票据已经装订?
公证服务可以接受 ZIP,但 stapler 不能直接将票据装订到 ZIP 文件。应对 ZIP 内可装订的应用或项目处理票据,再重新生成 ZIP;之后验证被装订的项目,并从最终 ZIP 解压实际交付文件进行复核。
票据阶段:按最终分发格式处理
公证接受提交的格式与 stapler 可以直接装订的对象,不应混为一谈。Apple 文档列出公证可接受的磁盘映像、签名的平面安装包和 ZIP 等容器;但 ZIP 不能直接装订票据,需要处理其中可装订的项目后重新制作分发 ZIP。应用包、磁盘映像或平面安装包则可按对应方式处理。操作前参照自定义工作流的格式与装订说明及Apple 的 Mac 软件打包说明。
# 以实际交付对象选择可装订路径
xcrun stapler staple "$STAPLE_TARGET"
xcrun stapler validate "$STAPLE_TARGET"
如果最终交付的是 ZIP,STAPLE_TARGET 应指向 ZIP 内应装订的项目,而不是 ZIP 本身。重新打包后再对最终 ZIP 解压检查,避免误把处理中间件当成交付文件。票据在线可用、票据已装订和签名仍有效是不同检查项;在网络不可用的用户环境中,缺少装订票据还可能影响首次安装或运行。
注意:不要在公证接受后覆盖、重新签名或重新打包却不重新验收。任何改变最终文件内容的步骤,都应产生新的文件标识,并重新验证实际发给用户的那一份。
发布阶段:用真实任务决定是否自动放行
上线前用隔离的发布任务走完 Archive、导出、签名检查、公证提交、结果复核、票据处理和分发包测试。最终记录至少能把源码提交、构建产物、提交标识、公证日志和发布文件关联起来;实际交付的安装包、磁盘映像或 ZIP 也应经过测试。具体打包验收可参考前述 Apple 的 Mac 软件打包说明。
按验收证据决定发布控制:
- 若所有阶段都能关联到同一发布任务,签名和票据验证通过,且实际分发包安装或启动正常:可逐步开放自动发布,同时保留审计记录。
- 若公证已接受,但最终包、票据或用户侧安装尚未验证:保留人工确认,不将接受状态作为自动放行条件。
- 若提交记录、签名资产或失败日志无法追溯:暂停上线,先补齐证据与恢复步骤。
同时写明凭据轮换负责人、失败后的重试条件,以及如何恢复到上一个已验收版本。若现有方案是开发机手动提交或依赖无法追溯的共享 Mac,它的实际缺点通常是凭据边界不清、重复构建难以对应到同一产物、失败恢复依赖个人操作;若是没有 macOS 工具链的 Linux 节点,则无法独立完成这条 macOS 公证执行链。先比较Mac mini 远程算力方案与当前执行环境,再判断远程 Mac 是否匹配团队的 Xcode、签名资产和流水线控制需求。
如果暂时没有可用的 Mac 执行环境,可用一条真实发布任务验证远程 Mac 的适配性;需要临时或阶段性环境时,再了解 NodeMini 的远程 Mac 方案。长期稳定的重负载或依赖物理接口的任务,应先核对本地设备是否更合适;选定方案前,验收标准仍是同一份最终分发包上的签名、票据与安装测试全部可追溯。