クラウドMacでmacOS PKGを構築・検証する

CI/CD ·約 11 分

クラウドMacでmacOS PKGを構築・検証する

チームでコマンドラインツール、バックグラウンドエージェント、社内アプリケーションを複数のMacへ配布する場合、ファイルを直接コピーする方法では、すぐに3つの問題に直面します。インストール先のパスが統一されないこと、アップグレード時に古いファイルが残ること、そしてインストール結果を監査できないことです。macOS PKGなら、payload、権限、インストールスクリプト、バージョンレシートを追跡可能な1つの成果物にまとめられます。ただし、開発用ワークスペースをそのまま 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"

コンポーネントが1つだけであれば、このコンポーネントパッケージをそのままインストールできます。共通のウェルカム画面やライセンステキストが必要な場合、または複数のコンポーネントをまとめる場合は、productbuild で配布パッケージを作成します。

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

署名が必要な場合は、保護されたCI環境でInstaller署名用IDを読み込み、--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 の該当時間帯を保存し、その後でテスト環境を復元します。すぐに再実行して障害発生時の状態を上書きしてはいけません。

最終チェックは、次の5項目に固定できます。ステージングディレクトリに余分なファイルがないこと、権限と所有者が契約どおりであること、パッケージバージョンが単調増加していること、正式な成果物の署名検証が成功すること、そして隔離環境へのインストール後にコマンドとレシートの両方を検証できることです。これによりPKGは、単なる「ダブルクリックできる圧縮ファイル」ではなく、レビュー可能で、再現性があり、回帰テストにも対応できるエンジニアリング成果物になります。

よくある質問

pkgbuildのrootにリポジトリを直接指定してもよいですか?

推奨しません。対象環境へ配置するファイルだけを含むステージングディレクトリを作り、ソース、キャッシュ、ログ、ローカル属性の混入を防ぎます。

Installer署名証明書がなくてもPKG工程をテストできますか?

可能です。まず未署名PKGで構造、権限、スクリプト、隔離インストールを検証し、配布版だけを署名してpkgutil --check-signatureで確認します。

専有物理ノード

次のビルドや推論タスクに向けてクラウドMacを構成

3種類のApple Silicon構成、6つのノード、固定利用期間から選択できます。実際の利用可能状況はコンソールのリアルタイム表示をご確認ください。

クラウドMacを構成