Linux Runner 的建置階段已通過,但 iOS Job 顯示找不到 Xcode。
最快解法:為 GitLab CI 建立專用的 macOS Runner 與使用者登入會話,再以 Shell executor、固定 Xcode 工具鏈、隔離憑據,通過 Archive、TestFlight 上傳及重啟恢復六項驗收。
SECTION 01誰適合採用這套 GitLab CI iOS 打包流程
如果你使用 GitLab,但目前的 Runner 只能處理 Android 或後端專案,這篇內容可以直接對應你的問題。你也適合閱讀本文,如果你正把手動 Xcode Archive 改成自動流程,或正在評估一台常駐遠端 Mac 能否承擔簽名與 TestFlight 發布。
GitLab CI 可以完成 iOS 自動打包,但「註冊 Runner」不等於「具備可發布的 CI 環境」。iOS Job 必須落到真實 macOS 主機,因為 Linux Runner 無法取代 Xcode、Apple SDK、Keychain 與簽名工具鏈。GitLab 官方建議 macOS 上的 Apple 平台建置使用 Shell executor;它能使用主機工具,但隔離能力有限,這會直接影響安全設計。GitLab Shell executor 官方說明 已清楚列出這個邊界。
SECTION 02六項指標如何判定遠端 Runner 能否進入生產環境
1. 主機在線、登入會話與接單狀態必須同時成立
macOS Runner 以使用者層級的 LaunchAgent 執行。換句話說,服務啟動、指定使用者登入、Runner 顯示 online,以及 Runner 實際接到 Job,是四個不同狀態,不能只看到其中一個就判定主機正常。GitLab macOS Runner 服務模式文件 說明了這種使用者級服務模型。
遠端 Mac 執行 GitLab Runner 一定要保持圖形會話嗎?
若 Job 只執行命令列建置,未必需要你持續操作圖形介面;但簽名涉及使用者 Keychain,模擬器、GUI 工具或需要登入桌面的工作也可能依賴有效的圖形登入會話。你不應把「自動登入」當成唯一安全方案,應改用專用 macOS 使用者、限制實體存取,並實際測試重啟後的 Keychain 行為。
驗收時依序檢查:
- 主機重啟後,LaunchAgent 是否由正確使用者載入。
- 該使用者是否已登入,且 Keychain 狀態符合建置需求。
- GitLab Runner 是否回到 online。
- Runner 是否仍帶有正確標籤,並能接收測試 Job。
- Job 是否使用預期的 Shell、工作目錄與環境變數。
2. 工具鏈基線要能證明「實際呼叫的是哪個 Xcode」
只檢查 Applications 目錄中裝了哪些 Xcode,不足以保證 CI 使用正確版本。你需要固定活動開發者目錄、SDK、命令列元件、專案依賴及 Shell 環境,並在 Job 裡輸出脫敏後的實際路徑與版本資訊。
可在隔離測試專案中加入類似檢查,路徑請按你的主機調整:
set -euo pipefail
xcode-select -p
xcodebuild -version
xcrun --sdk iphoneos --show-sdk-path
ruby --version
swift --version
不要在日誌中輸出 Token、密碼、憑證內容或完整的私密路徑。驗證結果也要分成三層:依賴解析成功,只代表套件能取得;編譯成功,只代表產生了可編譯的產品;Archive 成功,才開始接近可簽名發布的狀態。Apple 的 Xcode Archive 與發布流程文件 將 Archive、匯出及發布視為不同階段。
同一個提交應連續執行建置,並比較:
xcodebuild實際選用的開發者目錄。- Swift Package Manager、CocoaPods 或其他依賴是否由鎖定檔解析。
xcresult是否成功產生。.xcarchive是否存在且包含預期產品。- 簽名匯出是否成功,而非只完成 Debug Build。
3. 任務路由與主機隔離必須先於發布權限
建議建立專案級或專用 Runner,使用明確標籤,例如:
ios_archive:
tags:
- macos-release
script:
- ./ci/archive.sh
標籤只是路由條件,不是安全邊界。GitLab 的 Runner 標籤與受保護任務文件 說明,受保護分支及受保護 Runner 可協助限制哪些 Job 能使用發布環境。
Shell executor 會直接在宿主機執行命令,工作目錄、暫存檔、使用者權限及環境設定都可能互相影響。因此,發布 Runner 不應接收不可信儲存庫、任意合併請求或未審查的腳本。建置、測試與發布是否共用帳號,要根據風險判斷:
- 若測試程式碼可能執行任意 Shell,測試與發布不應共用同一個 macOS 使用者。
- 若工作目錄會留下簽名檔、匯出包或快取,發布任務應使用獨立目錄。
- 若團隊只有一台主機,至少把發布 Job 限定在受保護分支,並清理工作區。
- 若需要同時服務外部貢獻者與正式發布,應分離 Runner,而不是只增加標籤。
4. GitLab 與 macOS 兩層權限要分開驗證
iOS 發布憑據不是一個單一檔案。你至少要分清以下項目:
| 項目 | 用途 | CI 中的處理原則 |
|---|---|---|
| CI/CD 變數 | 傳遞非公開設定或秘密值 | 使用受保護變數並避免直接列印 |
| App Store Connect API Key | 驗證上傳或管理介面 | 只給必要權限,與簽名材料分開 |
| Distribution 憑證 | 對 App 進行發布簽名 | 以受保護檔案或秘密注入 |
| 私鑰 | 配合憑證完成簽名 | 不進普通快取,不提交儲存庫 |
| Keychain | 儲存及解鎖簽名身份 | 由專用使用者控制存取 |
| Provisioning Profile | 將 App、Bundle ID 與簽名能力對應 | 使用檔案型變數或受控暫存路徑 |
App Store Connect API Key 只解決服務驗證,不等於已經具備完整的程式碼簽名材料;Apple 的 API Key 建立文件 可用來核對其角色與權限。GitLab CI/CD 變數文件 則說明受保護變數、遮罩及檔案型變數的用途,但遮罩不是絕對保護:若腳本自行變形、分段輸出或將秘密寫入產物,仍可能外洩。
示例只保留明顯佔位符:
security import "${CERTIFICATE_FILE}" \
-k "${KEYCHAIN_PATH}" \
-P "${CERTIFICATE_PASSWORD}" \
-T /usr/bin/codesign
security unlock-keychain \
-p "${KEYCHAIN_PASSWORD}" \
"${KEYCHAIN_PATH}"
mkdir -p "${HOME}/Library/MobileDevice/Provisioning Profiles"
cp "${PROFILE_FILE}" \
"${HOME}/Library/MobileDevice/Provisioning Profiles/PROFILE_PLACEHOLDER.mobileprovision"
請將 CERTIFICATE_PASSWORD、KEYCHAIN_PASSWORD、PROFILE_FILE 等值放在適當的受保護變數中;不要把真實密碼、Token、Team ID、Key ID、憑證名稱或憑證內容寫進 YAML、Shell 腳本與錯誤訊息。
GitLab CI 如何匯入 iOS 簽名憑證與 Provisioning Profile?
先由受控環境產生檔案型變數,再在 Job 開始時匯入臨時 Keychain 與 Profile,完成簽名匯出後清理暫存檔。你必須分別測試憑證可讀、私鑰可用、Profile 對應 Bundle ID,以及 App Store Connect 驗證成功;只驗證 API Key 能登入,不能證明簽名鏈完整。
5. 快取、Artifacts 與發布產物不要混成一層
依賴快取適合加速重複解析,Artifacts 則適合在階段之間傳遞可追蹤產物。GitLab 的 Cache 與 Artifacts 官方文件 對兩者用途有明確區分;你不應把簽名私鑰、Provisioning Profile 或唯一的正式 Archive 放進普通快取。
快取鍵應以鎖定檔內容參與計算,例如以 Package.resolved、Podfile.lock 或其他實際依賴鎖定檔作為失效依據。當鎖定檔改變,快取應切換;否則舊依賴可能讓「同一提交」在不同時間得到不同結果。
建議分開保存:
- 依賴快取:只放可重新取得的套件資料。
xcarchive:保留提交、建置設定與簽名結果的對應關係。- 導出包:記錄導出方法及發布用途。
xcresult:保存測試與建置診斷資料。- 上傳紀錄:保存 App Store Connect 回應與構建識別資訊。
Artifacts 的保留期限與容量不要自行寫成固定數字;請依目前 GitLab 設定及專案政策核對。若是正式發布歸檔,應另存至團隊核准的儲存位置,而不是只依賴 CI 介面中的暫存保留。
SECTION 03一次真實發布驗收應該怎樣執行
不要用普通 Debug Build 代替生產驗收。你可以在受保護分支建立一個脫敏測試專案,按以下步驟執行:
- 確認主機狀態:重啟遠端 Mac,確認指定使用者登入、LaunchAgent 載入、Runner online,並能以目標標籤接單。
- 確認工具鏈:在 Job 中執行
xcode-select -p、xcodebuild -version與 SDK 路徑檢查,確定沒有被全域設定或其他 Shell 設定覆蓋。 - 清理執行環境:建立獨立工作目錄,移除上一次 Job 留下的暫存簽名檔、舊 Profile 與未完成 Archive。
- 解析與編譯:以鎖定檔取得依賴,執行測試及 Release 編譯,將
xcresult作為可追蹤產物保存。 - 執行 Archive:使用與正式發布一致的 Scheme、目的地及簽名設定,確認產生
.xcarchive,不能只看編譯命令回傳成功。 - 匯出並上傳:以受控憑據完成簽名匯出,再透過 Xcode、Transporter 或相關介面上傳;Apple 的上傳構建文件可用來核對上傳邊界。
- 確認後台處理:檢查構建是否在 App Store Connect 出現、處理狀態是否完成,以及 TestFlight 是否能看見該構建。
- 再次重啟驗證:重啟主機後重跑下一次受保護 Job,重新檢查 Runner、Xcode 選擇、Keychain 存取及發布結果。
GitLab Runner 在 macOS 重啟後離線怎樣處理?
先不要重複註冊 Runner 或重建 Token。先確認原本的登入使用者、LaunchAgent 狀態及 Runner 設定檔,再查看服務日誌與 GitLab 端的 online 狀態。若 Runner 能上線但 Job 仍失敗,下一步應查 Keychain、Xcode 路徑及工作目錄權限,而不是直接修改 Pipeline。
GitLab Runner 在 macOS 上是否必須讓使用者保持登入?
對只需要命令列編譯的 Job,圖形操作本身不是必要條件;但使用者級 LaunchAgent、Keychain 解鎖及某些模擬器或 GUI 工作會依賴登入狀態。生產環境應把「是否需要圖形會話」寫成你的驗收項目,並透過重啟測試證明,而不是假定 SSH 登入就能取代 macOS 使用者會話。
SECTION 04用條件分支決定現有 Mac、遠端 Mac或拆分 Runner
- 若主機能在重啟後自動恢復指定使用者會話、Runner、Keychain 與 Xcode 工具鏈,且發布 Job 只接受受保護分支,可先用這台 Mac 承擔常駐發布。
- 若現有 Mac 只適合互動開發、工作目錄與簽名材料和其他專案共用,不要直接把它升格為發布 Runner;先拆出專用使用者與工作區,否則回退到獨立遠端 Mac。
- 若沒有可長期在線的 Mac,但需要偶爾完成 Archive 或 TestFlight 上傳,先租用遠端 Mac 跑通一個完整 GitLab Pipeline,再按實際建置頻率選擇週、月或更長週期。
- 若測試 Job 來源包含未審查的合併請求或外部腳本,不要與簽名發布共用 Shell Runner;應分離測試與發布主機,或讓不可信 Job 使用沒有發布憑據的環境。
- 若團隊需要保存唯一 Archive、導出包與測試結果,先設計 Artifacts 與外部備份政策,再決定主機是否適合長期承擔發布責任。
- 若只能通過人工解鎖 Keychain、人工點擊上傳或人工修復 Runner,這套流程尚未達到可恢復的生產標準,應先修復會話與憑據設計。
這些條件也能幫你區分「暫時可用」與「適合常駐」:前者可以完成一次發布,後者還必須在主機重啟、下一次提交及失敗後清理等情況下維持可預期行為。若你正在規劃iOS 打包伺服器的配置與驗收,建議把上述六項指標直接加入團隊的交付文件。
SECTION 05現有方案與遠端 Mac 的取捨
把發布任務混入個人 Mac,短期看似省事,但常見問題是工作目錄互相污染、Xcode 更新後工具鏈漂移、登入狀態或 Keychain 依賴無人值守,以及開發者關機後 CI 立即中斷。只使用 Linux Runner 則連 Xcode Build 與 Apple SDK 都無法執行;完全依靠雲端託管建置,又可能受制於環境可調整程度、憑據策略及除錯方式。
若你已經有一台能獨立隔離、長期在線並可由團隊管理的 Mac,自建 Runner 可能更適合長期高頻發布。若你沒有這種主機,MACNOX 的遠端 Mac 可先讓你在真實 macOS 環境中完成 Runner、簽名、Archive、TestFlight 與重啟恢復驗收,再依照實際使用量選擇週、月或更長租期;你可以先查看遠端 Mac 方案與週期,不要在尚未通過真實發布測試前承諾長期部署。
當你需要的是臨時算力、一次版本發布或小團隊的專用測試環境,這種方式通常比把正式憑據放進個人開發機更容易管理。開始前可從MACNOX 的遠端 Mac 服務入口確認可用方案,並把本文的六項驗收結果逐項記錄下來:通過即可進入常態化流程,需修復就先補齊會話、工具鏈或權限;若主機無法安全隔離不可信 Job,則不適合與發布任務共用。