Акция

Apple Silicon, облачный Mac: iOS/macOS CI — codesign, нотаризация, stapler и границы связки ключей: воспроизводимый конвейер и таблица типичных отказов

CI Развёртывание и устранение неполадок
2026-05-07 Около 8 мин чтения

Сборки iOS и macOS на Apple Silicon в облачном Mac редко упираются только в флаги Xcode: критично, где лежат секреты — отдельная связка ключей только для CI, предсказуемые входы codesign, отправка через notarytool, затем stapler на том артефакте, который реально получает пользователь. Ниже — жёсткая граница между интерактивной машиной разработчика и безконсольным runner’ом, минимальный воспроизводимый порядок шагов и компактная таблица симптом — вероятная причина — действие для типичных сбоев подписи и нотаризации.

Ключевые выводы

  1. Создайте отдельный файл связки ключей для CI, импортируйте туда сертификаты дистрибуции и разблокируйте паролем из хранилища секретов — не полагайтесь на связку входа по умолчанию после перезагрузок образа.
  2. Подписывайте с явным --keychain / CODE_SIGN_KEYCHAIN и проверяйте security find-identity -v -p codesigning в том же контексте shell, что и сборка.
  3. После xcodebuild (archive/export) выполняйте notarytool submit → ожидание → stapler staple на внешнем бандле или образе диска, который скачивают пользователи; подшивка только внутреннего бинарника — частая ошибка.
  4. Размер runner’а и поведение очередей пересекаются с выбором hosted и выделенного Mac — для параллельных дорожек полезно сопоставить с матрицей решений Bitrise: облако iOS против собственного облачного Mac (на английском).
Рабочее место разработчика: ноутбук и код, иллюстрация облачного Mac CI и подписи
Иллюстрация; в проде сверяйте реальным codesign -dv, фрагментам журнала нотаризации и наличию билета на отгружаемом артефакте.

1. Границы связки ключей на безконсольных хостах

У облачного CI редко есть кто-то у консоли, поэтому всё, что предполагает одобрение в UI или дефолты связки сессии входа, начнёт «плавать» после обновления образа. Стандартизируйте файловую связку (например ~/Library/Keychains/ci-signing.keychain-db), импортируйте сертификат Apple Distribution и закрытый ключ, задайте известный пароль и в начале job выполняйте security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH". Пусть KEYCHAIN_PATH будет единственным источником истины и передайте его в xcodebuild через OTHER_CODE_SIGN_FLAGS=--keychain "$KEYCHAIN_PATH" или эквивалент в проекте. Не смешивайте Developer ID и Apple Distribution в одной цепочке без явной настройки команды и профиля в ExportOptions.plist. Для сетевого контура runner’а (регион, MTU, DNS) см. также WireGuard и сопряжение со шлюзом при трансграничном удалённом доступе: устранение неполадок MTU, асимметричной маршрутизации, разделения DNS и наблюдение за задержками (регион и конфигурация облачного Mac).

2. Слои codesign: entitlements, hardened runtime и вложенные бандлы

Воспроизводимая подпись означает одинаковые хеши списка entitlements и профиля подготовки на каждой сборке. После export запустите codesign --verify --deep --strict на .app и для хелперов в PlugIns/Frameworks убедитесь, что team ID и hardened runtime согласованы. Расхождения часто всплывают как errSecInternalComponent или errSecMissingEntitlement уже на этапе productbuild или notarytool, когда основное приложение казалось подписанным. Для .pkg подписывайте снизу вверх, затем пакет — сертификатом установщика из вашего runbook.

3. Нотаризация и stapler: строгий порядок

Используйте notarytool с API-ключом App Store Connect (issuer, key ID, путь к .p8) вне репозитория — предпочтительно пути из переменных окружения, без коммита ключей; поток только через Transporter оставьте для исключительных случаев. Отправляйте ровно тот бинарник, который оценит Gatekeeper — zip, .app или подготовленный .dmg, дождитесь Accepted, затем xcrun stapler staple <path> и stapler validate. Если подшить до успеха Apple или только внутренний .app, а наружу отдать перепакованный архив, у клиента другие хеши, чем у сервиса нотаризации. Ведите ротацию API-ключей и идентификаторы отправок в changelog; при отказе снимайте JSON через notarytool log <submission-id> и прикладывайте первый rule ID к тикету.

4. Минимальный чеклист конвейера

  1. Разблокировать связку CI; проверить личности через security find-identity.
  2. Чистить DerivedData при диагностике устаревших профилей; иначе — инкрементальные сборки с зафиксированными версиями зависимостей.
  3. Archive/export с зафиксированным ExportOptions.plist; выгрузку dSYM отделить от нотаризации.
  4. Отправка в Apple; опрос до Accepted; staple; validate; опубликовать контрольные суммы рядом с артефактами.

Контейнеризованные помощники на том же хосте всё равно требуют корректных монтирований томов и запаса по диску — сочетайте чеклист с Производственный Docker на облачном Mac Apple Silicon: образы arm64/amd64, bind mount и кеш сборки — руководство по устранению неполадок (с границами ресурсов тарифов), если Docker и Xcode делят одну машину.

5. Симптом — причина — действие (подпись и нотаризация)

Симптом / фрагмент лога Вероятная причина Предпочтительное действие
errSecInternalComponent при подписи Закрытый ключ недоступен в связке CI; ACL или пароль Переимпорт ключа и сертификата; unlock в job; явный --keychain
No signing certificate found Неверный порядок поиска связок или нет профиля Задать CODE_SIGN_KEYCHAIN; проверить UUID в ~/Library/MobileDevice/Provisioning Profiles
Нотаризация Invalid с hardened runtime Entitlement не разрешён для дистрибуции; неподписанный вложенный хелпер Сверить entitlements с документацией Apple; deep-verify вложенных бандлов
Gatekeeper карантинит после «успеха» в CI Нет stapler на отгружаемом файле; перепакованный zip ломает билет Staple на внешний артефакт; validate; не перепаковывать после staple
Ошибки авторизации notarytool Неверный issuer / key ID; сдвиг времени; отозванный ключ Ротация ключа; NTP; JSON-ключ вне репозитория

6. Заключение

Считайте подпись, нотаризацию и stapler одним автоматом состояний: выход каждого шага — вход следующего. Зафиксируйте в одном runbook путь к связке, UUID профилей и submission ID нотаризации, чтобы дежурный не гадал, какая личность была активна. После стабилизации прогоните холодный старт на свежем образе runner’а — проверка переживания перезагрузок и смены учётных данных.

Почему облачный Mac mini уместен для CI с тяжёлой подписью

Apple Silicon даёт нативные arm64-цепочки без сюрпризов кросс-эмуляции, а unified memory держит Xcode, симуляторы и лёгкие агенты в одном предсказуемом пуле. macOS сочетает Gatekeeper, SIP и защиту уровня FileVault — ниже риск подмены, чем у разрозненных Windows-VM для сборки. У выделенного облачного Mac нет конкуренции за I/O и очередь подписи с соседями по SaaS, а компактный Mac mini на M-серии держит низкое энергопотребление в простое — разумный TCO для круглосуточных runner’ов.

Если нужна стабильная нативная платформа Apple для воспроизводимых конвейеров подписи, облачный Mac mini M4 от kvmboot — практичная отправная точкатарифы и цены; подберите тариф под размер связки ключей и параллельные lane’ы.