從連線到第一份建置成品

取得機器後的
第一個小時

前半小時用來確認連線、檢查工具鏈並首次編譯,後半小時整理簽章與流水線。你取得的是獨享實體 Mac,不是虛擬機器;原有的 macOS 圖形介面與命令列工作流程都能繼續使用。

已下單?請先登入控制台,查看實際主機位址、連接埠、使用者名稱與交付資訊。範例變數不是連線憑證。

00–10 分鐘

核對主機指紋並建立 SSH 連線。

10–20 分鐘

確認 Xcode、專案相依套件與磁碟空間。

20–30 分鐘

先執行免簽章檢查,再驗證 archive。

步驟一 · 先確認連線

用 SSH 操作命令列,用螢幕共享操作圖形介面

先在本機產生金鑰,只上傳公開金鑰。私密金鑰請留在自己的裝置上,並設定密碼。以下指令中的主機、連接埠和使用者名稱,請以控制台提供的實際資訊為準。

在本機 macOS 終端機執行
read -r -p "主機地址: " MAC_HOST
read -r -p "SSH 端口: " MAC_PORT
read -r -p "用户名: " MAC_USER
ssh-keygen -t ed25519 -f "$HOME/.ssh/osxhost_build"
cat "$HOME/.ssh/osxhost_build.pub"
ssh -p "$MAC_PORT" "$MAC_USER@$MAC_HOST"

公開金鑰檔案的副檔名是 .pub 。首次連線時使用交付的登入方式,將公開金鑰加入遠端 ~/.ssh/authorized_keys,再從另一個本機終端機驗證金鑰登入。

將公開金鑰加入遠端,再變更密碼

在遠端建立 ~/.ssh,並將目錄權限設為 700,公開金鑰檔案權限設為 600。使用編輯器新增公開金鑰,保留現有授權。接著執行 passwd 變更目前使用者的密碼。

另開一個終端機,使用 ssh -i ~/.ssh/osxhost_build -p "$MAC_PORT" "$MAC_USER@$MAC_HOST" 確認登入成功後,再結束原本的連線。請勿先關閉唯一可用的連線。

驗收:可透過金鑰登入新連線

透過加密通道使用螢幕共享

在本機執行下方的通道指令,並保持終端機開啟。在 macOS 開啟「螢幕共享」,連線至 vnc://127.0.0.1:5901;Windows 和 Linux 請使用支援相應 VNC 驗證方式的用戶端,連線至本機連接埠 5901。

遠端必須已啟用螢幕共享,並授權目前使用者。圖形介面登入憑證與 SSH 私密金鑰是兩種不同的驗證資料;請勿將 VNC 連接埠直接開放給所有來源。

驗收:可檢視並操作 macOS 桌面
本機 SSH 通道 · 目標為遠端迴路位址
ssh -N -L 5901:127.0.0.1:5900 \
  -i "$HOME/.ssh/osxhost_build" \
  -p "$MAC_PORT" "$MAC_USER@$MAC_HOST"

步驟二 · 本機 Mac → 雲端 Mac

分三個方向移轉,不複製整套舊環境

先完成資料與工具鏈驗收,再註冊 CI 執行器。分開確認「可以登入」與「可以無人值守建置」,避免把本機互動式設定誤當成流水線設定。

路徑 01 · 資料

移轉專案,不搬明文機密

  • 從鑰匙圈匯出所需的簽章憑證與私密金鑰,存為加密的 .p12,並透過獨立的安全管道傳送密碼。
  • 只移轉目前專案所需的描述檔,並確認團隊、App 識別碼與有效期限。
  • 請勿複製整個登入鑰匙圈。建立專用的建置鑰匙圈,按需匯入憑證並授權簽章工具。
  • 同步原始碼與必要資源,排除相依套件目錄、DerivedData 和舊建置成品。

驗收條件:專案版本一致、憑證包含私密金鑰,且敏感檔案未加入 Git。

路徑 02 · 工具鏈

鎖定版本,再安裝相依套件

  • 使用 xcodes list 查看版本;完成所需授權後,再安裝目標 Xcode。
  • 使用 xcode-select 明確指定目前使用的工具鏈,不依賴圖形介面中最近一次的選擇。
  • 確認 Homebrew 與 arm64 工具鏈,避免直接搬移 Intel 環境的相依套件目錄。
  • 使用 Ruby 版本管理工具與 Gemfile 固定 Ruby、CocoaPods 和 fastlane 版本。

驗收條件:終端機與 CI 使用的 Xcode、Ruby、Bundler 版本一致。

路徑 03 · CI 串接

只讓執行器處理可信任的工作

  • GitHub Actions:依儲存庫設定註冊 self-hosted runner,選擇 macOS ARM64 套件。
  • GitLab Runner:註冊專案層級的執行器,依需求選擇 shell 執行方式並限制專案範圍。
  • Jenkins:建立 macOS agent,使用隔離的工作目錄並以受控方式注入憑證。
  • 先以單一工作並行數完成簽章建置,再依尖峰記憶體使用量增加並行數。

驗收條件:推送一筆可信任的提交,並可清除記錄與建置成品。

在本機同步專案 · 保留遠端現有檔案
rsync -avz \
  --exclude='.git/' \
  --exclude='build/' \
  --exclude='DerivedData/' \
  --exclude='Pods/' \
  --exclude='node_modules/' \
  -e "ssh -p $MAC_PORT -i $HOME/.ssh/osxhost_build" \
  ./ "$MAC_USER@$MAC_HOST:~/build-workspace/"

這個同步指令不會刪除遠端檔案。正式流水線建議直接從版本儲存庫檢出固定提交;快取鍵至少應包含工具鏈版本、相依套件鎖定檔與目標架構。請勿讓不受信任的外部合併請求讀取簽章憑證。

步驟三 · 分別驗收簽章與建置成品

用一份 Fastfile 串接 match、gym 和 pilot

先在專案根目錄執行 bundle install,並提交 Gemfile.lock。以下流程假設簽章儲存庫已初始化、App 已建立,且 CI 已安全注入環境變數;不包含建立新的簽章資產。

遠端預檢 · 先確認工具鏈
xcode-select -p
xcodebuild -version
xcodebuild -showsdks
ruby --version
bundle --version
bundle exec fastlane --version
security find-identity -v -p codesigning
df -h "$HOME"

將簽章儲存庫網址設為 MATCH_GIT_URL,並將解密密碼設為 MATCH_PASSWORD。上傳時使用伺服器端 API 金鑰:金鑰檔案路徑由 ASC_KEY_PATH 指定,金鑰 ID 與發行者 ID 則分別由 ASC_KEY_ID、ASC_ISSUER_ID 指定。金鑰檔案應由 CI 秘密管理機制保管,並在執行期間寫入磁碟;請勿提交至專案儲存庫。

設定 IOS_APP_IDENTIFIER、IOS_TEAM_ID、IOS_WORKSPACE 和 IOS_SCHEME。簽章儲存庫中的發佈類型必須與 appstore 相符;先確認共享 scheme,再執行完整流水線。

fastlane/Fastfile · 單一 App 的 TestFlight 流水線
default_platform(:ios)

platform :ios do
  desc "Archive a signed build and upload it to TestFlight"
  lane :release do
    setup_ci

    api_key = app_store_connect_api_key(
      key_id: ENV.fetch("ASC_KEY_ID"),
      issuer_id: ENV.fetch("ASC_ISSUER_ID"),
      key_filepath: ENV.fetch("ASC_KEY_PATH"),
      in_house: false
    )

    match(
      type: "appstore",
      readonly: true,
      git_url: ENV.fetch("MATCH_GIT_URL"),
      app_identifier: ENV.fetch("IOS_APP_IDENTIFIER"),
      team_id: ENV.fetch("IOS_TEAM_ID"),
      api_key: api_key
    )

    gym(
      workspace: ENV.fetch("IOS_WORKSPACE"),
      scheme: ENV.fetch("IOS_SCHEME"),
      configuration: "Release",
      clean: true,
      export_method: "app-store",
      output_directory: "build/artifacts",
      output_name: "release.ipa"
    )

    pilot(
      api_key: api_key,
      ipa: "build/artifacts/release.ipa",
      skip_waiting_for_build_processing: true
    )
  end
end
執行與確認 · 由 CI 注入環境變數
bundle exec fastlane ios release
find build/artifacts -maxdepth 1 -type f -print

上傳成功不代表處理已完成。這份範例會略過上傳後的處理等待。請前往發佈後台確認建置處理結果,並確保每次上傳使用不同的建置編號。readonly: true 會阻止 CI 修改簽章儲存庫;憑證或描述檔到期時,請在受控環境更新後重新執行。

跨平台專案 · 保留鎖定檔

讓 iOS 階段獨立執行,並確保快取可更新

Flutter 和 React Native 的 iOS 階段都需要 Xcode、CocoaPods 與簽章環境。先通過平台診斷,再串接上傳步驟。不要因為快取命中就跳過相依套件一致性檢查。

Flutter:先完成模擬器建置,再匯出 IPA

固定 Flutter SDK 版本,並保留 pubspec.lock。重複使用 Pub 下載快取;Pods 以 Podfile.lock 作為快取鍵,DerivedData 則以 Xcode 版本和專案設定作為快取鍵。首次快取為空時,下載與編譯所需時間會比之後更長。

在專案根目錄執行下方指令。簽章設定完成後,flutter build ipa --release 才能完成實機建置匯出。

請勿跨越 Xcode 主要版本重複使用建置快取

React Native:鎖定 Node、Ruby 與 Pods 版本

提交 JavaScript 套件管理工具的鎖定檔,並依專案慣例使用對應的安裝指令。以下以 npm 為例;Ruby 相依套件由 Bundler 管理,避免系統層級的 gem 與專案版本互相覆蓋。

只有在新增相依套件確實需要更新索引時,才使用 pod install --repo-update。日常執行 bundle exec pod install,不要每次都刪除 Pods 和鎖定檔。

快取鍵須包含架構與相依套件鎖定檔
Flutter 專案根目錄
flutter --version
flutter doctor -v
flutter pub get
flutter build ios --simulator
flutter build ipa --release
React Native 專案根目錄 · 使用 npm 的專案
node --version
npm ci
bundle install
(cd ios && bundle exec pod install)
xcrun simctl list devices available

並行模擬器測試必須設限。從可用裝置清單取得兩個實際 UDID,分別設為 SIM_UDID_A、SIM_UDID_B;在已設定 IOS_WORKSPACE、IOS_SCHEME 的專案中執行以下測試。首次先測試一個目標,再增加至兩個,並觀察記憶體壓力與磁碟寫入狀況。

Xcode 測試 · 使用專案支援的兩個模擬器目標
xcodebuild test \
  -workspace "$IOS_WORKSPACE" \
  -scheme "$IOS_SCHEME" \
  -destination "platform=iOS Simulator,id=$SIM_UDID_A" \
  -destination "platform=iOS Simulator,id=$SIM_UDID_B" \
  -parallel-testing-enabled YES \
  -maximum-concurrent-test-simulator-destinations 2 \
  -resultBundlePath "build/TestResults-$(date +%s).xcresult"

容器範圍 · 區分 Linux 服務與 macOS 建置

容器可執行配套服務,不能取代 Xcode 主機

節點本身是獨享實體 Mac。Docker Desktop 或 OrbStack 提供的 Linux 容器環境仍透過 Linux 執行;「實體節點」不代表容器與 macOS 共用同一個核心。

工作建議執行位置注意事項
測試資料庫、快取與內部 APILinux 容器優先選用 arm64 映像檔,並限制記憶體與監聽範圍。
Xcode archive 與模擬器測試macOS 主機系統需要 macOS SDK、簽章工具與模擬器執行環境。
僅提供 amd64 映像檔的服務先驗證相容性轉譯可能增加額外負擔,不能直接套用 arm64 的效能預期。
長時間執行的配套服務依資源隔離需求決定會與建置工作共用實體記憶體、SSD 與網路,請設限並保留備份。
安裝並啟動所選容器工具後,檢查
uname -m
docker version
docker info --format '{{.Architecture}}'
docker system df
docker stats --no-stream

選擇 Docker Desktop 或 OrbStack 其中一套來管理容器,避免不清楚目前的 Docker context 指向哪裡。新增服務前,先記錄可用記憶體與磁碟空間;不要把清除容器資料當成預設的故障排除方式。

擴充功能 · 先識別裝置,再寫入資料

將快取移至擴充磁碟,不要把多台機器當成一台

外接 SSD 與 Thunderbolt 5 並聯是個別設定項目。實際裝置名稱、掛載路徑、介面支援與連線拓撲,請以訂單交付資訊為準。尚未確認前,請勿執行格式化或分割磁碟指令。

外接 SSD:確認掛載點與權限

使用 diskutil list external 識別外接裝置,再查看個別磁碟區資訊。如果磁碟區已格式化但尚未掛載,確認識別碼後執行 diskutil mount "$DISK_IDENTIFIER"。不要只憑磁碟編號就認定它是外接 SSD。

建議將相依套件快取、DerivedData 和暫存成品放在此處。將實際路徑設為 CI 變數 BUILD_CACHE_ROOT,確認建置使用者具有寫入權限,並驗證重新啟動後路徑仍然有效。原始碼與簽章資料請另外備份。

擴充容量不等於備份

TB5 並聯:逐台確認連線拓撲

並聯不會自動將多台機器的 CPU 或統一記憶體合併成單一程序可用的資源池。必須由應用程式或 CI 排程層拆分工作,並在每台機器上分別設定工具鏈、執行器與工作目錄。

如果交付方案使用 Thunderbolt 網路連線,請逐台確認網路介面、位址與路由,再測試點對點連線。採購前請透過工單說明台數、機型與傳輸用途,不要自行假設所有機型的介面能力都相同。

分散式工作需要應用程式本身支援
遠端唯讀檢查 · 不變更磁碟或網路
diskutil list external
ls -la /Volumes
df -h
system_profiler SPThunderboltDataType
networksetup -listallhardwareports

擴充項目的四種租用期限價格請見方案頁。若需變更現有訂單,請先提交工單確認作業方式與資料移轉安排,請勿在正式建置期間直接變更磁碟結構。

最後一步 · 提供資訊,精準定位問題

先收集必要診斷資訊,再變更設定

記錄訂單編號、節點、發生時間與時區,以及最近一次成功建置的提交編號。保留第一個錯誤訊息與結束代碼,不要只提供記錄的最後一行。以下指令可協助縮小問題範圍,不會自動修復所有問題。

01

SSH 無法連線

先核對位址、連接埠、使用者名稱與私密金鑰路徑。連線逾時通常先檢查網路是否可達;驗證失敗則先檢查使用者名稱、公開金鑰授權與權限。若主機指紋變更,請先透過工單確認,不要直接忽略。

本機終端機 · 輸出可能包含主機資訊
ssh -vv -o ConnectTimeout=10 \
  -i "$HOME/.ssh/osxhost_build" \
  -p "$MAC_PORT" "$MAC_USER@$MAC_HOST"
提交連線問題工單
02

憑證失效或找不到簽章身分

檢查憑證是否包含私密金鑰、是否已過期,以及描述檔是否與 App 和團隊相符。CI 專用鑰匙圈也必須能在建置工作階段中存取。請先在受控環境修復簽章資產,再以唯讀模式同步 match。

遠端終端機 · 查看簽章身分與鑰匙圈清單
security find-identity -v -p codesigning
security list-keychains -d user
bundle exec fastlane match appstore --readonly
提交簽章問題工單
03

磁碟空間不足,無法寫入 archive

先區分系統磁碟區與外接磁碟區,再找出 DerivedData、模擬器和建置成品的空間用量。只清除確認可重新產生的快取;不要批次刪除鑰匙圈、簽章檔案或正在使用的工作目錄。

遠端終端機 · 僅查看空間用量
df -h
du -sh "$HOME/Library/Developer/Xcode/DerivedData" \
  "$HOME/Library/Developer/CoreSimulator" \
  "$HOME/build-workspace" 2>/dev/null
xcrun simctl list devices unavailable
提交磁碟問題工單
04

Xcode 長時間停留在 indexing

先確認索引程序是否仍在執行,再檢查相依套件安裝狀況與剩餘磁碟空間。圖形介面的索引狀態與命令列建置完成並非同一條件;可查看記錄,確認 archive 是否確實卡住。

遠端終端機 · 查看程序與建置設定
ps -axo pid,%cpu,%mem,etime,comm | grep -E 'Xcode|SourceKit|swift'
xcode-select -p
xcodebuild -list -workspace "$IOS_WORKSPACE"
df -h "$HOME"
提交 Xcode 問題工單

請勿在工單中附上私密金鑰、憑證密碼、存取權杖或完整的簽章儲存庫內容。提交前,請遮蔽記錄中的憑證與敏感路徑。若無法登入控制台,請寄信至 support@osxhost.com並提供訂單編號與故障情況;電子郵件和控制台工單都是公開的支援管道。

先完成一份 archive,再增加並行數

將工具鏈版本、簽章注入方式、快取鍵與成品目錄寫入流水線。需要機器就下單,遇到問題就附上診斷結果提交工單。