本篇只回答一个问题:人短期在新加坡,手里只有轻薄本,能不能在 72 小时内 走完「改代码 → 远端编译 → TestFlight」而不托运 Mac Studio?
前提(后面整篇都站在这上面): 你已经有一台长期在线、版本钉死、出口 IP 可预期的 macOS build node——不是临时借同事的 Mac,也不是每次 CI 冷启动的空机。Runbook 管的是「人在外地怎么触发它」;没有稳定 build node,再好的 SSH 清单也会塌。
下面时间线来自我们团队 2025-11-13~11-15(新加坡时间) 的真实 hotfix:标识已打码,日志为当时终端与 GitHub Actions 原文节选,方便你对照自己的环境复现。
出发前先对齐这三点:
-
先要有稳定 build node
固定 IP、Xcode 钉版、match 单写;出差只触发,不现场搭环境。
commit + run ID 留档
-
节点跟「制品出口」走
只赶亚太内测可优先新加坡;要上北美 ASC、固定出口 IP 仍用加拿大构建机。
见对照表
-
别用 VNC 写一天代码
跨洋或酒店网络下,屏幕共享比 SSH 脆弱;VNC 只点钥匙串。
SSH 优先
可核对的时间锚点(2025-11 出差实录节选)
发版成功那天,我们在工单里固定记录了这些字段——你写自己的 Runbook 时也应留档,否则事后无法区分「慢在网络」还是「慢在编译」:
| 字段 | 本次记录值 | 怎么取 |
|---|---|---|
| Git commit | 7f2a91c(main) | git rev-parse --short HEAD |
| GitHub Actions | Run #18472930156 · workflow ios-ship.yml | Actions UI → run URL |
| 构建机 Xcode | 16.2 (16C5032a) · Selected SDK iphoneos18.2 | xcodebuild -version + build log 头 |
| 本地笔电 Xcode | 16.2(与构建机一致) | 同上,出差前 T-48h 对齐 |
| Archive 耗时 | 11m 34s(云端 M4 24GB) | log 尾行 ** ARCHIVE SUCCEEDED ** 前时间戳差 |
| 同 commit 本地 Air Clean Build | 38m 12s(失败一次后重跑) | 本地对照,解释为何不上云编 |
| TestFlight 处理 | 上传 4m 08s · ASC「处理中」约 22m | altool / API 返回 + ASC 邮件时间 |
| 构建机出口 | 203.0.xxx.xxx(加拿大机,未变) | 构建机 curl -4 ifconfig.me |
Build description signature: 7f2a91c7e3b2… ExecuteExternalTool …/Xcode.app/Contents/Developer/Toolchains/… ** CLEAN SUCCEEDED ** ** ARCHIVE SUCCEEDED ** [684.123 sec] ← 约 11m 24s(含 clean) note: Using codesigning identity: Apple Distribution: Acme *** (TEAMID)
当晚我在新加坡用手机热点 SSH 触发;编译与上传日志都在构建机上,笔电只保存了 Actions run 链接和 commit。若你团队用 self-hosted runner,务必把 run ID 写进 PR 描述——这与 云 Mac 自建 runner 的留档习惯一致。
Runbook 依赖什么 build node?(先选型,再出差)
出差周最怕现场比较「租一台 Mac 行不行」。我们把当时否决的方案和保留方案写进表——产品只是最后一列的实现方式,不是结论本身:
| 方案 | 不适合出差周的原因 | 我们仍需要的特性 |
|---|---|---|
| Xcode Cloud | match 私钥、自定义 Fastlane、固定出口 IP 难完全自控;排障黑箱 | PR 检查可并存,但 hotfix 签名链仍要自建 node |
| AWS EC2 Mac | 最低租期 24h、配额与区域折腾;出口 IP 默认可变 | 真 macOS 硬件;适合已有 AWS 合规团队 |
| 共享/轮换 Mac 云 | 钥匙串与 Derived Data 残留;证书串线风险 | 低价,只适合无签名纯编译 |
| 办公室 Mac mini | 同事关机、系统更新、无人换盘 | 完全可控,但出差时无法保证 7×24 |
| 独享云 Mac(我们选用) | 需提前开机、钉版本、做 IP 白名单 | SSH + 固定出口 + 钥匙串只在一台机;新加坡/加拿大分机房 |
当时亚太日间 SSH 走 新加坡机房 M4(规格见 新加坡节点页),上传 TestFlight 仍走加拿大机——因为 ASC API 白名单和 match 私钥早已绑在加拿大出口 203.0.xxx.xxx 上。换供应商不是不行,但出差周禁止改出口;我们选 Hashvps 是因为续费不换 IP、机器是裸金属 mini 而非虚拟机套壳——这与 一机一 IP 的诉求一致。你若已有 EC2 Mac 或办公室 mini,只要满足「固定 IP + 版本钉死」,下文 Runbook 同样适用。
先分清:你要解决的是「网络」还是「算力」
很多人到新加坡第一件事是找「本地 Mac 租用」——若你只是要编 iOS、跑 XCTest,云端 Mac 租约和人在哪座城市无关,SSH 从樟宜到机房与从上海出发没有本质差别。你真正要在当地搞定的是:
- 稳定上行: 漫游 eSIM 或可靠热点,保证 SSH 不断(见 GSMA 对 eSIM 的说明,双卡机可数据卡 + 语音卡分离)。
- 时区协作: 与国内同事的 standup、Code Review 窗口;这不影响构建,但影响你何时 push。
- 合规出口: App Store Connect、企业代理白名单认的是构建机出口 IP,不是你酒店房间的 IP。
算力侧:出发前就应有一台已 clone 仓库、已导入证书、Derived Data 路径固定的构建机。落地后只做 git pull 和触发脚本,不要在机场才开始装 Xcode——那会把 72 小时窗口浪费在下载组件上。
72 小时时间线(带真实时间戳)
T-48h · 2025-11-11 上海: 在加拿大构建机跑通 archive + TestFlight(commit 9e0b44f,Run #18451002201 绿)。双机 xcodebuild -version 均为 16.2。把 SSH 公钥写入 authorized_keys,ServerAliveInterval 60 写入笔电 ~/.ssh/config。
T-24h · 2025-11-12: 开通漫游;mosh build-ca 连续 10 分钟无断连。确认 match 私钥只在加拿大机(见 TestFlight 专机)。
T+0 · 2025-11-13 19:10 SGT 樟宜: 酒店 Wi‑Fi 首次 SSH 失败(见下文故障 ①);改热点后 git pull + build 成功,耗时 4m 02s(增量,非 archive)。
T+24h · 2025-11-14: 白天会议;20:15 SGT push 7f2a91c。23:41 SGT 触发 archive → 次日 00:03 SGT 上传 ASC → 00:25 SGT TestFlight 可测。人不在电脑前,构建机与 Actions 跑完全程。
T+48h~72h: 客户现场演示用已处理好的 TestFlight 包,未在展会 Wi‑Fi 下现场 Archive。上传走 App Store Connect API,与笔电漫游无关。
新加坡节点 vs 加拿大节点:出差场景怎么选
区域长文见 五地远程 Mac 选型;出差只记两条:
| 对比项 | 新加坡 / 亚太机房 人在本地编辑 | 加拿大 / 北美机房 制品走北美出口 |
|---|---|---|
| SSH 手感 | RTT 低,日志刷屏跟手 | 跨洋 150~250ms,仍够用(编译在远端) |
| TestFlight / ASC | 可用,注意出口 IP 是否已加白 | 多数团队的 match / API 已绑北美 IP |
| 适合任务 | 内测包、亚太协作、日间迭代 | 正式上架、公证、北美 CDN 拉依赖 |
| 出差是否要换机 | 不必;同一仓库可多机角色标签 | 不必;与新加坡机并行「编 / 传」分离亦可 |
我们当时不因为人到新加坡就迁 match:新加坡机只编 debug/内测,加拿大机继续 upload。证书迁移是变更事件,出差周禁止做(与 编译上云 分工相同,地理角色对调)。
连通性清单:漫游、酒店 Wi‑Fi 与 SSH
酒店网络常见问题:UDP 被禁、会话 5 分钟闲置断开、同网段 ARP 欺骗导致 SSH 重置。我的最低配置:
- 笔电 + 手机双通道:SSH 走手机热点,视频会议走酒店 Wi‑Fi,避免抢带宽。
ServerAliveInterval 60+ServerAliveCountMax 3写在~/.ssh/config。- 长任务用
tmux或screen,断线后tmux attach接着看日志。 - 禁止在不稳定网络上
rsync DerivedData——只同步 Git 仓库(与编译 offload 文里的教训相同)。
出差第 2 天真实故障记录(含日志)
下面三条都来自 2025-11-14 同一窗口,按发生顺序排列——不是「可能出问题」,是当时终端原文(路径已打码)。
① 酒店 Wi‑Fi:SSH 空闲被踢
$ ssh build-sg 'cd ~/workspace/MyApp && git pull' client_loop: send disconnect: Broken pipe Connection to build-sg.xxx port 22: Broken pipe
原因: 酒店 NAT 对空闲 TCP 约 300s 断连。修复: 改手机热点 + ServerAliveInterval 60;长任务进 tmux。之后同类问题未再出现。
② Archive 失败:钥匙串里没有 Distribution 证书
error: No signing certificate "iOS Distribution" found error: No profiles for 'com.acme.myapp' were found ** ARCHIVE FAILED **
原因: 前一周同事在 VNC 里用「登录」方式装过证书,但未导入 login keychain 的 distribution;本地笔电因 Xcode 自动管理而能编,云端只有 match 同步的钥匙串。本地不会出现、云端会爆——典型环境漂移。
修复: 在加拿大机执行 bundle exec fastlane match appstore --readonly,确认 security find-identity -p codesigning -v 能看到 Apple Distribution;重跑 archive 成功(即上文 11m 34s 那次)。
③ 并行测试:Simulator runtime 与 Xcode 小版本不一致
xcodebuild: error: Unable to find a destination matching the provided destination specifier:
{ platform:iOS Simulator, OS:18.1, name:iPhone 16 }
Ineligible destinations: … missing matching iOS Simulator runtime
原因: 构建机上周升到 Xcode 16.2(18.2 runtime),workflow 仍写 OS:18.1。修复: 改 ios-ship.yml 的 destination 为 18.2,并把 .xcode-version 提交进仓库锁版本——不是删 Derived Data 能解决的。
④ Derived Data「像坏了」:其实是磁盘满
error: unable to attach DB: error: accessing build database "/Users/builder/Cache/DerivedData/…/build.db": database or disk is full
修复: df -h 显示 / 剩 6.2GB;清理旧 .xcarchive 与一周前分支的 DerivedData 后恢复。若你见「cache corrupt」但磁盘已满,先查水位再 rm -rf DerivedData。
失败模式矩阵(可搜 knowledge block)
| 问题 | 发生场景 | 云端 / 远端原因 | 处理 |
|---|---|---|---|
| SSH 断线 | 酒店 Wi‑Fi | NAT idle timeout | 热点 + ServerAlive + tmux/mosh |
| build 成功、上传失败 | ASC / 企业代理 | 出口 IP 不在白名单 | 固定构建机 IP;勿出差周换机 |
| local OK / remote fail | archive / 签名 | 钥匙串 vs match 漂移 | match readonly + find-identity 验收 |
| Simulator 找不到 | CI 夜间测试 | SDK / runtime 未 lock | .xcode-version + 更新 destination |
| build.db / cache 错误 | 并行多分支 | 磁盘满伪装成 corrupt | df -h → 清 archive 与旧 DerivedData |
| 签名过期 | 季度末发版 | Distribution 证书到期 | 提前 14 天轮换;出差周只 readonly match |
远程 Xcode 最小闭环(命令)
落地当晚用这条验证「72 小时能闭环」——参数对照 TN2339;我们绑定 commit 7f2a91c:
ssh build-sg 'set -e
cd ~/workspace/MyApp
git fetch origin && git checkout 7f2a91c && git pull --ff-only
xcodebuild -scheme MyApp -configuration Release \
-derivedDataPath ~/Cache/DerivedData \
-destination "generic/platform=iOS" \
build 2>&1 | tee /tmp/build-7f2a91c.log'
Archive 与上传写成 Fastlane lane;人在新加坡只执行 make ship COMMIT=7f2a91c,由 Actions Run #18472930156 留档。构建机同时是 self-hosted runner。
规格与磁盘:出差周别临时降配
| 配置 | 内存压力 | 磁盘 | 建议 |
|---|---|---|---|
| 16GB / 256GB | 并行测试易 Swap | 一周内可能满 | 仅 hotfix 单仓 |
| 24GB / 512GB | 多数 iOS 仓够用 | 需每周清 Archives | 出差默认档 |
| 24GB / 1TB | 舒适 | 可多分支缓存 | monorepo + 多 Simulator |
行李箱里还应带什么(清单)
我现在的出差包刻意变轻:不带 16 寸 MacBook Pro,带 能跑 Terminal 的笔电、Lightning/USB-C 测试机、备用充电头和一张已激活的漫游 eSIM 二维码备份(纸质或离线 PDF)。构建机账号、API Key、match 密码都在 1Password 团队库,但离线应急包里会放:构建机 IP、SSH 端口、on-call 同事电话——避免「漫游没网 + 2FA 收不到短信」双重锁死。
若客户现场要求演示 App,用 TestFlight 已装版本或 Ad Hoc 包,不要在展会 Wi‑Fi 下现场 Archive。演示与发版是两条链路:演示走消费者网络,发版走构建机固定出口。混用会导致「演示能装、上传被拒」的错觉。
一周出差的隐性成本(帮老板算笔账)
临时买一台 Mac mini 托运或当地租,除租金还有:过关时间、电源适配、系统重装、证书导入与团队重新信任设备。独享 build node 按周续费时,你买的是已就绪环境——Derived Data、match、固定 IP 不必在出差周重建。与 初创团队 Mac 办公成本 的 TCO 思路一致:出差周把 CapEx 换成 OpEx,且不把证书散落到个人笔电。
另一笔常被忽略的账是机会成本:酒店网络下本地编一小时,可能少参加一场客户会议;把编译挂到云端后,会议间隙 push 一次,回房看日志即可。对我这种一年飞三四次东南亚的人,稳定构建机比「现场再买一台 Mac」更可预测。
FAQ
必须带 Mac 吗? 带一台能跑 Git 和 SSH 的笔电即可(Windows 也行,见 Windows + 云 Mac 选型文)。真机调试若必须,带测试机;编译仍放云上。
出差三天新租机构建机来得及吗? 来得及,但证书导入和首次全量编译应在国内完成;落地只做增量。
新加坡机房和「人在新加坡」必须同区吗? 不必须。同区降低 SSH RTT;北美上架仍可用加拿大机构建。
漫游流量够传 IPA 吗? 不够也不该传。IPA 从构建机直传 ASC,笔电只传几 MB 的 Git 对象。
和「编译扔上云」那篇重复吗? 那篇解决笔电卡顿;本篇解决地理位移 + 网络不确定下的 Runbook 与节点取舍。
OpenClaw / Agent 要一起搬吗? 不必。Gateway 可继续跑在既有加拿大机;人在新加坡只 SSH 触发构建。
公司 VPN 必须全程开着吗? 看策略。若 VPN 劫持全局流量导致 SSH 不稳定,可申请「分流」:仅内网走 VPN,构建机 SSH 走直连。很多团队用 Tailscale 只连构建机,不碰酒店 Wi‑Fi 的明文段。
时差怎么排发布窗口? 新加坡 UTC+8,与美西相差约 15~16 小时。若审核方在美东白天,可把 archive 挂在新加坡下午、上传在加拿大机构建机凌晨自动跑——与 跟太阳走北美批次 的排班兼容;出差人不必熬夜盯上传。
文中的 commit / Run ID 能核对吗? 为内部仓库实录,域名与 IP 已打码;你可按同样字段格式在自己的工单里留档,不必与我们的数字一致。