Очистка метаданных артефактов в Cloud Mac CI

Очистка метаданных артефактов в Cloud Mac CI

Сборка на облачном Mac может успешно завершиться, а архив — корректно распаковаться, но получатель всё равно рискует обнаружить ._Config.json, .DS_Store или неизвестные расширенные атрибуты на этапе загрузки. Обычно причина кроется не в компиляторе, а в действиях через Finder, копировании между разными файловыми системами и различиях в обработке метаданных macOS архиваторами. Решение состоит не в однократной грубой очистке рабочей области, а в разделении процесса доставки на четыре контролируемых этапа: staging, сканирование, архивацию и повторную проверку.

Сначала определите, что разрешено включать в пакет

Помимо имени и содержимого, файлы macOS могут содержать расширенные атрибуты, ресурсные ветви и метаданные Finder. В локальной файловой системе они не всегда создают проблемы, но после переноса в ZIP, на общий диск или в объектное хранилище могут превратиться в сопутствующие файлы AppleDouble.

Заранее определите правила для артефакта, а не удаляйте файлы по ходу упаковки:

Объект Политика по умолчанию Причина
.DS_Store Запретить Описывает только представление каталога в Finder
._* Запретить Часто возникает при преобразовании ресурсных ветвей между файловыми системами
com.apple.quarantine Проверять и обрабатывать выборочно Может быть добавлен инструментом загрузки или браузером
com.apple.ResourceFork Оценивать с учётом типа артефакта Обычным пакетам конфигурации, как правило, не нужен, но некоторые файлы могут от него зависеть
Подписанный пакет приложения Сохранять структуру и повторно проверять Безусловная очистка повышает риск нарушения подписи

Очищать следует отдельный staging-каталог, а не рабочую область с исходным кодом. Тогда ошибка в политике не изменит кеши, каталоги зависимостей или входные данные, необходимые для следующей сборки.

Если поставка содержит подписанное приложение, сначала проверьте подпись исходного артефакта. Для обычных документов, конфигураций и журналов можно применять более строгую политику запрета метаданных. Не обрабатывайте эти две категории одной рекурсивной командой удаления.

Создайте одноразовый staging-каталог

Staging-каталог должен использоваться только текущей задачей и удаляться при её завершении. Не применяйте постоянный путь /tmp/release: параллельные задачи могут перезаписать данные друг друга.

#!/bin/bash
set -euo pipefail

SOURCE="${1:?source path required}"
OUTPUT="${2:?output path required}"
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/bamini-artifact.XXXXXX")"
UNPACK="$(mktemp -d "${TMPDIR:-/tmp}/bamini-unpack.XXXXXX")"

cleanup() {
  rm -rf "$STAGE" "$UNPACK"
}
trap cleanup EXIT

/usr/bin/ditto --norsrc "$SOURCE" "$STAGE/payload"
find "$STAGE/payload" -type f -name '.DS_Store' -delete
find "$STAGE/payload" -type f -name '._*' -delete

mktemp создаёт отдельный каталог для каждого запуска, а trap обеспечивает очистку после успешного завершения, ошибки или прерывания. Здесь содержимое копируется с помощью ditto --norsrc, чтобы не переносить ресурсные ветви намеренно. Последующее сканирование всё равно необходимо: во входном каталоге уже могут находиться физически существующие файлы ._.

Если процесс требует доставки полного пакета приложения, сначала убедитесь, что выбранный способ копирования сохраняет всё необходимое для его работы. Надёжнее размещать пакет приложения и обычные вложения в разных staging-каталогах и применять к ним отдельные политики.

Сканируйте расширенные атрибуты вместо полного удаления

Команда xattr -cr удобна, но действует слишком широко. Она удаляет не только известные атрибуты карантина, но и другие атрибуты, которые ещё не включены в правила проверки. В результате конвейер становится «зелёным», а первопричина остаётся скрытой.

Сначала можно вывести имена атрибутов, а затем останавливать задачу при обнаружении явно запрещённых элементов:

ATTR_REPORT="$STAGE/xattr.txt"

if /usr/bin/xattr -lr "$STAGE/payload" >"$ATTR_REPORT" 2>&1; then
  if grep -E 'com\.apple\.(quarantine|ResourceFork)' "$ATTR_REPORT"; then
    echo "disallowed macOS metadata found" >&2
    exit 41
  fi
fi

if find "$STAGE/payload" -type f \( -name '.DS_Store' -o -name '._*' \) -print -quit |
  grep -q .; then
  echo "hidden metadata files found" >&2
  exit 42
fi

Этот контроль использует принцип «обнаружить и завершить с ошибкой», что помогает сначала определить этап, на котором появились метаданные. После выявления источника проблему следует устранять выборочно при загрузке, копировании или генерации. Если определённый расширенный атрибут действительно разрешён бизнес-требованиями, укажите в списке разрешений имя атрибута и область файлов, а не исключайте из проверки весь каталог.

Фиксируйте этап появления метаданных

Выполняйте сканирование после получения исходного кода, после компиляции и после staging. Первый этап, на котором возникла аномалия, задаёт границы расследования. К распространённым источникам относятся просмотр каталогов через графический интерфейс, копирование из неродной файловой системы, а также инструменты, которые сначала загружают данные, а затем распаковывают их. Сканирование только перед окончательной архивацией способно заблокировать загрязнённый пакет, но не позволяет определить ответственный этап.

После архивации обязательно распакуйте и проверьте пакет повторно

Чистый staging-каталог не гарантирует, что архиватор не запишет метаданные повторно. После создания ZIP проверьте имена его элементов, а затем распакуйте архив в новый каталог и выполните повторную проверку.

rm -f "$OUTPUT"
/usr/bin/ditto -c -k --norsrc --keepParent \
  "$STAGE/payload" "$OUTPUT"

/usr/bin/unzip -Z1 "$OUTPUT" >"$STAGE/members.txt"

if grep -E '(^|/)\.DS_Store$|(^|/)\._[^/]+$' "$STAGE/members.txt"; then
  echo "archive contains forbidden metadata files" >&2
  exit 43
fi

/usr/bin/ditto -x -k "$OUTPUT" "$UNPACK"

if find "$UNPACK" -type f \( -name '.DS_Store' -o -name '._*' \) -print -quit |
  grep -q .; then
  echo "extracted artifact failed metadata check" >&2
  exit 44
fi

Проверка списка элементов выполняется быстро и подходит для первого барьера. Повторная проверка после распаковки дополнительно охватывает преобразование путей и поведение распаковщика. Необходимы обе проверки: система загрузки получает архивный файл, а пользователь в итоге работает с распакованным содержимым.

Если пакет содержит подписанное приложение, найдите распакованный каталог .app и снова выполните строгую проверку подписи. Недостаточно проверять только копию до упаковки, иначе изменения, внесённые параметрами архивации, останутся незамеченными.

Включите контроль в воспроизводимый процесс доставки

Рекомендуется закрепить за скриптом два параметра — «входной каталог» и «выходной файл» — и при каждом запуске сохранять следующие подтверждения:

  1. Путь и тип артефакта до staging.
  2. Результат сканирования расширенных атрибутов.
  3. Список элементов ZIP.
  4. Результат проверки скрытых файлов после распаковки.
  5. Результат повторной проверки подписи при наличии пакета приложения.
  6. Хеш SHA-256 итогового архива.

Хеш можно получить командой shasum -a 256 artifact.zip. Он не доказывает, что содержимое двух ZIP семантически полностью совпадает, но подтверждает отсутствие побайтовых изменений при загрузке, скачивании и передаче.

При выполнении таких задач на облачном Mac BAMini сначала проверьте в консоли доступные на данный момент конфигурации, затем добавьте скрипт в репозиторий и запускайте его через CI. Не полагайтесь на то, что кто-то из команды не забудет выполнить ручную очистку. После ошибки контроля сохраните список элементов и отчёт об атрибутах, но удалите временные каталоги с содержимым проекта. Следующая задача должна создать новый staging-каталог, чтобы остаточные данные предыдущего запуска не влияли на результат.

Конечная цель — не удалить все расширенные атрибуты, а оставить в поставляемом пакете только содержимое, явно одобренное командой. Изолированный staging ограничивает область изменений, поэтапное сканирование локализует источник, а проверка после распаковки подтверждает фактический результат доставки. Только сочетание этих трёх мер предотвращает повторное появление скрытых метаданных при разных способах копирования.

Часто задаваемые вопросы

Можно ли выполнить xattr -cr для всего рабочего каталога?

Нет, без необходимости этого делать не стоит. Команда удалит все расширенные атрибуты, включая потенциально нужные resource fork. Очищайте только отдельный staging-каталог по списку разрешённых атрибутов.

Почему после удаления .DS_Store в архиве остаются файлы ._?

Это сопутствующие файлы AppleDouble, а не разновидность .DS_Store. Нужно отключить перенос resource fork при упаковке и отдельно проверить полный список элементов готового архива.

Когда проверять подпись приложения?

Сначала проверьте исходное приложение, затем скопируйте его в staging без массового удаления атрибутов. После упаковки распакуйте архив в новый каталог и повторите строгую проверку подписи.

Выделенный физический узел

Перенесите следующую сборку в облачный Mac mini

Выберите конфигурацию, период оплаты и регион узла, чтобы запускать разработку, сборки и автоматизацию на выделенной физической машине.

Арендовать Mac mini