Mac 構建機 launchd 的企業部署,應採用「系統恢復層+使用者建置層」雙層架構:LaunchDaemon 負責主機級檢查與恢復輔助,專用服務帳號的 LaunchAgent 負責需要 Keychain、程式碼簽名或模擬器的 CI Agent。這個判斷適用於需要在重啟後恢復 iOS/macOS 流水線,而不是只要求 SSH 重新連線的團隊。

時間表:主機重啟後,應依序確認磁碟解鎖、使用者工作階段、Agent 註冊、Keychain 可用及真實建置成功;本週建議動作:先在隔離節點完成一次冷啟動與意外斷電驗收,再決定現有 Mac 是否值得整改。

本文適合負責 Mac 構建節點無人值守運行的 IT 負責人,也適合管理 iOS 簽名、模擬器測試和 CI 平台的研發效能負責人。若正在採購遠端 Mac 資源,本文也會把「恢復能力」轉化為供應商與容量驗收條件。

01

先分清三種「已啟動」狀態

Mac 構建機最常見的誤判,是把開機、Agent 在線和流水線可用視為同一件事。實際上至少有三個不同狀態:

  • 開機啟動:作業系統已載入,主機可能可以回應 SSH。
  • 使用者登入後啟動:指定的服務帳號已建立工作階段,使用者級 LaunchAgent 才有合理的執行上下文。
  • 能執行生產建置:CI Agent 能讀取受控憑證、呼叫 Xcode 工具鏈、啟動 iOS Simulator,並完成一條真實流水線。

Apple 將 LaunchDaemon 定義為系統上下文服務,LaunchAgent 則與使用者工作階段相關;Apple 的 launchd 工作說明是判斷服務歸屬的基準。Apple 對背景服務設計的進一步說明,也可參考系統 Daemon 的設計原則

因此,不能因為 LaunchDaemon 能在開機階段執行,就推論它適合直接承載所有 iOS CI 工作。企業應先列出每個任務需要的身份、目錄、網路權限、圖形服務與憑證,再選擇 launchd 域。

02

Mac 構建機 launchd 的選型邊界

LaunchDaemon:適合系統恢復,不適合直接持有簽名工作

LaunchDaemon 的合理職責包括:

  • 檢查磁碟、網路和主機健康狀態;
  • 在 Agent 未註冊時產生告警或執行受限恢復動作;
  • 回報節點版本、磁碟空間和最後一次重啟狀態;
  • 協助遠端管理系統判斷主機是否已恢復。

它不應直接讀取發佈憑證、操作專用建置帳號的 Keychain,或以 root 身份執行整條流水線。高權限並不會自動帶來正確的使用者工作階段,反而可能擴大程式碼、工作區和簽名資產的可見範圍。

全域 LaunchAgent 與使用者級 LaunchAgent

全域 LaunchAgent 可以由管理政策部署,但實際建置仍須確認它究竟在哪個使用者工作階段中執行。使用者級 LaunchAgent 的邊界較清楚:由專用 CI 帳號載入,工作目錄、Keychain 和工具鏈均歸屬該帳號。

GitLab 的 macOS Runner 文件明確描述其支援使用者模式 LaunchAgent;官方安裝說明可作為可驗證案例,但不能把單一 CI 平台的模式強制套用到 Jenkins、TeamCity 或自建 Agent。每一個 Agent 都應核對自己的服務安裝文件、升級行為與退出處理。

判斷可以簡化為:

  • 任務只需要主機級檢查:可考慮 LaunchDaemon。
  • 任務需要登入工作階段、Keychain 或 Simulator:優先使用專用帳號的 LaunchAgent。
  • 任務同時需要無人值守恢復和使用者態建置:採用雙層架構,而非二選一。
03

Keychain、簽名與 Simulator 是真正的分水嶺

iOS CI 的難點不在於「能否啟動一個程序」,而在於該程序是否取得正確且可審計的工作上下文。程式碼簽名通常需要存取憑證和私密金鑰;Apple 對 Mac Keychain 的分類、存取與管理方式,應以Apple Keychain 技術說明為準。

建置團隊應把以下依賴逐項列出:

  • 簽名資產:憑證、私密金鑰、Provisioning Profile 和臨時 Keychain 的位置與存取政策。
  • 使用者目錄:工作區、快取、DerivedData 和 Agent 設定是否屬於專用帳號。
  • 圖形與模擬器服務:iOS Simulator 是否需要該帳號已建立的登入工作階段。
  • 網路權限:原始碼、套件庫、內部 API 和 CI 控制平面的連線是否符合企業網路政策。
  • 退出後狀態:使用者登出後,建置是否應停止,而不是悄悄改由錯誤帳號繼續執行。

Apple 的程式碼簽名說明可用來核對簽名流程的基本要求,但它不會替企業決定 CI Agent 應放在哪個 launchd 域。這正是平台團隊需要把「官方簽名要求」和「內部服務帳號政策」分開記錄的原因。

04

第二步:用恢復證據,而不是控制台綠燈驗收

重啟恢復的合格標準,不是主機能回應 ping,也不是管理控制台顯示節點在線。合格證據至少要包含以下鏈路:

  1. 主機完成重啟,並可透過核准的遠端管理方式連線。
  2. 磁碟已解鎖;若啟用 FileVault,記錄是否需要人工介入。
  3. 專用服務帳號的使用者工作階段已建立。
  4. LaunchAgent 已載入,CI Agent 已向控制平面註冊。
  5. 受控 Keychain 可用,且沒有把憑證複製到不必要的系統路徑。
  6. 執行一條真實的簽名、測試或封裝流水線並成功回報。

Apple 對 FileVault 復原金鑰與磁碟解鎖的說明,可參考Apple 平台安全性文件;對啟用與管理 FileVault 的操作限制,則應核對Apple 支援文件。這些資料能界定磁碟解鎖與登入控制的邊界,但不代表任何遠端供應商都能保證完全無人介入。

驗收時應分別測試:

  • 冷啟動;
  • 計劃重啟;
  • 意外斷電後恢復;
  • 使用者退出後是否按政策停止建置;
  • FileVault 解鎖後能否建立使用者工作階段;
  • Agent 重新註冊後能否接單;
  • 遠端重啟是否能由核准的管理流程執行。

其中一項失敗,就應把節點標記為「部分恢復」而非「無人值守可用」。

05

最小權限要看能讀到甚麼

企業不應只比較 root、管理員和一般帳號的名稱,而應比較每種執行身份實際能接觸的資源:

  • 原始碼及工作區;
  • 套件庫與內部 API;
  • 簽名憑證和私密金鑰;
  • SSH 金鑰及 CI Token;
  • 日誌、快取和其他團隊工作資料;
  • 可執行的系統管理命令。

雙層方案的職責界線應保持簡單:系統層只負責不接觸發佈憑證的健康檢查與恢復輔助;使用者層才承擔受控建置。若必須提權,應留下企業政策記錄、命令範圍、執行身份和回滾方法,而不能只在 plist 中加入寬泛的權限。

若團隊使用共享管理員帳號,應先處理身份不可追蹤、Keychain 混用和離職撤權等問題,再討論 KeepAlive。自動重啟一個權限過大的 Agent,不是高可用,而是把憑證暴露面固定化。

06

第三步:把日誌和退出狀態納入營運指標

每一個 Mac 構建節點都應能回答四個問題:哪個 launchd 域載入了 Agent?由哪個帳號執行?最近一次退出原因是甚麼?重啟多少次後仍未恢復?

建議集中記錄:

  • plist 所屬域與執行帳號;
  • Agent 標準輸出與錯誤日誌位置;
  • 最近一次退出狀態;
  • KeepAlive 觸發次數;
  • Agent 註冊與取消註冊時間;
  • macOS、Xcode、CI Agent 和設定檔版本;
  • 最後一次成功簽名或測試建置的識別碼。

KeepAlive 只能表示 launchd 嘗試維持程序,不代表程序能成功建置。若錯誤帳號、Keychain 不可用或 Simulator 啟動失敗,KeepAlive 可能只是反覆掩蓋持續崩潰。平台團隊應把「程序反覆重啟」與「真實流水線成功」設為兩個獨立告警。

升級 macOS、Xcode、CI Agent 或 plist 前,先把節點移出排程,複製相同流水線到隔離工作區,完成重啟、簽名和回滾測試,再恢復接單。這比在生產節點直接修改 LaunchAgent 後觀察數小時更容易留下可審計證據。

07

上線前的可勾選驗收清單

  • [ ] 已記錄 LaunchDaemon、全域 LaunchAgent 和使用者級 LaunchAgent 的實際執行域。
  • [ ] 已確認 CI Agent 官方文件所支援的 macOS 服務模式。
  • [ ] 已由專用服務帳號執行需要 Keychain 或 Simulator 的建置。
  • [ ] 已證明 LaunchDaemon 不會直接讀取發佈憑證。
  • [ ] 已分開驗證主機可達、磁碟解鎖、使用者登入和 Agent 註冊。
  • [ ] 已完成冷啟動、計劃重啟與意外斷電測試。
  • [ ] 已測試使用者退出後的預期行為,而不是把它視為成功恢復。
  • [ ] 已執行至少一條真實簽名或封裝流水線。
  • [ ] 已保留退出狀態、重啟次數、版本變更和日誌位置。
  • [ ] 已測試遠端重啟、憑證隔離及備用節點切換。
  • [ ] 已為 FileVault 解鎖需要人工介入的情況建立明確標籤與升級流程。
  • [ ] 已定義節點無法恢復時的回退容量,而不是只依賴同一台主機。

若現有節點無法勾選「專用使用者工作階段」、「遠端重啟」和「真實建置成功」三項,應先整改服務邊界;若供應商也無法提供這些恢復證據,則不宜把該節點列為關鍵生產 CI 容量。

08

依指標決定單層或雙層架構

LaunchAgent 單層適合已能可靠建立專用登入工作階段、且主機級監控由其他平台負責的環境。否決條件是無法處理主機未啟動、網路未恢復或 Agent 完全未註冊的狀況。

LaunchDaemon 單層只適合主機健康檢查、遙測和恢復輔助,不適合直接承載需要使用者態資源的 iOS CI。若它必須讀取簽名資產,或團隊用 root 來繞過 Keychain 問題,應視為安全與可維運性否決項。

雙層架構是企業生產節點的預設選擇:LaunchDaemon 觀察主機並提供受限恢復動作,專用服務帳號的 LaunchAgent 在正確工作階段執行 CI Agent。它不會消除 FileVault、登入控制或供應商管理能力的限制,但能把系統恢復和憑證使用拆開,讓故障分類更清楚。

對於需要多個 iOS 團隊共用容量的環境,採購時應把使用者會話隔離、遠端重啟權限、備用節點、日誌交付和實際建置恢復紀錄列為驗收項目。若團隊正在評估企業 Mac 雲端算力訂購方案,不應只比較 CPU 或記憶體,而要要求供應方說明重啟後的 Agent 恢復證據與權限邊界;需要區域部署時,也可按香港遠端 Mac 方案等實際連線條件評估。

FAQ

Mac 構建機重啟後,怎樣讓 CI Agent 自動回來?

先確認主機是否能在無人登入時由 LaunchDaemon 執行健康檢查與恢復動作,再讓專用服務帳號登入後由 LaunchAgent 啟動 CI Agent。驗收不能只看 SSH 或控制台顯示在線,還要確認使用者工作階段、Keychain、Agent 註冊,以及一條真實簽名或測試建置均已成功。

LaunchAgent 和 LaunchDaemon 哪個較適合 iOS CI?

若工作需要 Keychain、程式碼簽名、iOS Simulator 或使用者目錄,應優先採用專用服務帳號的 LaunchAgent。LaunchDaemon 適合主機級監控、啟動前檢查和恢復輔助;它不等於完整的 iOS 建置環境。實際部署前仍須核對所用 CI Agent 的官方 macOS 服務模式。

為甚麼 LaunchDaemon 通常無法正常使用 Keychain?

LaunchDaemon 位於系統上下文,與互動式使用者登入工作階段、使用者 Keychain 和圖形服務不是同一個執行邊界。即使程序以高權限運行,也不代表它能取得建置帳號的簽名資產。應把憑證使用放在受控 LaunchAgent,並以臨時 Keychain、存取控制和企業憑證政策限制暴露面。

FileVault 開啟後,無人值守 Mac 如何恢復 CI 建置?

FileVault 會把磁碟解鎖納入恢復鏈路,因此「主機已重啟」不代表 macOS 已完成可建置狀態。驗收時要分開記錄磁碟解鎖、使用者登入、Agent 上線、Keychain 可用及真實建置成功;若其中一環必須人工輸入,該節點就不應被標示為完全無人值守。

遠端 Mac CI Agent 上線前,應驗證哪些重啟場景?

至少要測試冷啟動、計劃重啟、意外斷電、使用者退出,以及啟用 FileVault 時的解鎖路徑。每次都應保存主機可達、使用者工作階段建立、Agent 註冊、Keychain 存取和實際流水線通過的證據,並另測遠端重啟、憑證隔離與備用節點切換。

若現有方案只是讓一台辦公室 Mac 透過 SSH 保持在線,通常仍有三個缺口:重啟後沒有獨立的使用者工作階段、簽名憑證與日常帳號混用,以及沒有可切換的備用容量。這類方案短期成本可能較低,但一旦 FileVault 解鎖、硬碟故障或 Agent 升級失敗,就容易把人工處理變成生產瓶頸。對需要臨時擴充 CI、隔離測試環境或驗證遠端重啟流程的團隊,租用 NodeMini 的遠端 Mac 可作為較容易驗收的替代路徑;正式導入前仍應按上述清單核對使用者會話、權限、憑證和真實建置恢復結果。