发版前最熟悉的画面大概是:有人在 Slack 喊「谁帮点一下 TestFlight」,另一个人 SSH 上去手动xcodebuild,第三个人说「我这边能过啊」。云端 Mac 解决了硬件问题,但若检查仍靠人肉串联,「本机能过」的运气成分一点没少。我们把 OpenClaw 接进 Zutcloud 独享节点后,目标很明确:用编排层 + 固定节点,把触发、构建、签名校验与回执写进一条可审计的链路。
从「人肉发版」到「链路发版」
在接入自动化之前,我们统计过一个中型 iOS 团队的发布前动作,平均每次发版要花 40 分钟以上在「确认环境」上:
| 手工步骤 | 隐性成本 | 自动化后 |
|---|---|---|
| 确认 Xcode / 证书版本 | 口头同步,易遗漏 | 镜像版本写入 pipeline manifest |
| 本地 Archive 试编 | 每人环境不同 | 独享节点固定 DerivedData 路径 |
| 上传 TestFlight | 谁有空谁操作 | 签名 stage 自动触发 altool / Transporter |
| 通知 QA 验收 | 构建号对不上 | 回执 webhook 带 commit + 制品哈希 |
五段式发布前链路
我们把发布前检查拆成五个可独立重试的阶段。每一阶段结束时向编排层回报结构化 JSON,而不是只抛一段 stderr:
- 触发(Trigger)— PR merge、tag push 或定时夜构建;记录
trigger_id与分支 - 环境冻结(Freeze)— 校验节点镜像、Xcode 版本、
openclaw.yaml中的 region 与 cache 根路径 - 构建(Build)—
xcodebuild/fastlane;产出 .xcarchive 与单元测试报告 - 门禁(Gate)— 静态分析阈值、测试覆盖率、包体积环比;不通过则阻断下游
- 回执(Receipt)— 制品哈希、节点 ID、耗时写入 webhook / 只读频道
编排层配置示例
下面是一段简化示意,展示如何把 Zutcloud 独享节点注册为 OpenClaw runner,并在触发时注入元数据(实际字段名以你使用的 CI 为准):
name: pre-release-mac on: push: tags: ['v*'] jobs: build-on-cloud-mac: runs-on: [self-hosted, macOS, zutcloud-ap-northeast] steps: - run: openclaw daemon health --json - run: | export OPENCLAW_NODE_ID="vn-apne1-m4-01" export ARTIFACT_ROOT="/Volumes/artifacts/ap-northeast-1" ./scripts/ci-build.sh - run: openclaw receipt publish \ --trigger-id "${{ github.run_id }}" \ --sha "${{ github.sha }}" \ --artifact-hash "$(shasum -a 256 dist/*.ipa)"
注意三个细节:health 检查放在第一步,避免 daemon 异常时白跑半小时编译;环境变量在 shell 块里显式导出,别依赖 SSH 登录时的 profile;receipt 单独一步,就算上传 TestFlight 失败,也能保留「构建已成功」的证据链。
独享节点:为什么不用共享 Runner
共享 Mac CI 看似便宜,但对发布前检查来说有三个硬伤:缓存目录不可控、证书策略难隔离、排队时间不可预测。独享节点让 OpenClaw 能把 DerivedData、Pods 缓存与签名钥匙串绑在同一台物理机上,链路确定性远高于「每次随机一台机器」。
| 维度 | 共享 Runner | Zutcloud 独享 + OpenClaw |
|---|---|---|
| 缓存 | 作业结束即清 | 按卷快照,绑 Xcode 版本 |
| 证书 | 多租户风险 | 单租户钥匙串,可离线签名机 |
| 审计 | 日志分散 | trigger → node → artifact 一串到底 |
| 排队 | 高峰可能小时级 | 独占算力,夜构建可预约 |
回执与可审计性
「可审计」不是存一份巨大日志 tarball,而是让每次发版都能回答五个问题:
- 谁触发的?(PR 作者 / tag / 定时任务)
- 在哪台节点、哪个区域编的?(
node_id+region) - 用的什么 Xcode 与缓存快照?(镜像版本 + cache 根路径)
- 制品哈希是多少?(IPA / dSYM 可复现)
- 哪一 stage 失败?(freeze / build / gate / upload)
我们把回执推到团队只读 Slack 频道(或等价工具),格式固定为单行 JSON 摘要 + 详情链接。QA 不用再问「这是哪次构建」——构建号、commit 与哈希在消息里已经对齐。
{
"trigger_id": "gh-1849201",
"sha": "a1b2c3d",
"node_id": "vn-apne1-m4-01",
"region": "ap-northeast-1",
"xcode": "16.2",
"stages": {
"freeze": "ok",
"build": "ok",
"gate": "ok",
"receipt": "ok"
},
"artifact_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
渐进式落地建议
不必第一周就关掉所有本地 Archive。更稳妥的路径是:
第 2 周:打开 gate stage,测试失败阻断 merge。
第 3 周:tag 发版走完整五段链路,本地 Archive 改为例外流程。
配合区域与磁盘选型手册一起用:节点元数据在第一周就配对,第三周切发版时才不会出现「链路通了但制品路径错了」的返工。
失败时怎么办
自动化链路的价值一半在成功,一半在失败可定位。我们维护一张简单 playbook:
- freeze 失败— 镜像漂移或 openclaw.yaml 与节点不符 → 冻结节点,回滚镜像 tag
- build 失败— 代码或依赖问题 → 研发修代码,与基础设施无关
- gate 失败— 覆盖率或包体积回归 → 阻断发版,需明确豁免审批
- receipt 失败— 构建已成功但通知/上传失败 → 可重跑 receipt stage,不必全量重编
把阶段拆开后,「重跑」的代价从一小时全量编译,降到几分钟的 receipt 或 gate 重试——这才是编排层真正省下来的时间。
在云端 Mac 上跑通整条链路
OpenClaw 守护进程、健康检查与流水线脚本,在 Zutcloud Mac mini M4 上可 7×24 无人值守运行。约 4W 待机功耗适合作为常驻 CI 节点;独享算力让 DerivedData 与证书策略与编排层配置一一对应。
当你把发布前检查从「人肉点按钮」换成「触发即回执」,团队讨论会从「谁的环境有问题」转向「哪条 gate 规则要调」——这才是云端 Mac 该带来的确定性。
准备搭第一条 pre-release 链路?先从一台亚太或美西独享节点 + 夜构建 cron 开始——查看 Mac 云主机套餐,把 OpenClaw 跑在固定硬件上。