从连接到交付

跑通第一条 iOS 打包流水线

先确认能连接,再锁定工具链,最后接入 CI。这里按实际操作顺序,带你把一台独享物理 Mac 配成可重复执行的打包节点。非虚拟机,macOS 图形界面与命令行均可使用。

已有主机,从下方「开通与登录」开始;尚未选型,可先用单任务验证构建,再决定并发规模。

开通与登录

核对订单和主机凭据,完成首次 SSH 连接,添加自己的登录公钥。

查看首次连接步骤

远程连接

脚本和 CI 使用 SSH;调试 Xcode、模拟器与图形界面时使用 VNC 屏幕共享。

查看连接设置

CI 接入

注册 self-hosted runner,区分工具链标签,先把每台主机的构建并发设为 1。

配置 runner

账单与续费

核对计费周期、附加项和到期时间。订单、支付与续费状态以控制台返回为准。

查看账单指引
先确认连接链路

开通后的第一小时,先做这四件事

下面是建议的操作顺序,不是开通或处理时限承诺。先在控制台确认服务已开通,再使用该实例实际返回的地址、端口与用户名。不要根据示例猜测主机凭据。

  1. 核对实例

    登录控制台,在对应服务详情中核对机型、节点与租期。记录主机编号;后续排查、续费与工单都以这个编号关联。连接地址与初始凭据只在授权账户内查看。

  2. 首次 SSH 登录

    将控制台返回的信息填入本地连接参数。首次连接时核对主机密钥指纹;若没有可信的指纹来源,先通过工单确认,不要直接跳过校验。连接成功后运行系统与用户检查。

  3. 修改登录密码

    在交互式 SSH 会话中执行 passwd,按提示修改当前用户密码。密码不要写进命令参数、仓库、聊天记录或构建日志。保持当前会话,另开会话确认新凭据可用。

  4. 添加登录公钥

    在自己的电脑生成专用密钥,只把公钥加入主机。确认公钥登录成功后,再调整密码登录策略;涉及 SSH 服务设置前保留可用会话,避免把自己锁在主机外。

本地连接参数与主机侧检查
ssh -p "$MAC_SSH_PORT" "$MAC_USER@$MAC_HOST"

whoami
sw_vers
uname -m
xcode-select -p
df -h
passwd

连接前,在本地设置 MAC_SSH_PORT、MAC_USER、MAC_HOST,值取自控制台。其余命令在登录后的主机中执行;xcode-select -p 用于检查当前开发工具路径。

主机侧 · 添加已复制的公钥
install -d -m 700 "$HOME/.ssh"
touch "$HOME/.ssh/authorized_keys"
chmod 600 "$HOME/.ssh/authorized_keys"
printf '%s\n' "$LOGIN_PUBLIC_KEY" >> "$HOME/.ssh/authorized_keys"

先将 LOGIN_PUBLIC_KEY 设置为你的完整公钥文本,再执行追加命令。不要上传私钥,也不要重复追加同一公钥。连接超时、认证失败和主机密钥变化是不同问题,提交工单时应区分描述。

让环境可以复现

锁定 Xcode,再准备模拟器

先记录项目需要的 Xcode、SDK 与 runtime,而不是直接升级到最新版本。系统与 Xcode 的兼容性决定了可安装范围;已有项目应先沿用本地成功构建的组合,再单独验证升级。

如主机尚未安装 xcodes,先按团队认可的软件安装流程准备该工具。安装指定版本时,使用合法取得、与项目要求一致的本地 Xcode 安装包;下面的 XCODE_VERSION 与 XCODE_XIP 分别表示已核对的版本号和安装包绝对路径。不要把安装过程中的交互步骤放进无人值守的构建任务。

主机侧 · 安装与核对开发工具
xcodes installed
xcodes install "$XCODE_VERSION" --path "$XCODE_XIP"
xcodes installed

export DEVELOPER_DIR="$XCODE_APP/Contents/Developer"
xcodebuild -version
xcodebuild -showsdks
xcrun simctl list runtimes

XCODE_APP 应指向安装后的实际应用路径。对 CI,优先在任务环境中设置 DEVELOPER_DIR,避免多个任务争抢全局选择。如果确实要切换系统默认工具链,再执行 sudo xcode-select --switch "$XCODE_APP/Contents/Developer",随后重新核对 xcodebuild -version。

第一次启动所选 Xcode,完成必要的组件准备。图形界面可通过远程连接操作;随后在 Xcode 的组件设置中安装目标 runtime。执行 xcrun simctl list devices available,选择与测试目标匹配的模拟器,先交互式启动一次并等待启动完成,再跑一个最小测试任务。归档真机包与执行模拟器测试是不同步骤,不要把缺少 runtime 误判为归档工具链故障。

把工具链检查放在 CI 日志开头:至少输出 Xcode 版本、SDK 列表和代码提交号。更换 Xcode 后重新生成缓存键,并验证第三方依赖、签名与导出流程;只修改 runner 标签而不核对实际工具路径,无法保证构建环境一致。

先单任务,再扩展

把主机注册为 self-hosted runner

同一台物理节点上的任务会共享 CPU、内存、磁盘和用户环境。起步时每台只执行 1 个构建任务;确认归档、签名和清理都稳定后,再根据实际峰值占用增加并发,或增加独立节点。

GitLab Runner:标签与执行器一起核对

在项目的 runner 设置中创建项目 runner,准备对应的认证 token,并设置 macos、arm64、ios-archive 三个标签。将实例地址与认证 token 临时放入 GITLAB_URL、GITLAB_RUNNER_TOKEN,在专用构建用户下注册 shell executor。完成后清理临时 token 变量。

GitLab Runner · 注册与并发配置
gitlab-runner register \
  --non-interactive \
  --url "$GITLAB_URL" \
  --token "$GITLAB_RUNNER_TOKEN" \
  --executor "shell" \
  --description "armrdp-ios-archive"

unset GITLAB_RUNNER_TOKEN
gitlab-runner verify

在该 runner 实际使用的 config.toml 中,将全局 concurrent = 1,并为对应 runner 设置 limit = 1。按所安装版本的服务管理方式启动 runner;交互式验证成功不等于后台服务已运行。

项目侧 · .gitlab-ci.yml 中的归档任务
ios_archive:
  tags:
    - macos
    - arm64
    - ios-archive
  script:
    - xcodebuild -version
    - xcodebuild archive -workspace "$IOS_WORKSPACE" -scheme "$IOS_SCHEME" -destination 'generic/platform=iOS' -archivePath "$CI_PROJECT_DIR/output/App.xcarchive"
  artifacts:
    paths:
      - output/App.xcarchive
    expire_in: 7 days

在项目变量中设置实际的 IOS_WORKSPACE 和 IOS_SCHEME,并先完成下一节的签名准备。这里仅归档,不包含导出或上传步骤;产物保存 7 天是示例配置,可按团队保留策略修改。不要将证书、私钥或临时 Keychain 放进 artifacts。

GitHub Actions:一个 runner 进程接一项任务

从仓库设置提供的 runner 安装步骤取得适用于 macOS ARM64 的程序,进入解压目录。设置 REPOSITORY_URL 为当前仓库地址,RUNNER_TOKEN 为本次注册 token。标签必须同时出现在注册配置和工作流中,才能避免任务被路由到错误工具链。

GitHub Actions · 主机注册
./config.sh \
  --url "$REPOSITORY_URL" \
  --token "$RUNNER_TOKEN" \
  --name "armrdp-ios-archive" \
  --labels "macos,arm64,ios-archive" \
  --work "_work"

unset RUNNER_TOKEN
./run.sh
项目侧 · GitHub Actions 工作流
name: iOS archive
on:
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: ios-archive
  cancel-in-progress: false

jobs:
  archive:
    runs-on: [self-hosted, macos, arm64, ios-archive]
    env:
      DEVELOPER_DIR: ${{ vars.DEVELOPER_DIR }}
      IOS_WORKSPACE: ${{ vars.IOS_WORKSPACE }}
      IOS_SCHEME: ${{ vars.IOS_SCHEME }}
    steps:
      - uses: actions/checkout@v4
      - name: Archive
        run: |
          xcodebuild -version
          xcodebuild archive \
            -workspace "$IOS_WORKSPACE" \
            -scheme "$IOS_SCHEME" \
            -destination 'generic/platform=iOS' \
            -archivePath "$RUNNER_TEMP/App.xcarchive"

先设置仓库变量中的工具路径、workspace 与 scheme。该示例使用手动触发,便于先验证环境;不要直接向不受信任的外部代码开放带签名凭据的 runner。一个 runner 进程同时接一项任务,concurrency 还会约束同组工作流的执行;它不是主机级资源限额。

验证通过后,按下载程序提供的服务脚本配置常驻运行,并确认服务用户与手动测试用户一致。若把签名准备在一个用户下、服务却运行在另一个用户下,常见结果就是交互式构建成功、CI 找不到证书或描述文件。

凭据和代码分开

在构建用户下准备签名环境

分发证书、对应私钥和描述文件必须与项目使用的团队及 Bundle Identifier 匹配。先在受控任务中验证签名,再接入无人值守流程;不要用关闭签名检查来掩盖导出失败。

为自动化任务使用单独的构建 Keychain,密码由 CI 的受保护秘密变量注入。导入证书后,只授予确有需要的签名工具访问权限,不要允许所有应用访问私钥。下面的检查命令用于确认当前用户能看到有效签名身份;它不会证明描述文件、导出配置和项目设置已经完全匹配。

主机侧 · 检查签名身份与描述文件目录
security list-keychains -d user
security find-identity -v -p codesigning
printf '%s\n' "$HOME/Library/MobileDevice/Provisioning Profiles"
printf '%s\n' "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"

不同 Xcode 版本使用的描述文件位置可能不同,以当前工具链实际读取的路径为准。导入后检查有效期、应用标识和签名类型。归档成功但导出失败时,重点对照导出配置里的方式、团队标识及描述文件映射,而不是反复重新安装 Xcode。

团队已使用 fastlane match 时,沿用现有的加密证书仓库和访问策略。在验证阶段使用 bundle exec fastlane match appstore --readonly,先读取已有签名材料,避免构建节点意外修改团队证书。仓库访问凭据、解密密码和 Keychain 密码分别管理;生产签名变量只向受保护分支或受控手动任务开放。

任务结束后清理临时文件并锁定临时 Keychain。日志只保留必要的错误码和步骤结果,不输出私钥内容、证书密码、注册 token 或完整的秘密变量。独享物理机提供资源隔离,但项目内的凭据权限仍应按最小授权管理。

把缓存当作可重建数据

确认扩展卷,再迁移 DerivedData

可加购 +1TB SSD 或 +2TB SSD。先在订单与主机侧确认扩展已经完成,再安排数据迁移;卷名、挂载路径和文件系统以实际交付信息为准,不要直接照抄其他主机的磁盘标识。

执行 diskutil list 和 df -h,核对容量及挂载点。不要对不明磁盘执行抹除或分区命令。为构建用户创建有写入权限的缓存目录,并用小文件测试读写;确认重启后的挂载行为后,再让常驻 runner 使用新路径。

迁移前暂停接收新任务,等待正在运行的归档和签名完成。DerivedData 是可重建缓存,优先在新位置重新生成,而不是复制可能处于写入中的旧缓存。下面使用明确的 -derivedDataPath,无需把整个用户目录迁到扩展卷。

主机侧 · 检查挂载与指定构建缓存
diskutil list
df -h

test -d "$BUILD_VOLUME" && test -w "$BUILD_VOLUME"
mkdir -p "$BUILD_VOLUME/DerivedData/$CACHE_KEY"

xcodebuild archive \
  -workspace "$IOS_WORKSPACE" \
  -scheme "$IOS_SCHEME" \
  -destination 'generic/platform=iOS' \
  -derivedDataPath "$BUILD_VOLUME/DerivedData/$CACHE_KEY" \
  -archivePath "$BUILD_VOLUME/Archives/$BUILD_ID/App.xcarchive"

执行前,把 BUILD_VOLUME 设为已确认可写的挂载目录。CACHE_KEY 应区分仓库、Xcode 版本与构建配置;BUILD_ID 使用当前任务的唯一标识,避免不同任务覆盖归档。并行任务不要共用同一个可写 DerivedData 目录。

先完成一次无缓存构建,再观察后续任务的缓存命中和磁盘增长。为归档、模拟器数据和依赖缓存分别设置保留策略;删除前确认没有任务仍在使用。构建产物应另外保存到团队控制的产物存储,不能把主机缓存当作唯一备份。租期结束前完成导出,并验证备份能读取。

让问题能被复现

工单里写清步骤,比只贴报错更有效

涉及主机、订单或支付的问题,请登录控制台提交工单,便于关联实际服务。无法登录时,可发邮件至 support@armrdp.com。这两种方式都是正式联系入口。

按问题类型补充信息,不提交密码、私钥或完整支付凭据。
字段建议填写内容用途
主机或订单编号控制台中的对应编号,以及当前节点定位具体服务,避免查错实例
发生时间开始与结束时间、时区、是否仍持续对齐服务记录与任务日志
问题类别连接超时、认证失败、构建失败、签名失败或账单状态区分网络、工具链和结算问题
复现步骤执行顺序、最小命令、预期结果与实际结果减少来回确认所需的上下文
环境与日志Xcode 版本、runner 服务用户、错误前后的脱敏日志确认是否与版本或权限相关
业务影响单个任务还是整台主机受影响,是否有可用的替代构建路径说明影响范围和当前处置情况

回复与解决时间,分开看

工单处理进度与回复时间以控制台内的实际记录为准,不把首次回复等同于故障解决。连接问题可能需要核对主机状态与访问链路,构建问题通常还需要任务日志和复现步骤,支付问题则需要关联订单及交易记录;这些情况不能共用一个固定解决小时数。

等待期间,建议把新增证据补充到同一工单,注明已等待时长和影响是否扩大。不要重复执行可能改变现场的重装、清盘或批量清理操作。若错误只出现在 CI,保留同一用户下交互式执行与服务执行的差异;若涉及签名,仅提供脱敏后的错误信息。

账单、续费与到期前检查

服务按天、周、月、季预付,查看订单时应同时核对机型、节点、周期起止与附加项。续费、是否启用自动续费及付款结果,都以控制台当前设置和实际返回为准;提交付款并不等于订单已经开通,不要仅凭支付页面截图判断服务状态。

仅支持 USDT-TRC20 与 Visa / Mastercard / Amex(经 Stripe),全部以美元结算,实际可用网关以后端返回为准。使用链上支付时核对网络、收款信息和金额;需要排查时提供订单编号与交易标识,不提供钱包私钥或完整银行卡信息。

到期前先停止新增构建,导出代码变更、归档产物及必要配置,并在自己的存储中核对可恢复性。若准备继续使用,完成续费后再次确认新的到期时间;若准备结束租用,不要把到期后的主机当作长期存档位置。

先让一个 scheme 稳定出包

从单台、单任务开始,验证工具链、签名和产物,再扩展到多台 runner。独享物理 Mac 按天起租,入口价 $21.1/天;具体可用状态以控制台实时返回为准。