發版前最熟悉的畫面大概是:有人在 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 跑在固定硬件上。