В задачах CI нередко требуется монтировать DMG: чтобы проверить структуру установочного пакета, прочитать тестовые данные, проверить каталог поставки после подписания или сравнить содержимое двух образов диска. При однократном ручном запуске проблем обычно не возникает. Сбои начинаются при параллельном выполнении задач, аварийном завершении скрипта или наличии тома, оставшегося от предыдущего задания. В таких условиях скрипт, жёстко привязанный к /Volumes/Product, может незаметно прочитать данные другой задачи вместо немедленного завершения с ошибкой.
Почему стандартное монтирование теряет управляемость при параллельном запуске
Команда hdiutil attach App.dmg выбирает имя точки монтирования на основе метки тома внутри образа. Если том с таким именем уже существует, macOS может создать новый путь с числовым суффиксом. Если скрипт продолжит обращаться к исходному фиксированному каталогу, результатам тестирования больше нельзя будет доверять.
Ещё одна распространённая проблема — выполнение detach только при успешном прохождении задачи. Если команда проверки завершится ошибкой, задача будет остановлена или один из последующих шагов вернёт управление раньше времени, очистка не запустится. На многократно используемых облачных узлах Mac постепенно накапливаются оставшиеся тома, что приводит к конфликтам меток, блокировке файлов и ошибочному определению рабочего каталога.
Точку монтирования следует считать временным ресурсом конкретной задачи, а не общим путём на машине. Одна и та же задача должна отвечать за её создание, использование, отключение и диагностику.
Перед изменением процесса зафиксируйте текущее состояние:
/usr/bin/hdiutil info
/bin/df -h
/bin/ls -la /Volumes
Команда hdiutil info позволяет проверить соответствие между образами, устройствами и путями монтирования. df показывает только использование файловой системы и не заменяет проверку связей с образами.
Назначайте каждой задаче уникальную точку монтирования
Каталог монтирования должен содержать стабильный и уникальный идентификатор задачи. Одного имени репозитория недостаточно: для одного репозитория могут одновременно выполняться задания разных веток или повторные запуски. Кроме того, из идентификатора необходимо удалять или заменять символы пути, включая косые черты и пробелы.
Следующий скрипт сначала проверяет образ, затем монтирует его в режиме только для чтения и пытается отключить при штатном завершении, ошибке команды или получении сигнала завершения:
#!/bin/bash
set -euo pipefail
IMAGE="${1:?usage: mount-image.sh path/to/file.dmg}"
RAW_JOB_ID="${CI_JOB_ID:-local-$$}"
JOB_ID="${RAW_JOB_ID//[^a-zA-Z0-9._-]/_}"
MOUNT="${TMPDIR%/}/sdkmac-image-${JOB_ID}"
ATTACHED=0
cleanup() {
local status=$?
trap - EXIT INT TERM
if (( ATTACHED == 1 )); then
if ! /usr/bin/hdiutil detach "$MOUNT"; then
printf 'unable to detach %s\n' "$MOUNT" >&2
fi
fi
/bin/rmdir "$MOUNT" 2>/dev/null || true
exit "$status"
}
trap cleanup EXIT INT TERM
/usr/bin/hdiutil verify "$IMAGE"
/bin/mkdir -p "$MOUNT"
/usr/bin/hdiutil attach \
-nobrowse \
-readonly \
-mountpoint "$MOUNT" \
"$IMAGE"
ATTACHED=1
test -r "$MOUNT"
/usr/bin/find "$MOUNT" -maxdepth 2 -type f -print
Параметр -nobrowse не позволяет смонтированному тому попасть в стандартный графический интерфейс просмотра, а -readonly защищает тестовые данные от случайного изменения во время проверки. Скрипт использует rmdir, а не rm -rf: если отключение завершится ошибкой, rmdir не перейдёт внутрь всё ещё смонтированного каталога и не удалит его содержимое.
Режим только для чтения подходит не для всех сценариев
Если требуется проверить запись, не переводите общий эталонный образ напрямую в режим записи. Сначала скопируйте образ для текущей задачи, убедитесь, что копия находится в принадлежащем ей временном каталоге, и только затем смонтируйте её. Тогда аварийно завершившаяся задача не изменит эталонные входные данные для следующего запуска.
| Назначение | Рекомендуемый режим | Что проверять |
|---|---|---|
| Проверка установочного пакета или тестовых данных | Монтирование только для чтения | Наличие файлов, права доступа и контрольные суммы |
| Проверка операций записи | Доступная для записи копия конкретной задачи | Результат записи и корректность отключения |
| Сравнение двух образов | Две независимые точки монтирования только для чтения | Уникальность путей и фиксированный порядок сравнения |
Сохраняйте диагностические данные при ошибке отключения
Обычно сбой отключения вызван не случайной ошибкой hdiutil, а процессом, который по-прежнему использует находящийся на томе текущий каталог, открытый файл или рабочий путь. Не скрывайте причину немедленным принудительным отключением — сначала определите, кто использует том:
/usr/sbin/lsof +D "$MOUNT" 2>/dev/null || true
/usr/bin/hdiutil info
/bin/ps -axo pid,ppid,command
Команда lsof +D может медленно работать с большими каталогами, поэтому её следует запускать только в ветке обработки ошибки. В первую очередь проверяйте тестовые процессы, сборщики журналов, средства архивации и оболочки, которые выполнили cd в каталог монтирования и не вышли из него. Обычно проблему устраняют, дождавшись завершения дочерних процессов, закрыв файловые дескрипторы и явно вернувшись в рабочий каталог задачи перед отключением.
Функция очистки также должна сохранять исходный код завершения. Если ошибка отключения заменит настоящую ошибку теста, в конвейере будет виден только сбой очистки, а первоначальная причина потеряется. Надёжнее отдельно сохранять статус тестирования и статус очистки, а затем записывать оба значения в сводку задачи.
Проверяйте оставшиеся тома, а не удаляйте их вслепую
Перед запуском новой задачи узел может проверить каталоги монтирования, созданные по правилам данного конвейера, но нельзя удалять каталог только из-за похожего имени. Сначала убедитесь, что он действительно является точкой монтирования, затем проверьте, выполняется ли связанная с ним задача и относится ли образ к текущему рабочему пространству.
Результаты проверки рекомендуется разделять на три категории:
- Том текущей задачи: штатно отключается обработчиком выхода.
- Том другой активной задачи: регистрируется и пропускается; очистка ресурсов другой задачи запрещена.
- Потенциально оставшийся том без активной задачи: перед отключением необходимо собрать данные
hdiutil info, сведения об использующих его процессах и контекст создания.
Принудительное отключение допустимо только как крайняя мера после ручной проверки. Если автоматический скрипт ориентируется исключительно на возраст каталога, он может прервать длительную, но исправно выполняющуюся задачу. Время каталога также не равно времени монтирования: копирование файлов и чтение метаданных могут изменить показатели, на которых основано такое решение.
Включите обработку образов в критерии приёмки конвейера
Повторно используемый этап работы с образом диска должен как минимум проверять следующее:
- Входной файл существует, а перед монтированием выполнена команда
hdiutil verify. - Каждая задача использует отдельный каталог и не полагается на метку тома образа при формировании пути.
- По умолчанию образ монтируется только для чтения; если нужна запись, создаётся копия для конкретной задачи.
- Обработчик очистки установлен для всех путей завершения, а дочерние процессы также получают сигнал завершения.
- Перед отключением процесс покидает каталог монтирования и ожидает завершения процессов, читающих данные с тома.
- При ошибке отключения сохраняются сведения об устройстве, пути и процессах; рекурсивное удаление не выполняется.
- После завершения задачи подтверждается исчезновение точки монтирования и отсутствие ошибочной очистки других параллельных задач.
На выделенных физических узлах Mac от SDKMac этот подход одинаково применим как для интерактивной диагностики, так и для автоматических конвейеров. Главное — не добавлять всё больше команд очистки, а определить для каждого смонтированного тома владельца, жизненный цикл и диагностические данные на случай сбоя. Только тогда проверка DMG не будет давать трудно воспроизводимые результаты из-за повторного использования машины или параллельного выполнения.
Часто задаваемые вопросы
Почему CI не следует полагаться на стандартное имя тома в /Volumes?
Параллельные задания могут подключить образы с одинаковым именем. macOS добавит номер, и жёстко заданный путь способен незаметно указать на том другого задания.
Можно ли удалить каталог, если hdiutil detach завершился ошибкой?
Нет. Сначала нужно определить удерживающий том процесс и повторить штатное отключение. Рекурсивное удаление смонтированного пути может повредить данные образа.
Следует ли по умолчанию монтировать DMG только для чтения?
Да, если задание проверяет пакеты, тестовые данные или неизменяемые артефакты. Для записи создавайте отдельную копию образа для конкретного задания.
Выберите облачный Mac для непрерывной сборки
Сравните конфигурации, регионы и четыре варианта расчётного периода SDKMac M4 и SDKMac M4 Pro, затем оформите заказ.