Apple 官方文档明确区分了系统上下文的 LaunchDaemon 与用户会话中的 LaunchAgent;因此,需要 Keychain、代码签名或 iOS Simulator 的 CI Agent,不应简单塞进 LaunchDaemon。本周建议先采用“系统恢复层 + 专用用户构建层”:LaunchDaemon 负责主机健康检查、网络与恢复辅助,LaunchAgent 负责真正的用户态构建,并用真实签名流水线验证恢复结果。(developer.apple.com)

01

适用对象与判断边界

这篇文章适合负责 Mac 构建节点无人值守运行的 IT 负责人,尤其是正在处理“Mac 已重启、SSH 可达,但流水线仍离线”的团队。

如果团队只运行不依赖用户目录、Keychain 或图形会话的主机级探针,LaunchDaemon 可以独立承担任务;如果涉及签名、模拟器或用户凭证,则应继续读完运行上下文和恢复验收部分。

02

运行上下文

LaunchDaemon、全局 LaunchAgent 与用户级 LaunchAgent

在 macOS 中,“开机启动”“用户登录后启动”和“能够执行生产构建”不是同一个状态。LaunchDaemon 属于系统上下文,通常在系统启动阶段由 launchd 管理;LaunchAgent 则属于用户会话,在相应用户登录后运行。Apple 对这两类服务的生命周期和身份边界有明确说明。(developer.apple.com)

模式 启动条件 运行身份 适合职责 主要限制
LaunchDaemon 系统启动或按需触发 系统账号或指定服务账号 主机探活、端口检查、恢复辅助、无用户依赖的监控 默认没有交互式用户会话,不能假设用户 Keychain 或模拟器可用
全局 LaunchAgent 指定用户登录后 登录用户 团队统一安装的 CI Agent、用户态构建服务 用户退出后停止;必须验证登录链路
用户级 LaunchAgent 指定用户登录后 专用 CI 账号 签名、模拟器、用户目录、构建缓存 配置依赖该账号,账号策略和会话必须可恢复

Apple 官方资料还指出,用户登录时会启动该用户的 launchd,并加载系统级和用户目录中的 LaunchAgent;用户退出时,相关 Agent 会收到终止信号。换句话说,LaunchAgent 的“自动拉起”通常是登录事件之后的自动拉起,而不是磁盘刚解锁时就已经可用。(developer.apple.com)

因此,验收时必须把状态拆开记录:

  • 主机已经完成启动;
  • 启动磁盘已经解锁;
  • 专用构建账号已经建立用户会话;
  • CI Agent 已注册并在线;
  • Keychain 已可访问;
  • 真实构建已经完成签名或模拟器测试。

控制台显示在线,只能证明其中一部分。它不能替代生产构建成功证据。

iOS CI 的运行归属

对于 iOS CI,默认选择不是把所有任务都交给一种服务,而是按依赖拆分。

某主流 CI Agent 的官方 macOS 安装文档明确将用户模式 LaunchAgent 作为支持模式,并指出该模式运行在已认证用户下,能够使用用户 Keychain 和图形会话;同一文档同时说明,系统级 LaunchDaemon 没有用户会话,不能直接替代这种部署方式。(docs.gitlab.com)

这条结论不能机械推广到所有 CI 工具。不同 Agent 的安装方式、权限需求和官方支持范围必须单独核查,但它足以说明一个常见边界:如果流水线必须读取签名身份、调用 iOS Simulator 或访问用户目录,用户级 LaunchAgent 通常更符合运行条件。

03

用户态依赖

Keychain 访问边界

问题通常不在 launchd 没有启动进程,而在于进程启动时所处的安全上下文不同。

官方技术说明指出,在用户上下文之外运行的 launchd daemon 必须面向文件型 Keychain;数据保护 Keychain 只能由用户上下文中的程序访问。也就是说,将依赖登录用户 Keychain 的签名流程直接改成 root LaunchDaemon,可能导致身份查询失败、临时 Keychain 不可用,或构建过程在签名阶段中断。(developer.apple.com)

代码签名还涉及证书、私钥、访问控制和签名身份是否被当前进程识别。官方代码签名文档建议通过 security find-identity -p codesigning -v 检查当前环境可见的签名身份,而不是只检查证书文件是否存在。(developer.apple.com)

建议在专用 CI 用户会话中执行以下检查:

whoami
security find-identity -p codesigning -v
security list-keychains
xcrun simctl list devices

预期结果不应只是命令返回成功,而应包括:

  • whoami 输出专用 CI 账号;
  • 签名身份列表中存在流水线需要的有效身份;
  • Keychain 搜索路径指向受控位置;
  • Simulator 设备列表能够正常返回;
  • 后续真实构建能够完成签名或测试。

iOS Simulator、窗口服务与用户目录

模拟器并不是一个单纯的后台二进制程序。它可能依赖用户目录、图形会话、设备运行时、缓存路径和当前用户拥有的服务状态。因此,SSH 能登录并不等于模拟器已经具备可运行条件。

对于采用 LaunchAgent 的 CI Agent,配置文件应放在专用账号的用户目录中,并让 Agent 使用固定的 HOME、工作区和日志目录。下面的片段只保留用于说明运行身份和输出位置的最小字段,实际字段必须以所用 Agent 的官方安装文档为准:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.example.ci-agent</string>
    <key>ProgramArguments</key>
    <array>
        <string>/path/to/ci-agent</string>
        <string>run</string>
    </array>
    <key>StandardOutPath</key>
    <string>/Users/ci/Library/Logs/ci-agent.out.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/ci/Library/Logs/ci-agent.err.log</string>
</dict>
</plist>

不要把 UserNameHOME、Keychain 路径和工作区路径混在多个脚本中隐式传递。配置越分散,重启后越难判断是 Agent 没启动、账号不对,还是构建环境变量丢失。

04

无人值守恢复链路

FileVault 与无人值守恢复

FileVault 会把“磁盘解锁”和“用户登录”连在一起,但企业验收不能因此假设所有策略都能无人值守。官方资料说明,在部分配置下,成功完成 FileVault 解锁后会自动进入对应用户;同时,自动登录选项在开启 FileVault 时可能不可用或被组织策略禁止。(support.apple.com)

企业需要先明确恢复模型:

  1. 允许受控的专用 CI 账号登录:适合需要用户 Keychain、Simulator 和签名的节点,但必须评估凭证暴露风险。
  2. 人工或远程完成 FileVault 解锁:安全边界更清晰,但不属于完全无人值守,需要把人工介入时间计入 SLA。
  3. 使用设备管理和恢复密钥策略:适合有集中管理能力的团队,必须验证恢复密钥托管、权限审批和审计记录。
  4. 利用系统版本支持的远程解锁能力:官方安全文档说明,Apple Silicon 且运行 macOS 26 或更高版本的设备,在启用 Remote Login 并具备网络连接时,可以在重启后通过 SSH 解锁 FileVault;这项能力仍需结合企业安全策略和实际版本进行验证。(support.apple.com)

因此,FileVault 的验收不能只问“能不能自动重启”。真正要问的是:重启后谁能够解锁磁盘、谁能够建立用户会话、凭证是否进入可用状态,以及这些动作是否留下审计记录。

重启恢复场景

建议至少执行以下 5 类测试,每类测试都保存时间、主机状态、Agent 状态、Keychain 检查结果和真实构建编号:

  • [ ] 冷启动:从关机状态启动,确认主机可达、磁盘解锁、用户会话和 CI Agent 依次恢复。
  • [ ] 计划重启:通过受控命令重启,确认 Agent 不依赖人工打开终端或图形应用。
  • [ ] 意外断电:模拟异常掉电后恢复,确认工作区不会因锁文件或缓存损坏而阻塞下一次构建。
  • [ ] 用户退出:让专用 CI 账号退出,确认系统能够识别 Agent 离线,而不是继续报告虚假的在线状态。
  • [ ] FileVault 解锁:分别验证有人工介入和无人工介入两种策略,记录哪一个环节阻断了生产流水线。
  • [ ] 真实签名构建:不要只运行健康检查,要执行包含签名和必要模拟器步骤的流水线。
  • [ ] 远程重启回滚:测试失败后,确认 IT 能够远程停止错误 Agent、恢复上一版配置并重新注册节点。

⚠️ 经验上,KeepAlive 只能表达“进程应保持运行”或“退出后尝试重新启动”的管理意图,不能证明账号正确、Keychain 可用或构建逻辑成功。持续崩溃的 Agent 可能被反复拉起,却仍然无法接单;因此必须把退出状态和真实构建结果分开记录。相关启动行为和异常处理边界应以官方 launchd 文档为准。(developer.apple.com)

05

权限与可观测性

双层启动架构

生产环境建议按职责分层,而不是让一个 root 进程承担所有任务。

系统恢复层使用 LaunchDaemon,负责:

  • 检查主机是否在线、磁盘空间和基础网络是否可用;
  • 检查用户态 CI Agent 是否存在、是否长时间未更新状态;
  • 记录进程退出码、重启次数和配置版本;
  • 触发受控恢复动作,但不读取发布证书和私钥;
  • 在无法恢复时发出告警并标记节点不可调度。

用户构建层使用专用账号的 LaunchAgent,负责:

  • 访问用户目录和工作区;
  • 读取受控 Keychain;
  • 执行代码签名;
  • 调用 Simulator 和 Xcode 工具链;
  • 向 CI 控制面注册、接单和回传日志。

Apple 的服务设计文档也给出了类似的职责分离思路:如果一个系统同时提供用户无关服务和用户专属服务,可以分别使用 daemon 与 agent,让系统层处理独立能力,让用户层处理会话相关能力。(developer.apple.com)

最小权限指标

权限决策不要只写“使用非 root 账号”,而要记录每类账号实际能访问的范围:

运行账号 可访问内容 适合任务 否决条件
root 或系统账号 主机级目录、系统服务和网络配置 健康检查、恢复辅助 直接读取发布 Keychain、执行完整签名流水线
专用 CI 账号 专用工作区、用户 Keychain、模拟器和构建缓存 生产构建、签名、测试 与开发者个人账号共用凭证或管理员权限
共享管理员账号 范围通常过大,审计边界不清 临时迁移或人工维护 长期无人值守生产构建

每次升级系统、Agent 或配置文件,都应先在隔离节点复测。至少保存以下信息:

# 查看当前用户域中的服务状态
launchctl print "gui/$(id -u)/com.example.ci-agent"

# 查看进程与退出状态
ps -axo user,pid,ppid,state,etime,command | grep -i ci-agent

# 检查日志最近内容
tail -n 80 "$HOME/Library/Logs/ci-agent.err.log"

日志中应能区分 4 类故障:进程崩溃、用户会话缺失、凭证不可用、任务路由失败。只记录“服务在线”或“端口可访问”,无法支持采购验收和故障追责。

06

方案准入与节点选择

在结尾前,可以用下面两张表判断现有主机是否值得整改。第一张表关注启动模式,第二张表关注供应和容量要求;两者不能互相替代。

方案 适用条件 关键证据 直接否决项
仅 LaunchAgent 构建完全依赖用户会话,且已有可靠登录与解锁流程 用户会话建立、Keychain 可用、真实签名成功 重启后无法建立会话,或没有远程恢复路径
仅 LaunchDaemon 任务不依赖用户 Keychain、Simulator 和图形服务 系统启动后探针、网络和任务执行成功 构建需要用户态签名或模拟器
双层架构 生产节点需要无人值守,同时存在用户态构建依赖 主机恢复、Agent 注册、凭证隔离、真实构建、远程重启 系统层与用户层共用高权限凭证,或没有备用恢复方式
验收指标 通过标准 不通过时的处理
主机可达 重启后 SSH 或管理通道可用 检查网络、磁盘解锁和远程管理策略
用户会话 专用 CI 账号进入预期会话 回退到登录策略和 FileVault 解锁链路
Agent 注册 Agent 使用正确账号在线并能接单 核查 LaunchAgent 域、配置路径和账号归属
Keychain 签名身份可见,临时 Keychain 可读写 禁止把流程直接迁移到 root LaunchDaemon
真实流水线 签名、测试和产物回传均成功 节点不得进入生产调度
远程恢复 IT 能完成重启、停止错误进程和回滚配置 增加系统恢复层或改用可独立恢复的节点
备用容量 主节点不可用时有明确切换路径 重新评估节点数量、租赁周期和容量冗余

如果现有 Mac 只能做到“重启后 SSH 在线”,却无法独立完成用户会话、Keychain 恢复和真实签名构建,那么问题不是再增加一个 KeepAlive 参数就能解决,而是节点的恢复链路没有达到生产准入标准。

对于长期重负载、必须接入本地硬件或需要完全掌握物理设备的团队,自购 Mac 仍然可能更合适;但如果目标是临时增加 CI 容量、快速补充备用节点,或者需要远程重启、完整 root 权限和可独立验证的 Mac 环境,传统自购方案往往还要承担采购周期、硬件维护、故障替换和闲置折旧。此时,使用 NodeMini 的远程 Mac 租赁方案,可以先按实际流水线验证用户会话、签名和恢复流程,再决定是否扩大长期容量;相关的 Mac 远程算力租赁方案 可作为节点采购评估的一部分。

本周可以先复制上面的验收清单,在一台非核心节点上完成冷启动、计划重启、FileVault 解锁和真实签名构建。如果现有节点无法提供独立用户会话、远程重启与备用容量,再把 企业 Mac 构建节点方案 纳入供应评估,而不是继续用“主机在线”替代“流水线已恢复”。