2026-04-22 無頭租用雲端 Mac 上 iOS 鑰匙圈與描述檔衛生:CI 簽章實務
發行經理與iOS CI 擁有者透過 SSH 租用 Apple Silicon Mac 時,常遇到同一道牆:本機 Archive 一切正常,遠端建置機卻出現 errSecInternalComponent、「找不到簽章憑證」,或描述檔 UUID 已過期卻仍被快取命中。這份2026-04-22指南說明如何在香港 / 日本 / 韓國 / 新加坡 / 美國區域,讓登入鑰匙圈、描述檔與codesign 身分在無人值守作業中保持一致。你將獲得兩張對照表、加固的 SSH 工作流、NVMe 數字預算,以及指向配套文件的連結:自動與手動簽章、匯出與 App Store Connect API、並行 xcodebuild。
在真實團隊裡,問題往往不是「憑證壞了」,而是狀態漂移:有人用圖形介面匯入憑證、有人在另一個使用者下跑過 fastlane、還有人把描述檔重新命名成「好記」的檔名。無頭環境不會跳出鑰匙圈授權對話框,因此任何「靠人點一下」的步驟都會在凌晨建置裡變成隨機失敗。把鑰匙圈當資料庫、把描述檔當不可變工件、把身分列舉寫進 CI 矩陣,才能把失敗從玄學變成可定位的工程議題。下文依層拆解,並提供你可直接抄進 Runbook 的檢查命令與告警閾值。
無頭建置機上的失敗面
與帶 UI 的筆電不同,租用的雲端 Mac 常在非互動式 shell 中執行 xcodebuild。最常見的斷裂可清楚映射到三層:鑰匙圈狀態、描述檔新鮮度、身分不相符。當你看到「偶發成功」,優先懷疑層間耦合:例如 profile 已換但鑰匙圈仍指向舊私鑰,或並行作業把兩個團隊的 DerivedData 混寫,導致 Xcode 解析到「看起來正確」的 profile。
為了把排除時間從小時級壓到分鐘級,建議在 CI 入口統一列印三類基線:security list-keychains、security find-identity -v -p codesigning,以及 ~/Library/MobileDevice/Provisioning Profiles 下依 mtime 排序的最新三個 UUID。把這三段日誌視為每次 Archive 的「簽章指紋」,能在回滾時快速比對差異。對跨時區團隊,最好把日誌時區固定為 UTC 或統一為東八區,避免「檔案時間看起來過期但其實沒過期」的誤判。
| 症狀 | 常見根因 | 首選排查 |
|---|---|---|
errSecInternalComponent(codesign 階段) |
登入鑰匙圈鎖定或私鑰 ACL 不允許自動化存取 | security list-keychains + 針對 CI 使用者稽核 unlock |
| 描述檔「不包含簽章憑證」 | 磁碟 UUID 與 target 內嵌的 profile 不一致 | 對照 ~/Library/MobileDevice/Provisioning Profiles 與 Xcode 報告 |
| 分支 A 成功、分支 B 失敗 | 多個相同 CN 的身分;選錯簽章身分 | security find-identity -v -p codesigning 並在矩陣中明確設定 CODE_SIGN_IDENTITY |
xcodebuild archive 預留 120–320 GB 高速 NVMe——詳見 dSYM 留存與當機符號化管線。
簽章模式:決策矩陣
在動鑰匙圈之前,先決定建置機依賴自動簽章(由 Xcode 管理描述檔)或手動簽章(描述檔入庫)。答案取決於憑證輪換頻率、是否多應用共用主機,以及你是否能接受 Apple ID 工作階段類故障的不確定性。對金融與合規團隊,手動簽章 + 明確 UUID 通常更可控;對迭代頻繁的小型產品組,自動簽章能顯著降低維運摩擦。
| 模式 | 最適合 | 維運成本 | 純 SSH Mac 風險 |
|---|---|---|---|
| 自動 + Xcode 託管 | 快速迭代、bundle id 少 | 描述檔 babysitting 較低 | 中等——依賴 Apple ID 工作階段健康 |
| 手動 + 已提交描述檔 | 企業管線、可重現封存 | 輪換紀律要求更高 | 較低——UUID 與身分明確 |
| 每作業暫存鑰匙圈 | 多租戶主機 | 自動化工程量最高 | 做對時跨團隊外洩最低 |
SSH 工作階段的鑰匙圈紀律
雲端 Mac 上的 CI 使用者應把 login.keychain-db 視為線上資料庫:只允許一個自動化主體執行匯入,解鎖步驟必須明確。建議模式為:
- 起飛前檢查——確認 CI 使用者的鑰匙圈清單與預設鑰匙圈。
- 解鎖——使用組織核准的非互動式解鎖(密碼來自密鑰管理系統,而非儲存庫明文)。
- 分割清單——在需要時讓
codesign與productbuild存取私鑰,但不擴大無關工具的 ACL。 - 作業後清理——若匯入暫時憑證,刪除並做一次輕量「孤兒身分」清理。
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/login.keychain-db
描述檔生命週期
磁碟上的描述檔必須與 Xcode 在封存階段解析結果一致。請維持以下不變量:
- 檔名使用 profile UUID:
<UUID>.mobileprovision——若市場同事重新命名,自動化會在沉默中失敗。 - 對 App Store 發佈描述檔設定 T-30 / T-14 / T-7 到期告警;可用 App Store Connect API 支撐輪詢任務。
- 刷新後刪除被取代的 UUID,避免多個匹配同一 bundle id 時 Xcode 選中「幾乎正確」的舊檔。
將本節與 匯出選項與 ASC API 搭配閱讀,確保上傳步驟不會在「封存成功但發佈描述檔已過期且仍快取於磁碟」的情況下繼續執行。
身分、embedded.mobileprovision 與匯出
使用 security find-identity -v -p codesigning 列舉可用身分,然後在 CI 矩陣中明確固定 CODE_SIGN_IDENTITY。匯出 IPA 時,確認封存內嵌描述與發佈通道一致(TestFlight 與 Ad Hoc 不同)。若管線包含當機符號化,請將簽章衛生與 dSYM 套件連結,確保 UUID 端到端一致。
密鑰配置與 NVMe 預算
新加坡與美國東部的多專案團隊常安排重疊封存。請分離:
- 簽章根目錄——每團隊在受控的
/var/lib/ci風格前綴下使用獨立目錄。 - DerivedData——依隔離文章為每個作業設定暫存根,避免模組快取混用。
- 產物保留——保留最近三棵
.xcarchive以便回滾,刪除更舊切片回收 NVMe。
當你橫向擴展並行任務時,請遵循 並行 xcodebuild 指南,避免 CPU 壓力放大簽章重試,從而掩蓋鑰匙圈層面的真實錯誤。另一個常見誤區是「NVMe 很大就可以不清理」:Xcode 的模組快取與索引體積會在數週內指數級成長,最終拖慢 codesign 與連結階段;把清理寫進每週維護視窗,比臨時救火更省成本。
若你的團隊同時維護多個簽章憑證(開發、內測、上架),建議把憑證到期日與描述檔到期日寫進同一張看板,並為「憑證與 profile 同時到期」的極端情境預留演練。演練內容不需要複雜:備份舊 UUID、匯入新 UUID、跑一次最小目標的 xcodebuild -showBuildSettings 校驗簽章設定,即可顯著降低發行日風險。
MacXCode 上的相關指南
將本文與 自動與手動簽章搭配閱讀以決定策略;帳戶與基礎問題見 說明中心;若你要按區域增加專用簽章主機,請查看 定價。
常見問題:鑰匙圈與描述檔
| 問題 | 實務答案 |
|---|---|
| 是否應在多個 CI 使用者間共享一個 Apple ID? | 盡量避免——依應用家族拆分服務帳號,並每季稽核登入紀錄。 |
| 是否需要 VNC? | 僅在首次信任提示等情境;穩態應以 SSH + 日誌為主。詳見 VNC 指南。 |
| 簽章故障最快的回滾是什麼? | 從版本控制恢復昨日的描述檔集合,並從離線備份重新匯入身分——然後只重建一次,而不是盲目重試五次。 |
為何 Mac mini M4 裸金屬仍贏得簽章吞吐
Apple Silicon Mac mini M4 節點提供可預測的 codesign 延遲,因為加密運算與 I/O 都落在本地 NVMe 上,而不會被虛擬化管理程式搶占中斷預算——當你把封存 → 匯出 → 上傳串在 SLA 下執行時尤其關鍵。MacXCode 在香港 / 日本 / 韓國 / 新加坡 / 美國的配置讓你能把簽章主機靠近測試團隊,同時維持一致的 SSH 工作流;對每週發版的團隊,還提供 1–2 TB 儲存選項與裸金屬隔離,避免「鄰居吵鬧」造成的身分爭用。若路線圖上的應用比金鑰成長更快,請從 定價 增加建置機,而不是讓單一鑰匙圈過載。