Когда команде нужно развернуть утилиту командной строки, фоновый агент или внутреннее корпоративное приложение на нескольких компьютерах Mac, простое копирование файлов быстро приводит к трём проблемам: целевые пути различаются, после обновления остаются старые файлы, а результат установки невозможно проверить в рамках аудита. Формат macOS PKG позволяет объединить payload, права доступа, установочные сценарии и квитанцию с версией в единый отслеживаемый артефакт. Однако для этого нельзя просто передавать рабочий каталог разработчика в pkgbuild. Ниже на примере компонента командной строки, устанавливаемого в /Library/Application Support/AcmeTool, показан процесс сборки и приёмочного тестирования, пригодный для CI на облачных Mac.
Сначала определите контракт установки
До упаковки необходимо явно определить целевой путь, владельца, права доступа, идентификатор пакета и правила обновления. После выпуска идентификатор пакета должен оставаться неизменным, а версия — монотонно увеличиваться. Иначе системные квитанции не смогут правильно определить, какой пакет заменяет предыдущий.
| Параметр | Пример | Критерий приёмки |
|---|---|---|
| Корневой каталог установки | /Library/Application Support/AcmeTool |
Не записывать данные в домашний каталог пользователя |
| Исполняемый файл | bin/acmetool |
root:wheel, права 0755 |
| Шаблон конфигурации | config/default.json |
Права 0644, без секретов |
| Идентификатор пакета | com.example.acmetool.pkg |
Не меняется в зависимости от ветки |
| Версия | 1.4.0 |
Совпадает с тегом релиза |
Каталог проекта на сборочной машине не является частью контракта установки. В PKG не должны попадать .git, отчёты тестирования, кеш загрузок, временные журналы и расширенные атрибуты, созданные на локальной машине.
Самый надёжный источник для упаковки — не «текущее содержимое репозитория», а заново созданный staging-каталог с полностью перечислимым содержимым.
Создайте чистый payload
В начале каждой задачи удаляйте прежнюю staging-область, явно копируйте только разрешённые к поставке файлы, а затем централизованно назначайте права. Не используйте слишком широкий вызов 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"
Если требуется подпись, считывайте идентификатор подписи Installer в защищённой среде CI и собирайте официальный пакет с параметром --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, а затем восстановите тестовую среду. Не запускайте задачу повторно сразу же, иначе исходные данные о сбое будут перезаписаны.
Итоговую проверку можно свести к пяти постоянным пунктам: в staging-каталоге нет лишних файлов; права и владельцы соответствуют контракту; версия пакета монотонно увеличивается; подпись официального артефакта успешно проверяется; после изолированной установки можно проверить и команду, и квитанцию. Такой подход превращает PKG из «архива, который можно открыть двойным щелчком» в проверяемый, воспроизводимый и пригодный для регрессионного тестирования инженерный артефакт.
Часто задаваемые вопросы
Можно ли передать pkgbuild корень рабочего репозитория?
Не следует. Создайте отдельный staging-каталог только с файлами, которые должны попасть на целевую систему, иначе в PKG могут оказаться исходники, кэши, журналы и локальные атрибуты.
Можно ли проверить процесс без сертификата подписи Installer?
Да. Соберите неподписанный PKG, проверьте структуру, права, сценарии и тестовую установку, а перед распространением подпишите пакет доступной Installer-идентичностью и выполните pkgutil --check-signature.
Настройте облачный Mac для следующей сборки или задачи машинного обучения
Выберите одну из трёх конфигураций Apple Silicon, шести узлов и фиксированных сроков аренды. Фактическая доступность отображается в консоли в реальном времени.