症状:工作流已经写好,Runner 也显示在线,但任务仍然排队、调用了错误的 Xcode,或者 Archive 成功后迟迟看不到 TestFlight 构建。
最快解法:GitHub Actions iOS 打包不能只注册 Runner 就投入生产,必须同时验收主机可用性、Xcode 基线、签名隔离、任务路由和重启恢复。
这篇教程适合哪些开发者
这篇内容适合已经使用 GitHub 托管代码,希望自动完成 iOS 构建、签名和发布的独立开发者,也适合没有本地 Mac、准备把远程 macOS 主机改造成常驻打包机的 Windows 或 Linux 开发者。
如果团队只想偶尔手动打一个测试包,完整的常驻 Runner 方案可能过重;如果需要固定原生依赖、持续集成和 TestFlight 发布,下面的验收方法更适合长期使用。
先用 5 个指标判断方案是否合格
GitHub Actions 可以调度远程 Mac 上的 self-hosted runner,但“Runner 在线”只说明它能连接调度平台,不代表已经具备生产发布能力。一个可用的 iOS 打包环境,至少要同时满足以下条件:
- ✅ 主机能够持续运行,网络连接稳定,远程会话断开后任务不会随之停止。
- ✅ Runner 具备明确的
macOS、ARM64和发布用途标签。 - ✅ Xcode、macOS、SDK、依赖锁文件和构建 Scheme 已固定。
- ✅ 代码签名证书、私钥、Provisioning Profile 和上传凭据没有写入仓库。
- ✅ 真实项目能够分别通过编译、Archive、签名导出、上传和后台处理验收。
GitHub 官方支持把自托管 Runner 注册到仓库、组织或企业层级;注册时生成的令牌是限时令牌,官方文档说明其有效期为 1 小时,因此不能把旧令牌长期保存在脚本或密码管理器中反复使用。添加 self-hosted runner 的官方步骤
| 决策维度 | 短期测试 Runner | 常驻发布 Runner |
|---|---|---|
| 适合场景 | 验证项目能否在远程 Mac 编译 | 持续执行 Archive、签名和 TestFlight 上传 |
| 主机要求 | 临时可用、手动启动也可以 | 重启后自动恢复,长期保持在线 |
| 权限策略 | 可使用测试项目和测试凭据 | 仅限私有仓库、受控分支和发布工作流 |
| 工具链管理 | 先确认能否编译 | 固定 Xcode、SDK、依赖和 Scheme |
| 验收标准 | 编译成功 | 编译、Archive、导出、上传、后台处理全部可追踪 |
| 是否适合直接生产 | ❌ 不建议 | ✅ 通过完整验收后才适合 |
远程 Mac 需要先满足哪些条件?
先核对 macOS、Xcode 与 SDK 的组合
远程 Mac 不是普通 Linux 构建机。Xcode 对 macOS 版本、SDK、设备支持和模拟器都有对应限制,主机系统不满足要求时,Runner 即使在线,也可能在安装工具链或启动构建阶段失败。
写作时 Apple 官方系统要求页面列出:Xcode 26.6 支持 macOS Tahoe 26.2 至 macOS Tahoe 26.x,并包含 iOS 26.5 SDK;Apple 另行说明,自 2026 年 4 月 28 日起,上传到 App Store Connect 的 App 必须使用 Xcode 26 或更高版本,并采用 iOS 26 等对应 SDK 构建。正式配置前,应以当前官方系统要求页重新核对,而不是只看 Runner 的操作系统标签。Apple Xcode 系统要求与上传要求
主机验收时至少检查:
sw_vers
xcode-select -p
xcodebuild -version
xcodebuild -showsdks
uname -m
df -h
重点不是把所有 Xcode 版本都安装到同一台主机,而是确认当前项目实际调用的开发者目录、SDK 和架构。若一台远程 Mac 同时存在多个 Xcode,工作流必须显式设置:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version
路径中的 Xcode.app 只是示例占位符。如果实际安装目录不同,应替换成经过人工确认的路径,不能把不存在的目录直接写进生产工作流。
按仓库或组织范围注册 Runner
在仓库的设置页面创建 self-hosted runner 后,GitHub 会根据操作系统和架构显示安装命令。下载 Runner 后,在远程 Mac 上执行官方生成的配置命令;示例中的地址、令牌和目录均使用占位符:
mkdir -p ~/actions-runner
cd ~/actions-runner
# 下载并解压官方 Runner 程序
# 下载地址、版本和校验值以 GitHub 页面实时生成内容为准
./config.sh \
--url https://github.com/<OWNER>/<REPOSITORY> \
--token <RUNNER_REGISTRATION_TOKEN> \
--name <RUNNER_NAME> \
--labels self-hosted,macOS,ARM64,ios-release
配置结束后先不要马上运行发布任务,先执行:
./run.sh
终端应出现已连接并等待任务的状态,GitHub 设置页面也应显示 Runner 为 Idle,而不是 Offline。官方文档明确指出,Runner 应用必须处于活动状态,才能接收工作流任务。Runner 状态与故障排查文档
如何固定 Runner 的 Xcode 工具链版本?
用标签锁定 macOS 架构和发布用途
Runner 的默认标签可以帮助工作流筛选操作系统和架构,但默认标签并不能替代工具链检查。建议为生产发布主机添加用途明确的自定义标签,例如:
jobs:
release:
runs-on:
- self-hosted
- macOS
- ARM64
- ios-release
ios-release 的含义应保持单一:只表示这台 Runner 允许执行发布链路。不要使用过于宽泛的 build、test 等标签,否则普通测试任务也可能抢占发布主机。
GitHub 的标签匹配是“工作流要求的标签全部满足后,任务才会被分配”;标签名称不区分大小写。若没有同时满足 self-hosted、macOS、ARM64 和 ios-release 的 Runner,任务会继续处于排队状态,而不是自动切换到不符合条件的主机。GitHub 标签路由说明
随后在工作流中固定工具链,而不是相信主机当前的默认设置:
steps:
- name: Select Xcode
run: |
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version
xcodebuild -showsdks
- name: Restore dependencies
run: |
xcodebuild -resolvePackageDependencies \
-project <PROJECT_NAME>.xcodeproj \
-scheme <SCHEME_NAME>
- name: Build without signing
run: |
xcodebuild \
-project <PROJECT_NAME>.xcodeproj \
-scheme <SCHEME_NAME> \
-sdk iphoneos \
-configuration Release \
CODE_SIGNING_ALLOWED=NO \
build
这一步的目的,是先证明源码、依赖、Scheme 和编译工具链能够重复运行。若不含签名材料的最小构建都不能稳定通过,继续导入证书只会把“编译问题”伪装成“签名问题”。
⚠️ 不要在工作流里把 Xcode 版本写成“最新版”。Xcode 更新可能同时改变 SDK、Swift 编译器和上传边界;生产 Runner 应使用明确路径,并在版本升级前重新执行完整验收。
代码签名材料怎样保存和隔离?
分清 4 类不同材料
安全保存 GitHub Actions 的 iOS 签名证书,重点不是把全部内容塞进一个 Secret,而是先按用途拆分:
- 分发证书和私钥:用于对 App 进行签名,私钥通常需要进入临时 Keychain。
- Provisioning Profile:必须与 Bundle ID、团队和分发用途匹配。
- App Store Connect API Key:用于上传或调用后台接口,不能代替代码签名证书和 Provisioning Profile。
- GitHub Actions Secrets:只保存加密后的材料或临时密码,不保存真实文件到代码仓库。
Apple 的文档明确区分了开发证书、分发证书和 Provisioning Profile;用于上传 App Store Connect 的配置文件需要显式 App ID,并包含一个分发证书。Apple 证书与 Provisioning Profile 说明
在仓库中只保留明显脱敏的变量名:
env:
KEYCHAIN_NAME: ci-temporary.keychain-db
KEYCHAIN_PASSWORD: <TEMPORARY_KEYCHAIN_PASSWORD>
ASC_KEY_ID: <APP_STORE_CONNECT_KEY_ID>
ASC_ISSUER_ID: <APP_STORE_CONNECT_ISSUER_ID>
证书、私钥和 Profile 可以通过 GitHub Secrets 注入到 Runner 的临时目录,再在任务结束时删除。示例不应出现真实的 Bundle ID、Team ID、证书名称、密码、API Key、文件路径或日志内容。
临时 Keychain 解决的是构建完成后减少凭据残留;最小权限解决的是上传账号权限过大;任务后清理解决的是下一次任务读取旧材料;凭据轮换解决的是材料已经暴露后的持续风险。这几项不是同一个控制点,不能用一枚 API Key 替代完整的代码签名材料。
限制发布 Runner 的访问范围
常驻发布主机不应接收任意分支、任意 Pull Request 或公开仓库 Fork 发起的工作流。GitHub 官方建议谨慎使用 self-hosted runner,因为不受信任的代码可能在主机上执行,并接触到环境变量、工作目录或其他凭据。GitHub 自托管 Runner 安全建议
建议采用以下边界:
- 发布 Runner 只绑定私有仓库或经过严格控制的组织仓库。
- 只有受保护分支或人工批准的发布工作流才能调用
ios-release标签。 - 普通单元测试使用另一组 Runner,不与签名主机混用。
- 不允许外部贡献者的代码直接触发带有发布凭据的任务。
- 工作流日志中禁止输出证书内容、私钥、临时密码和完整上传响应。
如果团队需要进一步整理凭据,可以参考 GitHub Actions iOS 签名凭据配置指南;重点不是把 Secret 数量做多,而是让每一份材料只在必须的步骤、必须的主机和必须的权限范围内出现。
把 Archive、导出和 TestFlight 上传拆开验收
用分层任务定位失败位置
GitHub Actions iOS 打包最容易误判的地方,是把 xcodebuild 返回成功等同于“App 已经发布”。实际上,构建、Archive、导出、上传和后台处理是不同阶段,失败原因也不同。
可以按下面的顺序组织工作流:
jobs:
release:
runs-on:
- self-hosted
- macOS
- ARM64
- ios-release
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Verify toolchain
run: |
xcode-select -p
xcodebuild -version
xcodebuild -showsdks
- name: Resolve dependencies
run: |
xcodebuild -resolvePackageDependencies \
-workspace <WORKSPACE_NAME>.xcworkspace \
-scheme <SCHEME_NAME>
- name: Archive
run: |
xcodebuild archive \
-workspace <WORKSPACE_NAME>.xcworkspace \
-scheme <SCHEME_NAME> \
-archivePath "$RUNNER_TEMP/<APP_NAME>.xcarchive"
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/<APP_NAME>.xcarchive" \
-exportOptionsPlist <EXPORT_OPTIONS_PLIST_PATH> \
-exportPath "$RUNNER_TEMP/export"
- name: Upload to TestFlight
run: |
xcrun altool \
--upload-app \
-f "$RUNNER_TEMP/export/<APP_NAME>.ipa" \
-t ios \
-u <UPLOAD_ACCOUNT_PLACEHOLDER> \
-p <UPLOAD_PASSWORD_PLACEHOLDER>
示例中的上传参数仅用于展示结构,不能直接复制到生产环境;团队也可以使用 App Store Connect API 和 JWT 方式上传。Apple 说明,上传后构建还需要经过后台处理,处理完成前不会立即出现在 App Store Connect 中,因此“上传命令退出成功”并不代表 TestFlight 已经可以安装。Apple 上传构建说明
为每一层保留不同证据
建议在工作流中分别保存:
- 依赖恢复日志:确认锁文件、网络和依赖缓存是否正常。
- 编译日志:确认 Swift、原生依赖和 SDK 是否兼容。
.xcarchive:用于检查 Archive 是否生成以及包含哪些架构。- 导出日志和 IPA 文件:确认签名导出是否使用了正确的分发配置。
- 上传日志:确认传输阶段是否成功。
- 构建编号、上传状态和后台处理结果:确认 TestFlight 是否真正出现构建。
首次上线前,最好使用脱敏测试项目走完整链路,并人为制造一次错误,例如暂时使用不匹配的 Profile。验收记录应能回答:这是工具链错误、签名错误、传输错误,还是 App Store Connect 后台仍在处理,而不是只留下一个“Job failed”。
让远程 Mac 在重启后自动恢复
把交互式启动改成系统服务
直接运行 ./run.sh 适合初次验证,不适合作为常驻打包机。关闭 SSH 或 VNC 会话后,交互式进程可能结束;远程 Mac 重启后,也不会自动恢复。
在 Runner 目录中执行:
cd ~/actions-runner
./svc.sh install
./svc.sh start
./svc.sh status
GitHub 官方说明,macOS Runner 可以通过系统服务在主机启动时自动运行;状态检查应确认服务已经启动,而不是只确认配置文件还存在。GitHub Runner 服务配置文档
重启恢复验收至少包含以下动作:
- 记录当前 Runner 名称、标签和注册范围。
- 停止交互式
./run.sh,确认服务可以独立启动。 - 重启远程 Mac。
- 通过
./svc.sh status和 GitHub 设置页面确认 Runner 回到Idle。 - 提交一个无签名最小构建,确认重启后可以接单。
- 检查
xcode-select、Keychain 权限和工作目录是否仍然正确。 - 若主机不再使用,撤销 Runner 注册并删除本地配置。
长期运行还需要检查 Runner 更新、磁盘增长、依赖缓存失效和构建中断后的清理方式。若主机出现离线状态,应区分主机断电、服务停止、Runner 更新失败和访问权限失效,而不是直接重复注册。
上线前的可勾选验收清单
主机与路由
- [ ] macOS 版本满足目标 Xcode 的官方系统要求。
- [ ]
uname -m与工作流中的ARM64标签一致。 - [ ] 磁盘空间、网络连接和远程登录方式已验证。
- [ ]
self-hosted、macOS、ARM64、ios-release标签均能匹配。 - [ ] 没有匹配 Runner 时,团队能够发现排队状态并触发告警。
- [ ] 工作流设置了合理的超时,而不是无限等待。
工具链与项目
- [ ]
xcode-select -p指向经过确认的 Xcode。 - [ ] 项目依赖锁文件已提交并能够恢复。
- [ ] Scheme、Configuration 和 SDK 选择固定。
- [ ] 不含签名材料的最小构建已经通过。
- [ ] Archive 输出路径不会与并发任务互相覆盖。
凭据与访问
- [ ] 证书私钥不在仓库、不出现在日志中。
- [ ] Provisioning Profile 与 Bundle ID 和分发用途一致。
- [ ] App Store Connect 上传凭据与代码签名材料分开管理。
- [ ] 临时 Keychain 在任务结束后清理。
- [ ] 发布 Runner 不接收公开仓库 Fork 或未经审核的外部代码。
- [ ] Runner 注册、撤销和凭据轮换都有记录。
发布结果
- [ ] 编译、Archive、导出、上传各自有独立日志。
- [ ] IPA 文件可以被验收步骤找到。
- [ ] 上传响应没有被误读为后台处理完成。
- [ ] TestFlight 构建已经在 App Store Connect 中出现。
- [ ] 构建编号、版本号和 Bundle ID 与预期一致。
- [ ] 首次真实发布已经能定位每一层的失败原因。
如果团队还没有可长期运行的 macOS 环境,可以先查看 远程 Mac 持续集成环境验收清单;短期测试阶段不必一开始就承诺常驻运行,先用真实项目完成 Archive、签名和上传,再决定是否扩大到持续集成。
当前方案和远程 Mac,应该怎样做选择
如果现有方案是本地 Windows 或 Linux 加上临时虚拟机,常见缺点是 Xcode 无法直接运行、macOS 版本和原生依赖难以固定,而且主机重启后 Runner、Keychain 和构建环境往往需要人工恢复。若改用公共 CI 环境,工具链选择、缓存行为、并发排队和签名材料边界又可能不完全受团队控制。
对于只需偶尔打包的开发者,本地借用设备或短期测试环境可能更简单;对于需要持续运行 GitHub Actions、固定 Xcode 并保留完整 root 权限的项目,租赁 RUVCLOUD 的远程 Mac 更适合先验证生产链路,再决定是否长期投入自购 Mac。可以根据项目周期查看 RUVCLOUD 的 Mac 方案,先选择短周期完成 Runner 注册、重启恢复和 TestFlight 验收,确认工作流稳定后再调整使用周期。