클라우드 Mac에서 macOS PKG 빌드와 검증하기

CI/CD ·약 10분 읽기

클라우드 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 서명 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의 실패를 허용해서는 안 됩니다. 0이 아닌 종료 코드가 발생하면 파이프라인을 즉시 중단해야 합니다. 절대 경로로 된 빌드 경로, 개인 키 조각, 작업 공간 사용자 이름도 거부해야 합니다.

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

설치 성공은 스크립트가 0을 반환했다는 의미일 뿐입니다. 실행 파일이 실제로 시작되는지, 구성 템플릿을 파싱할 수 있는지, 영수증 버전이 올바른지도 검증해야 하며 설치 로그와 파일 목록을 CI 산출물로 보관해야 합니다. 실패하면 먼저 /var/log/install.log에서 해당 시간대의 로그를 보존한 뒤 테스트 환경을 복원하십시오. 즉시 다시 실행해 현장 정보를 덮어쓰면 안 됩니다.

마지막 검사는 다섯 가지 항목으로 고정할 수 있습니다. 스테이징 디렉터리에 불필요한 파일이 없는지, 권한과 소유자가 계약을 준수하는지, 패키지 버전이 단조 증가하는지, 정식 산출물의 서명이 유효한지, 격리 설치 후 명령과 영수증을 모두 검증할 수 있는지 확인합니다. 이 과정을 거치면 PKG는 단순히 “더블 클릭할 수 있는 압축 파일”이 아니라 검토 가능하고 재현 가능하며 회귀 테스트가 가능한 엔지니어링 산출물이 됩니다.

자주 묻는 질문

pkgbuild의 root에 저장소 경로를 바로 지정해도 되나요?

권장하지 않습니다. 최종 설치 파일만 담은 별도 스테이징 디렉터리를 만들고 그 경로를 root로 전달해야 캐시, 로그, 소스 파일이 패키지에 섞이지 않습니다.

Installer 서명 인증서 없이도 PKG 파이프라인을 시험할 수 있나요?

가능합니다. 먼저 서명되지 않은 패키지로 구조, 권한, 스크립트와 격리 설치를 검증하고, 배포 단계에서 서명한 뒤 pkgutil --check-signature로 최종 확인합니다.

독점 물리 노드

다음 빌드 또는 추론 작업을 위한 클라우드 Mac 구성

세 가지 Apple Silicon 구성, 여섯 개 노드와 고정 대여 기간 중에서 선택할 수 있으며, 실제 사용 가능 여부는 콘솔에서 실시간으로 확인됩니다.

클라우드 Mac 구성