平台工程已把 Archive 跑綠,卻在 公證(Notarization) 或 stapler 階段被鑰匙圈彈窗、輪詢逾時、同一台遠端 Mac 上多 Job 爭用暫存目錄拖進夜班。本文寫給熟悉 VPS 維運、要把 macOS 當成獨佔建置平面的團隊:先用七條清單拆穿無人值守公證最常見的隱性假設,再用「分發管道 × 技術動作」對照表收斂是否必須 staple,接著給出六步可交接 Runbook(API Key、notarytool submit/store、日誌留存、重試與並發邊界),並鏈到站內 Fastlane 與 CI、可重現建置與鑰匙圈 的分工閱讀。
Apple 的公證鏈路在文件層面很清晰,但落到長期在線的 CI 使用者與共用 NVMe上時,失敗往往表現為偶發、難重現、日誌分散。以下七條用於把風險從「感覺能腳本化」推進到「能寫進變更單」。
把公證當成 Archive 的附屬品:未單獨為 notarytool 分配結束碼處理與成品留存時,排障會退化成「誰 last green 誰負責」;應與發佈流水線的稽核欄位對齊。
混用個人 Apple ID 與 CI API Key:工作階段型憑證在無人值守環境不可控;2026 年應以 App Store Connect API Key 為預設,並與 Fastlane 無頭發佈 的金鑰合約一致。
忽略 stapler 的分發語義:把 staple 寫進所有流水線會增加時間與失敗面;應對「直接下載分發/企業內部入口/僅 TestFlight」分支化,見下一節對照表。
多 Job 共用預設暫存目錄:/var/folders 與自訂 zip 工作區若衝突,會出現上傳損毀或雜湊不一致;需要像 DerivedData 一樣依 pipeline 分桶。
不做 notarytool 輪詢退避:Apple 側佇列波動時,線性重試會把同一提交打爆;應使用指數退避與最大牆鐘預算,並把 submission id 記入工單。
鑰匙圈與 TCC 仍按「桌面使用者」設定:無人值守建置應使用專用登入工作階段或明確的 CI 鑰匙圈分區,並與 可重現建置 的 Keychain 合約一起評審。
沒有「公證失敗樣本庫」:團隊只在綠線時看日誌,遇到 Invalid 或 Hardened Runtime 問題時缺少對照截圖與版本三元組;應在知識庫固定採集範本。
這些假設的共同根因,是把遠端 Mac 當成「能跑 Xcode 的 Linux」,而不是帶公證狀態機與鑰匙圈邊界的獨佔節點。與 企業建置資源池 篇聯動:當多應用共用機群時,公證並發與簽章材料隔離必須寫進平台 SLA,而不是交給單一業務儲存庫「自己看著辦」。
與純 xcodebuild 相比,公證階段更強調可觀測性與冪等:同一二進位重複提交、同一 request UUID 的冪等語義,以及失敗後在工單系統裡的「可回放命令」。把三件事寫進 Runbook,才能把遠端 Mac 從「能編」升級到「能簽、能證、能稽核」。
若同時跑夜間 Archive 與白天 PR,建議把公證 Job 與編譯 Job 的資源組隔離:公證往往受網路與 Apple 側佇列影響,和 CPU 密集編譯混排會放大尾延遲;此舉與 GitLab resource_group 或 Actions 並發上限的治理思路一致,只是資源名應改成「notarization slot」這類可讀模型。
最後,遠端供應商若提供固定出口 IP 或區域選擇,應把公證鏈路的 RTT 與 TLS 攔截策略納入驗收:企業代理對 Apple 端點的中間人解密是「綠本機、紅公證」的經典根因之一。
維運成熟度也包含將 notarytool CLI 與 Xcode 一併版本化:Apple 會隨 Xcode 與命令列工具更新行為;在內部變更日誌釘選行為,可避免映像刷新後團隊集體踩預設參數變更。API Key 應排程輪替並註冊 Issuer 與團隊對應關係,讓事件應變不必猜測主機上啟用哪一支 p8。
最後,把公證遙測接到與編譯耗時相同的儀表板:追蹤提交耗時、輪詢次數、staple 耗時與磁碟可用空間百分比,當主管詢問「為何 CI 變慢」時才有資料可答,而不是憑印象調機。
沒有一張表能在所有組織裡直接拍板,但評審時可以用「使用者如何拿到封包」為主軸,把技術動作收斂成可測試的驗收項。
| 維度 | 直接下載 .dmg/.zip | 企業內部分發入口 | TestFlight / App Store |
|---|---|---|---|
| 公證提交 | 通常需要:Gatekeeper 預設路徑要求公證憑證可驗證 | 需要:與 MDM/入口驗簽策略對齊 | 由 Apple 託管分發鏈主導;本機 stapler 需求與直接下載不同 |
| stapler staple | 強烈建議:離線安裝時憑證隨封包走 | 視入口是否二次重打包;重打包後需重新 staple 或改分發策略 | 一般不等同於「本機構建完必 staple」;以發佈通道為準 |
| 失敗影響面 | 使用者側彈窗與安裝攔截;支援成本高 | 合規與稽核欄位;可能觸發資安事件複盤 | 多體現為上傳/處理佇列;與 CI 公證並不同構 |
| 遠端 Mac 側效益 | 獨佔磁碟與穩定出口;便於保留 notary zip 與日誌 | 與內網成品倉相鄰部署可降低傳輸時間 | 與 Fastlane 流水線銜接;公證與上傳角色拆分 |
| 典型誤區 | 以為「公證通過」等於「使用者雙擊一定綠」;忽略 staple 時機 | 入口防毒改寫封包導致憑證失效 | 把桌面端 notarytool 經驗硬套到 App Store Connect 處理延遲上 |
「像買 VPS 一樣租 Mac」在公證語境裡,買的是可重複的提交平面:固定使用者、可預期的磁碟水位,以及能把 submission id 與建置指紋綁進工單的狀態機。
對照 notarytool 與已棄用路徑時,評審應明確維護窗口:團隊是否仍殘留依賴圖形工作階段的舊腳本;是否所有呼叫端都已切到 JSON 輸出與結構化日誌。把「切換完成」定義為 CI 範本與內部腳手架全部更新,而不是某個儲存庫局部 green。
若你在多地區有機群,公證上傳的地理就近不如編譯那麼敏感,但仍建議把「出口路徑與代理策略」寫入每台節點的 Runbook,避免「東京編、矽谷證」這類路徑在合規上踩雷。
資安審查附件可附一頁決策紀錄:選定管道、是否 staple、誰負責 API Key 輪替,以及乾淨虛擬機上驗證憑證的指令;比單靠截圖更耐時間考驗。
下列順序強調「先身分與金鑰,再成品與提交,最後才放開並發」:與 可重現建置 的指紋採集腳本對齊,避免「能 submit 卻不能複盤」。
建立專用 CI 使用者與登入工作階段基線:禁止與個人 Apple ID 混用;確認螢幕鎖定策略不會中斷長時間輪詢。
安裝 API Key 與 notarytool 憑證:使用 p8、Issuer ID、Key ID 三件套;金鑰檔權限限制為 CI 使用者唯讀;禁止把 p8 明文貼進公開儲存庫。
在流水線內產生可上傳成品:對 .app 正確簽章後打包 zip/dmg;路徑含 pipeline id;計算 SHA256 並隨建置日誌輸出。
執行 submit 並擷取 submission id:使用 --wait 或自建輪詢;把 Apple 回傳狀態寫入成品目錄備查。
store log 與失敗分類:對 Invalid 類錯誤拉取詳細日誌;在工單系統關聯 xcodebuild -version 與儲存庫 commit。
依分發策略 staple 並驗收:stapler validate 與試安裝矩陣(乾淨 VM 優先);再進入入口或物件儲存上傳。
ZIP_PATH="dist/MyApp.${CI_PIPELINE_ID}.zip"
xcrun notarytool submit "$ZIP_PATH" \
--key "./AuthKey_XXXXX.p8" \
--key-id "$ASC_KEY_ID" \
--issuer "$ASC_ISSUER_ID" \
--wait \
--output-format json > "logs/notary-${CI_PIPELINE_ID}.json"
# 失敗時拉詳細日誌(submission id 從 json 解析)
# xcrun notarytool log <submission-id> --key ... > "logs/notary-detail.txt"
xcrun stapler staple "dist/MyApp.app"
提示:若同一流水線還承擔 App Store Connect 上傳,請閱讀 Fastlane 與 CI 銜接,把 API Key 角色(Developer vs App Manager)與 notarytool 最小權限寫進變數表,避免「一把鑰匙開所有門」。
在 Xcode 或 macOS 大版本升級日,建議先對「最小 zip 包」做金絲雀公證:固定一個 hello-world 級別的 signed bundle,驗證 notarytool 與 staple 全鏈,再放開業務儲存庫;此與黃金映像回滾策略互補。
若遠端 Mac 由供應商提供快照能力,請把「公證鏈版本」寫進快照標籤:包含 Xcode build 號、CLI tools 版本與 notarytool 行為變更紀錄,避免團隊在新映像上集體踩預設參數變化。
成品留存應對齊合規:部分產業須長期保存 notary JSON 與 stapler 輸出;及早決定這些 blob 與 IPA 並存於物件儲存,或放在具生命週期規則的稽核桶。
平台工程裡最常見的誤判,是把「能同時開幾個 xcodebuild」的並發模型直接套到公證上。實際上,上傳頻寬、Apple 側佇列、zip 壓縮 CPU 與 鑰匙圈解鎖 會在不同階段成為瓶頸。
建議為公證單獨定義 notary_slots:例如每台遠端 Mac 同時最多 1–2 個 submit,其餘 Job 在佇列裡等待;並把牆鐘逾時與「可接受的排隊 SLA」寫進內部文件。與 SwiftPM/Pods 磁碟治理 篇聯動:公證暫存 zip 往往體積大,應與 DerivedData 分卷或分目錄,避免系統盤在夜間批量任務時觸頂。
注意:不要在磁碟低於安全水位時繼續硬塞公證任務:半寫入的 zip 會上傳失敗且難以重現;應先停排程與清理,並記錄刪除路徑以便稽核。
重試策略上,應對「網路瞬斷」與「Apple 側繁忙」分層:前者可在秒級做有限次重試;後者應使用分鐘級退避並上限,避免觸發速率限制。所有重試必須記錄 submission id 與成品雜湊,防止「重複 staple 舊包」類事故。
若組織使用集中式代理,請把 Apple 相關網域與 TLS 繞過策略寫成顯式白名單;否則會出現「編譯全綠、公證全紅」且日誌只在代理層的現象,排障成本極高。
與僅在本機筆電上偶爾 Archive 相比,獨佔遠端 Mac 的價值在於7×24 可預測環境與固定 SSH 維運面:平台可以把公證 Runbook、磁碟水位與金鑰輪替綁進同一套節點畫像,而不是讓每台開發機各自為政。
若導入自癒代理程式,請確保不會刪除進行中的 notary 工作目錄:以租約或 pipeline 感知路徑保護刪除作業,避免與 stapler 步驟競爭。
下列條目用於內部對齊;具體閾值以你們封包大小與並發度為準。數據與比例來自常見平台工程實踐,實施前請在你們環境複測。
--wait 設定顯式上限 + 可告警。submission id、成品 SHA256、xcodebuild -version 三行,以便跨團隊複盤。純辦公室筆電做公證常受睡眠策略、VPN 抖動與個人鑰匙圈干擾;純 Linux 又無法完成官方 macOS 簽章鏈。把執行平面放到獨佔、長期在線、SSH 可達的遠端 Mac,並把 notarytool 與 staple 寫進可重複範本,才能把「能發封包」變成「能證明、能稽核、能交接」。相較自建零散機器或在不透明虛擬化層裡硬跑 Xcode,NodeMini 的 Mac Mini 雲端租賃在固定 SSH、清晰磁碟檔位與可複製的節點畫像上更利於平台治理;需要對比規格與價格時,可先閱讀 租賃價格說明,再結合 說明中心 完成接入與驗收。
落地時建議把本 Runbook 與內部「發佈等級」綁定:灰度、正式與熱修復對應不同的公證並發、日誌保留天數與 staple 驗收矩陣。