個人 Mac 可以完成歸檔,但遠端節點找不到對應私鑰,通常代表簽名資產尚未完成遷移。

最快解法:新專案可直接採用 fastlane match;已有生產簽名則先導入現有身份並保留舊鏈路雙軌驗證,CI 預設使用臨時鑰匙串與 readonly,同時不要把 match nuke 當成預設步驟。

這篇文章適合以下讀者:

  • 維護 iOS 或 macOS 自動簽名流水線、希望消除個人 Mac 依賴的 DevOps 工程師。
  • 負責證書、私鑰、描述檔與 App Store 發布權限的簽名管理員。
  • 準備租用遠端 Mac 作為長期建置或發布節點的研發負責人。

先確認簽名資產邊界

fastlane match 遠端 Mac 的問題,並不是把 .cer 或描述檔下載到節點就算完成。Apple 的代碼簽名身份需要證書及其對應私鑰;Apple 也在團隊簽名憑證說明中將兩者視為必須一起同步的資產。可先閱讀 Apple 關於團隊簽名憑證同步的官方說明

因此,遷移前應建立資產盤點表:

  • 應用程式的 Bundle Identifier:<APP_BUNDLE_ID>
  • Apple 團隊識別碼:<TEAM_ID>
  • 證書類型:開發、Ad Hoc、App Store,或 macOS 發布用途。
  • 對應私鑰是否仍存在於目前發布用 Mac 的 Keychain。
  • 每個 Target 使用的描述檔,包括 Extension、Widget、Watch App 或其他附屬 Target。
  • 現有生產發布鏈路、負責人、備份位置與回退入口。

Xcode 只在主應用成功歸檔,不能證明所有附屬 Target 都能簽名。若專案包含 macOS App,還應另外核對 Apple 的 macOS 分發簽名流程;iOS 或 iPadOS 應用則要對照 Apple 的註冊裝置分發流程

新專案初始化

全新專案沒有需要保護的現有生產身份時,可以直接初始化 match。初始化前,先決定簽名倉庫由誰持有、加密口令由誰管理,以及 Apple 團隊權限由哪一個受控角色操作。不要把倉庫存取憑據、match 解密口令與發布令牌放在同一個秘密變數中。

設定檔只使用占位符,不要把真實帳號資料提交到程式碼庫:

# Appfile
app_identifier("<APP_BUNDLE_ID>")
team_id("<TEAM_ID>")

Appfile 的團隊與應用程式設定方式,可對照 fastlane Appfile 官方文件

接著依照實際發布用途建立簽名資產:

  • 開發建置:供開發裝置或內部測試使用。
  • Ad Hoc:供註冊裝置測試使用。
  • App Store:供商店發布或相應的分發流程使用。
  • macOS 發布:依 macOS Target、分發方式與專案設定獨立核對。

初始化完成後,第一個驗收目標不是「證書檔案已下載」,而是全新的遠端 Mac 能完成最小簽名建置。這個基線至少應包括:同步簽名資產、Xcode 解析正確的 Target、產生歸檔,以及在指定測試流程中驗證安裝或分發結果。

既有簽名遷移

已有生產簽名時,先使用 match import 將現有證書、私鑰與描述檔納入受控流程,並在隔離的簽名倉庫或分支試跑。fastlane 官方文件確認 match 支援匯入既有簽名資產,相關做法可參考 match 的匯入說明

建議按以下順序執行:

第一步:保留現有發布能力。
先確認目前個人 Mac 或既有 CI 仍能使用同一提交完成歸檔,保存歸檔檔案、簽名檢查結果與發布紀錄。不要在尚未確認遠端節點可用前撤銷證書。

第二步:建立隔離試跑。
使用 <SIGNING_REPO_URL><MATCH_BRANCH><APP_BUNDLE_ID> 等占位符配置,讓測試分支只服務非生產建置。這樣可以檢查解密口令、倉庫權限、私鑰匯入與描述檔匹配,而不影響正式發布。

第三步:比較同一提交。
讓舊鏈路與遠端 Mac 使用同一個 Git commit,分別檢查歸檔是否成功、簽名身份是否一致、安裝或分發結果是否一致。單次建置成功不足以證明遷移完成,因為它可能只覆蓋主應用,沒有覆蓋 Extension 或其他 Target。

第四步:設計回退入口。
回退入口至少應包含舊鏈路、簽名倉庫的可恢復版本、加密口令保管責任與節點失效時的替代建置位置。只有完成影響評估、備份與回復演練後,才討論是否重置簽名資產。

match nuke、證書撤銷或刪除鑰匙串都可能使仍在使用的發布流程失效,因此不應用來「清理環境」或掩蓋配置問題。若確實需要破壞性操作,必須先列出會受影響的 App、Target、發布渠道與回退步驟。

多應用與多團隊隔離

同一個簽名儲存空間可以在受控範圍內重用相應證書,但每個應用程式的描述檔、Bundle Identifier 與附屬 Target 仍須分別維護。不同 Apple 團隊不應只靠命名規則區分,應使用獨立分支、獨立儲存空間或至少獨立的存取權限。

例如:

<TEAM_ID_A>/<APP_BUNDLE_ID_A>
<TEAM_ID_A>/<APP_BUNDLE_ID_A_EXTENSION>
<TEAM_ID_B>/<APP_BUNDLE_ID_B>

多應用配置需要特別檢查:

  • 主應用與 Extension 是否使用正確的 <TEAM_ID>
  • Widget、Watch App 的 Bundle Identifier 是否被納入同步範圍。
  • 不同團隊的倉庫密鑰、解密口令與發布令牌是否分離。
  • CI 工作區是否會重用上一個 Job 留下的描述檔或 Keychain 狀態。
  • App Store Connect 或其他發布服務的認證是否與簽名倉庫權限分開。

多應用和多團隊如何隔離 match 證書倉庫?

若應用屬於不同 Apple 團隊,優先選擇獨立儲存空間與獨立權限;若同一團隊管理多個應用,則可按應用與附屬 Target 分別維護描述檔,並用分支或路徑規範降低誤取風險。Appfile 的團隊設定不可只留在人工記憶中,應放進可審查的 CI 配置,但真正的口令與令牌必須由秘密管理機制注入。

無人值守 CI 配置

遠端 Mac CI 的核心不是讓登入畫面保持開啟,而是讓非互動任務可以在沒有圖形確認、沒有人工輸入與沒有個人 Keychain 依賴的情況下完成簽名。

setup_ci 的用途是建立 CI 適用的臨時鑰匙串,並配合簽名同步流程降低對登入使用者環境的依賴;參數與行為應以 setup_ci 官方文件 為準。

遠端 Mac CI 為什麼要使用臨時鑰匙串?

macOS Keychain 若沿用共享節點的登入鑰匙串,容易出現權限提示、使用者切換後無法存取、前一個 Job 殘留身份,或普通任務讀取到生產私鑰等問題。臨時鑰匙串讓每次 CI 執行有明確的建立、使用與清理邊界;它不是取代簽名倉庫,而是限制私鑰在建置期間的暴露範圍。

在 CI 中,簽名同步應先於 Xcode 建置與歸檔:

setup_ci

match(
  type: "<MATCH_TYPE>",
  readonly: true,
  app_identifier: [
    "<APP_BUNDLE_ID>",
    "<APP_EXTENSION_BUNDLE_ID>"
  ]
)

fastlane match readonly 應該在哪些任務中開啟?

所有一般建置、測試歸檔、Pull Request 驗證與正式發布 Job,都應讓 match 以 readonly 模式讀取已批准的簽名資產。寫入或續期只交給受控的簽名管理流程,例如由指定管理員審批後執行,不能讓每一個建置 Job 都具備建立或修改證書的權限。

這樣可以把兩種責任分開:

  • 簽名管理流程:建立、匯入、輪換與批准證書及描述檔。
  • CI 建置流程:讀取已批准資產,執行測試、歸檔與發布。

同時,應分開管理以下秘密:

  • <SIGNING_REPO_TOKEN>:簽名倉庫存取憑據。
  • <MATCH_PASSWORD>:簽名資產解密口令。
  • <APPLE_AUTH>:Apple 服務認證。
  • <DISTRIBUTION_TOKEN>:發布渠道令牌。

驗收時刻意觀察任務是否停在鑰匙串確認、雙重認證或圖形彈窗;並保存可收集的 Xcode 與 fastlane 日誌,但不要在日誌中輸出私鑰、口令或完整令牌。

提醒: 長期遠端節點的重啟、登入使用者變更與並發 Job,都可能使臨時鑰匙串、工作區或描述檔狀態改變;「今天能跑」不等於「重啟後仍能無人值守」。

共享節點與生產發布

同一台遠端 Mac 可承擔不同任務,但至少要在帳戶、工作區、鑰匙串與 CI 秘密上劃分邊界。開發建置不應讀取生產簽名身份;測試歸檔與正式發布也不應共用沒有清理策略的工作目錄。

建議透過兩個隔離任務進行驗收:

  • 任務 A:只載入非生產證書,完成測試歸檔。
  • 任務 B:在不同工作區與秘密範圍中載入生產發布所需資產。
  • 連續或並行執行後,檢查 A 是否能看到 B 的身份、描述檔、令牌或殘留檔案。
  • 重啟節點後重跑,確認臨時鑰匙串會重新建立,且不依賴人工點擊。

生產發布與證書輪換還要處理證書到期、描述檔重新生成、簽名倉庫不可用、口令輪換及遠端節點更換。Apple 提供了描述檔編輯、下載與刪除的 官方管理說明,可用於核對描述檔變更後的責任範圍。

遷移決策表

場景 建議策略 寫入權限 上線前必要驗收
全新專案 直接初始化 fastlane match 只限受控初始化流程 遠端 Mac 完成同步、歸檔與安裝或分發驗證
已有生產簽名 match import,再雙軌運行 CI 不得寫入 同一提交比較舊鏈路與遠端鏈路
多應用同一團隊 分別管理 Bundle Identifier 與附屬 Target 依應用與流程分層 主應用、Extension、Widget 等均完成簽名
多個 Apple 團隊 獨立分支或儲存空間 團隊權限完全分離 確認不會讀取其他團隊資產
一般 CI 建置 readonly 加臨時鑰匙串 僅讀取批准資產 無互動提示、無殘留、重啟後可重跑
正式發布 受控流程執行輪換與發布 由發布責任人審批 歸檔、簽名、分發與回退均可驗證

遠端 Mac 驗收清單

遷移完成前,可逐項勾選:

  • [ ] <APP_BUNDLE_ID><TEAM_ID> 與每個 Target 已核對。
  • [ ] 現有證書、對應私鑰、描述檔與生產責任已盤點。
  • [ ] 既有發布鏈路仍可用,且已保存回退入口。
  • [ ] 匯入流程在隔離分支中完成,沒有先撤銷有效生產身份。
  • [ ] match 同步先於 Xcode 建置與歸檔。
  • [ ] CI 使用臨時鑰匙串,沒有等待圖形確認或人工輸入。
  • [ ] 一般 Job 使用 readonly,寫入與續期被移出普通建置流程。
  • [ ] 簽名倉庫憑據、解密口令、Apple 認證與發布令牌互相分離。
  • [ ] 主應用、Extension、Widget、Watch App 或其他 Target 都完成驗證。
  • [ ] 兩個隔離任務沒有互相繼承私鑰、描述檔、工作區或令牌。
  • [ ] 節點重啟、使用者會話變更與並發執行後仍能重建。
  • [ ] 同一提交已比較舊鏈路與遠端 Mac 的歸檔、簽名及安裝或分發結果。

三種遷移結論

驗收結果 結論 後續行動
遠端鏈路與舊鏈路結果一致,隔離與回退均通過 直接遷移 將正式 CI 切換至遠端 Mac,保留受控回退流程
建置成功,但重啟、附屬 Target 或權限隔離未通過 繼續雙軌 暫不移除舊鏈路,先修復環境與秘密邊界
私鑰不完整、簽名結果不一致或無法回退 暫緩上線 恢復資產盤點與責任確認,不執行 match nuke 或撤銷操作

若目前方案是依賴某位工程師的個人 Mac,常見缺點是私鑰留在個人 Keychain、節點無法在團隊成員離線時重建,以及重啟或權限變更後缺少可追溯的恢復流程;若改用一般 Linux 雲端伺服器,則又無法直接提供 Xcode 與 macOS 專屬簽名環境。對需要短期驗證、隔離測試或長期 CI 節點的團隊而言,先租用一台具備完整管理權限、可重啟驗收的遠端 Mac,通常比立即購買硬體或把簽名責任綁在個人裝置上更容易控制風險。可先查看 RUVCLOUD 的遠端 Mac 方案租用方案資訊,再以非生產證書跑通 match、Xcode 歸檔與恢復流程,最後才決定是否遷移正式發布任務。