Почему цепочка Apple привязана к железу macOS
В отличие от Android, где Gradle работает в Linux-контейнере, цепочка доставки iOS и macOS —
xcodebuild, codesign, notarytool, App Store Connect API —
требует ОС и чипа с подписью Apple. Это не вопрос «поставить эмулятор и обойти»:
цепочка сертификатов Archive и App Store, оптимизации компилятора Swift под Apple Silicon —
всё предполагает, что под вами настоящий Mac.
Поэтому каждая команда с iOS-продуктом рано или поздно отвечает на один вопрос: Кто будет долгосрочно предоставлять этот Mac? Три типичных пути — macOS Runner GitHub, свой Mac mini в офисе, выделенная физическая машина в облаке — нет абсолютного лучшего; разница в терпимости к очередям, частоте сборок и наличии человека для обслуживания системы и сертификатов. Статья о технической реализации третьего пути, но сначала — границы двух других.
Железо: Mac mini M4 · 10-ядерный CPU · 16 GB унифицированной памяти · 256 GB NVMe · 1 Gbps выделенный канал (узел ZovCloud в Японии).
Система: macOS 15 Sequoia, Xcode 16.4. Пример: среднее SwiftUI-приложение (~118 тыс. строк, 3 Extension target).
CI: GitHub Actions self-hosted runner 2.323.0; Jenkins 2.479 LTS + macOS agent.
Подпись: сертификат Apple Distribution + App Store Connect API Key (Issuer ID + Key ID + .p8).
Разбор времени CI: ожидание, сборка, подпись
Многие жалуются, что «CI медленный», не разделяя общее время на три части: ожидание машины, локальная сборка и линковка,
подпись и загрузка. На одном commit сравнили Runner macos-14 GitHub
с выделенным узлом M4 ZovCloud — получили показательные цифры.
Управляемый Runner в пик (UTC 13:00–17:00) в среднем ждал 22 минуты до старта job;
сам xcodebuild archive занял 5 мин 38 с. На выделенном M4 без очереди
полная сборка Archive — 3 мин 58 с; параллельная компиляция Swift на 10-ядерном M4 быстрее старых Intel CI,
16 GB унифицированной памяти при clean build не вызвали swap.
| Путь сборки | Типичная месячная стоимость | Очередь / параллелизм | Более подходящий сценарий |
|---|---|---|---|
| Управляемый macOS Runner GitHub | Поминутная оплата (от ~$0.08/мин) | Общий пул, заметные очереди в пик | < 500 минут сборок в месяц, приемлемое ожидание |
| Свой Mac mini в серверной | Разовая покупка железа + электричество и обслуживание | Эксклюзивно, обновления системы — на вас | Постоянный офис, частые сборки круглый год |
| Выделенный Mac в облаке (по дням) | ZovCloud от $19.8/день, без контракта | Выделенная физическая машина, доступ сразу после оплаты | Малые команды, сборки в неделю релиза, удалённая работа |
Если боль — «полчаса после push, чтобы узнать, прошла ли сборка», узкое место обычно в очереди, а не в Xcode. Закрепить Runner на всегда онлайн выделенном Mac — самый прямой способ сократить цикл обратной связи — с раздела 3 — как подготовить эту машину как воспроизводимую базу сборки.
Воспроизводимая база сборки Xcode на Mac в облаке
Mac mini ZovCloud поставляется с полным macOS и правами администратора. После активации войдите по SSH или браузерному VNC; рекомендуем фиксированную структуру каталогов для сертификатов и профилей, чтобы скрипты Runner не искали пути по-разному. Четыре шага — наш стандартный чеклист инициализации каждого нового узла.
-
01
Установить Xcode и принять лицензию
Установите Xcode 16.x из App Store, выполните
sudo xcodebuild -license acceptиxcodebuild -runFirstLaunch. Проверка:xcodebuild -version— ожидаемая версия;xcode-select -pуказывает на/Applications/Xcode.app/Contents/Developer. -
02
Импорт сертификата Distribution и профилей
Передайте .p12 по защищённому каналу в
~/certs/, импортируйте черезsecurity importв отдельную связку~/Library/Keychains/ci.keychain-db; .mobileprovision — в~/Library/MobileDevice/Provisioning Profiles/. Настройте доверие partition list дляcodesign, чтобы не было диалогов при unattended-сборке. -
03
Настройка App Store Connect API Key
Создайте API Key в Apple Developer, сохраните
AuthKey_XXXXXX.p8в~/private_keys/. Для TestFlight —xcrun altoolили Fastlanepilot upload, без интерактивной 2FA Apple ID. -
04
Первый полный Archive и сохранение DerivedData
Клонируйте репозиторий и локально пройдите Release Archive, убедитесь в цепочке подписи. 256 GB системного диска достаточно для среднего проекта; DerivedData ускоряет инкрементальные сборки на ~30–45%. При многих Swift Package укажите
-clonedSourcePackagesDirPathдля кэша.
В ~/.zprofile или скрипте запуска Runner экспортируйте единообразно
KEYCHAIN_PATH, P8_KEY_PATH, DEVELOPER_DIR,
чтобы GitHub Actions и Jenkins использовали одни пути и не было «локально собирается, в CI нет сертификата».
Регистрация self-hosted Runner GitHub Actions и маршрутизация workflow
После регистрации self-hosted Runner workflow по label направляет job на этот Mac в облаке,
минуя общий пул. Путь: Settings репозитория → Actions → Runners → New self-hosted runner,
выберите macOS ARM64, скачайте пакет actions-runner и выполните:
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token RUNNER_TOKEN --labels macos-m4,zovcloud,ios-build --unattended
После регистрации установите как системный сервис, чтобы Runner поднимался после перезагрузки:
sudo ./svc.sh install → sudo ./svc.sh start.
В YAML workflow укажите label:
runs-on: [self-hosted, macos-m4]
Типичная цепочка iOS job: checkout → разблокировка CI keychain → xcodebuild archive →
xcodebuild -exportArchive → Fastlane upload_to_testflight или altool --upload-app.
На узле M4 от push до обработки TestFlight (с очередью Apple) в среднем ~10 минут,
из них локальная сборка и загрузка — 5–6 минут.
Runner видит исходники и ключи подписи — ограничьте права collaborators, регулярно меняйте registration token, не запускайте workflow с секретами автоматически на PR из fork. При нескольких проектах на одном узле — отдельные Runner на репозиторий или песочница OpenClaw для ограничения доступа к файловой системе.
Подключение Jenkins Agent и ключевые моменты pipeline
Если у команды уже есть контроллер Jenkins (может на Linux), сборки macOS идут через Agent.
На Mac в облаке установите JDK 17, скачайте agent.jar, запустите как LaunchDaemon,
контроллер отдаёт задачи по SSH или JNLP.
Сила Jenkins — визуальные pipeline и плагины: Credentials Binding для пароля keychain,
AnsiColor для логов, архив артефактов в Artifactory и т.д.
Типичный Pipeline вызывает sh 'xcodebuild ...' в stage('Archive'),
Fastlane в stage('Upload').
По сравнению с GitHub Actions Jenkins лучше для многоветочных, многосредовых процессов с ручным approval внутри компании.
Mac в облаке по дням — «эластичный Agent»: на неделю релиза подключите Jenkins,
в спокойный период освободите — не нужно круглый год обслуживать Mac в серверной.
Фиксированный DEVELOPER_DIR против путаницы версий Xcode — часто упускаемая деталь стабильности Jenkins.
Замкнутый цикл Archive → Export → TestFlight из командной строки
И в GitHub Actions, и в Jenkins цепочка одна: Archive → .xcarchive → Export → .ipa → загрузка в App Store Connect. Командная строка — стандарт CI, без GUI Xcode.
Пример Archive (Release, указанный scheme):
xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -archivePath build/MyApp.xcarchive CODE_SIGN_STYLE=Manual PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore"
Export с ExportOptions.plist (method app-store):
xcodebuild -exportArchive -archivePath build/MyApp.xcarchive -exportPath build/export -exportOptionsPlist ExportOptions.plist
Загрузка в TestFlight (API Key, для unattended):
xcrun altool --upload-app -f build/export/MyApp.ipa -t ios --apiKey KEY_ID --apiIssuer ISSUER_ID
10-ядерный CPU M4 заметно ускоряет параллельную компиляцию Swift против старых Intel CI;
при многих Swift Package кэшируйте в workflow
~/Library/Developer/Xcode/DerivedData и каталог SourcePackages,
со второй сборки время сокращается примерно на треть.
Keychain и подпись в unattended-среде
Сбой codesign по SSH или на headless Runner почти всегда из-за keychain, а не просроченного сертификата.
В начале скрипта сборки разблокируйте и авторизуйте:
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db
Пароль через GitHub Secrets или Jenkins Credentials — никогда в открытом виде в репозитории.
При errSecInternalComponent проверьте default keychain и доверие codesign в partition list.
Если altool зависает на аутентификации — сверьте Issuer ID API Key с именем .p8 и права «Developer» у ключа.
Частая ловушка — несовпадение Provisioning Profile и Bundle ID: на Archive не падает,
на Export — да. Добавьте в CI security cms -D -i profile.mobileprovision для UUID
и сверьте с PROVISIONING_PROFILE_SPECIFIER в проекте Xcode.
Эластичная мощность macOS: когда вынести Runner на выделенный узел в облаке
Инди и малые команды в дилемме: нужны сборки macOS, но не хочется арендовать офис, тянуть линию и решать отключения и обновления ради одной CI-машины. Linux VM в публичном облаке не подходит (нет полного macOS и цепочки Apple); Mac mini дома — нестабильный uplink, смена IP, случайное выключение.
ZovCloud даёт выделенный физический Mac mini M4: без виртуализации и перепродажи, 16 GB памяти и 1 Gbps выделенный канал на машину, активация за 1–5 минут после оплаты. Пять узлов — Сингапур, Япония, Корея, Гонконг, восток США — по географии пользователей; для App на японский рынок — Токио, меньше задержка при загрузке в TestFlight.
От $19.8/день, $53.5/неделю, $99.1/месяц, без долгого контракта. Неделя плотных релизов — узел и Runner; в спокойный период — освободить, часто выгоднее года своего железа и электричества. Для параллельного Archive на нескольких машинах — кластер Thunderbolt 5 80 Gbps, для крупного monorepo или матрицы App.
-
01
Выбрать узел и активировать в ZovCloud
В консоли выберите регион и срок — после оплаты SSH и VNC выдаются автоматически. Подключение — в центре помощи.
-
02
По разделу 3 — база Xcode и подписи
Пути keychain и API Key — в переменные окружения для GitHub Actions / Jenkins.
-
03
Зарегистрировать Runner и прогнать первый pipeline
Сначала Debug для проверки сборки, затем Release Archive + TestFlight, закрепите Fastlane lane в репозитории.
| Тип команды | Рекомендация | Роль Mac в облаке |
|---|---|---|
| Инди, 1–2 релиза в месяц | Аренда по дням в день релиза + ручной Archive | Временная сборочная машина |
| 5–15 человек, много push в день | Постоянный self-hosted Runner | Помесячная аренда, без очереди |
| Есть Jenkins, нет macOS Agent | Mac в облаке как эластичный Agent | Пиковая мощность без нового железа |
| Матрица приложений + ночные пакетные сборки | Кластер TB5 | Параллельная Archive на нескольких M4 |
Планка iOS CI/CD — не меню Xcode, а стабильная предсказуемая мощность macOS плюс одна доверенная среда подписи. Публичные Runner подходят для редких сборок; своя стойка — для команд с ops-ресурсами; выделенный Mac в облаке закрывает разрыв между «не хочу очередей» и «не хочу покупать железо». Перенесите компиляцию в облако — MacBook сосредоточится на коде, а не на вентиляторах и памяти, украденной ночным CI.
Дайте iOS pipeline macOS-сборщик без очередей
Выделенные узлы ZovCloud Mac mini M4: полный macOS и Xcode, 16 GB унифицированной памяти, доступ SSH / VNC, готовность к Runner GitHub Actions и Jenkins, от $19.8/день.