雲端 Mac 建置與驗收 macOS PKG 安裝檔

CI/CD 實踐 ·約 9 分鐘閱讀

雲端 Mac 建置與驗收 macOS PKG 安裝檔

當團隊需要把命令列工具、背景代理程式或公司內部應用程式部署到多台 Mac 時,直接複製檔案很快就會遇到三個問題:目標路徑不一致、升級後殘留舊檔,以及無法稽核安裝結果。macOS PKG 能把 payload、權限、安裝指令稿與版本收據整合成可追蹤的產物,但前提是不能直接把開發工作區交給 pkgbuild。以下以安裝至 /Library/Application Support/AcmeTool 的命令列元件為例,建立一套可放進雲端 Mac CI 的建置與驗收流程。

先定義安裝契約

封裝前應先明確定義目標路徑、擁有者、權限、套件識別碼與升級規則。套件識別碼發布後應維持不變,版本則必須單調遞增;否則系統收據將無法準確判斷版本之間的覆蓋關係。

項目 範例 驗收要求
安裝根目錄 /Library/Application Support/AcmeTool 不寫入使用者家目錄
可執行檔 bin/acmetool root:wheel,權限 0755
設定範本 config/default.json 權限 0644,不含金鑰
套件識別碼 com.example.acmetool.pkg 不隨分支變更
版本 1.4.0 與發布標籤一致

建置機上的專案目錄不屬於安裝契約的一部分。.git、測試報告、下載快取、暫存記錄與本機產生的延伸屬性,都不應進入 PKG。

最可靠的封裝輸入不是「目前的儲存庫」,而是重新建立、內容可逐項列舉的暫存目錄。

建立乾淨的 payload

每次工作開始時都先刪除舊的暫存區,明確複製允許交付的檔案,再統一設定權限。不要使用範圍過大的 cp -R .,否則新增檔案可能在無人察覺的情況下進入安裝套件。

set -euo pipefail

ROOT="$PWD"
STAGE="$ROOT/build/stage"
PKGDIR="$ROOT/build/packages"
INSTALL_DIR="$STAGE/Library/Application Support/AcmeTool"

rm -rf "$STAGE" "$PKGDIR"
mkdir -p "$INSTALL_DIR/bin" "$INSTALL_DIR/config" "$PKGDIR"

install -m 0755 "$ROOT/dist/acmetool" "$INSTALL_DIR/bin/acmetool"
install -m 0644 "$ROOT/packaging/default.json" \
  "$INSTALL_DIR/config/default.json"

xattr -cr "$STAGE"
find "$STAGE" -type d -exec chmod 0755 {} +
find "$STAGE" -type f ! -path '*/bin/acmetool' -exec chmod 0644 {} +

接著產生一份清單,既方便進行程式碼審查,也能在建置完成時比對實際輸入內容:

find "$STAGE" -print0 |
  sort -z |
  xargs -0 stat -f '%Sp %Su:%Sg %N' > "$PKGDIR/payload-manifest.txt"

grep -Eq '/(\.git|DerivedData|node_modules)(/|$)' \
  "$PKGDIR/payload-manifest.txt" && exit 1 || true

這裡先移除延伸屬性,再設定權限,避免把下載來源標記或開發機上的 ACL 帶入產物。設定範本只能包含預設值,執行階段使用的權杖應在部署時注入。

使用 pkgbuild 產生元件套件

安裝指令稿應放在獨立目錄中,並確認具有執行權限。指令稿應具備冪等性、快速且不需要互動;不要讀取終端機輸入,也不要假設系統中有特定使用者處於登入狀態。

mkdir -p packaging/scripts
cat > packaging/scripts/postinstall <<'SH'
#!/bin/sh
set -eu

TARGET="/Library/Application Support/AcmeTool"
test -x "$TARGET/bin/acmetool"
chown -R root:wheel "$TARGET"
chmod 0755 "$TARGET" "$TARGET/bin" "$TARGET/bin/acmetool"
chmod 0644 "$TARGET/config/default.json"
exit 0
SH
chmod 0755 packaging/scripts/postinstall

pkgbuild \
  --root "$STAGE" \
  --identifier "com.example.acmetool.pkg" \
  --version "1.4.0" \
  --install-location "/" \
  --scripts packaging/scripts \
  "$PKGDIR/AcmeTool-component.pkg"

只有一個元件時,元件套件本身就能安裝。若需要統一的歡迎頁面、授權文字,或要組合多個元件,再使用 productbuild 產生散發套件:

productbuild \
  --package "$PKGDIR/AcmeTool-component.pkg" \
  "$PKGDIR/AcmeTool-1.4.0.pkg"

需要簽章時,應在受保護的 CI 環境中讀取 Installer 簽章識別身分,並透過 --sign "$INSTALLER_IDENTITY" 建置正式套件。未簽章的產物仍可用於前置測試,但不能冒充最終交付檔案。

安裝前先進行靜態驗收

第一層檢查不會修改系統。先確認套件識別碼、版本與 payload,再展開散發套件,檢查指令稿與中繼資料:

PKG="$PKGDIR/AcmeTool-1.4.0.pkg"
EXPANDED="$PKGDIR/expanded"

pkgutil --check-signature "$PKG" || true
pkgutil --payload-files "$PKGDIR/AcmeTool-component.pkg"
rm -rf "$EXPANDED"
pkgutil --expand-full "$PKG" "$EXPANDED"

grep -R "com.example.acmetool.pkg" "$EXPANDED"
grep -R "1.4.0" "$EXPANDED"
find "$EXPANDED" -type f -name postinstall -exec sh -n {} \;

如果正式套件要求具備簽章,就不應讓 pkgutil --check-signature 容錯執行,而應讓非零結束碼直接中止流水線。此外,也必須拒絕絕對建置路徑、私鑰片段與工作區使用者名稱:

if grep -R -E "$HOME|BEGIN (RSA |EC )?PRIVATE KEY" "$EXPANDED"; then
  echo "sensitive build data found" >&2
  exit 1
fi

常見的錯誤是只比較套件檔案大小。檔案大小穩定不代表內容也保持一致;清單、識別碼、版本、指令稿語法與簽章狀態都必須分別檢查。

隔離安裝並核對收據

最終驗收必須在可回復的測試環境中執行,因為 installer 會寫入系統路徑與安裝收據。測試節點應在工作開始前還原至已知狀態,而且不應與日常開發工作階段共用。

sudo installer -pkg "$PKG" -target /

test -x "/Library/Application Support/AcmeTool/bin/acmetool"
test "$(stat -f '%Sp' \
  '/Library/Application Support/AcmeTool/bin/acmetool')" = "-rwxr-xr-x"

pkgutil --pkg-info "com.example.acmetool.pkg"
pkgutil --files "com.example.acmetool.pkg" |
  sort > "$PKGDIR/installed-files.txt"

"/Library/Application Support/AcmeTool/bin/acmetool" --version

安裝成功只代表指令稿傳回零,還需要驗證可執行檔能否啟動、設定範本能否解析、收據版本是否正確,並將安裝記錄與檔案清單儲存為 CI 產物。發生失敗時,應先保留 /var/log/install.log 中對應時段的內容,再還原測試環境;不要立刻重新執行,以免覆蓋問題現場。

收尾檢查可以固定為五項:暫存目錄中沒有額外檔案、權限與擁有者符合契約、套件版本單調遞增、正式產物通過簽章驗證,以及隔離安裝後能同時驗證命令與收據。如此一來,PKG 就不再只是「可以連按兩下的壓縮檔」,而是可審查、可重現、可進行迴歸驗證的工程交付產物。

常見問題

pkgbuild 的 root 可以直接指向專案目錄嗎?

不建議。應先建立只包含最終安裝內容的暫存目錄,避免原始碼、快取、記錄檔與本機延伸屬性被意外封裝。

沒有 Installer 簽章憑證時可以先測試 PKG 流程嗎?

可以。先用未簽章套件完成結構、權限、指令稿和隔離安裝驗證,正式交付前再簽章,並以 pkgutil --check-signature 確認結果。

獨享實體節點

為下一次建置或推論任務設定雲端 Mac

從三種 Apple Silicon 設定、六個節點與固定租期中選擇,實際可用狀態以控制台即時回傳為準。

設定雲端 Mac