Одна кодовая идентичность Apple состоит как минимум из двух связанных частей — сертификата и соответствующего ему закрытого ключа; это прямо следует из официального описания синхронизации подписей Apple. Поэтому файл сертификата, скачанный на удалённый Mac, ещё не означает готовность сборки.

Практический вывод такой: для нового проекта fastlane match можно инициализировать сразу, а действующую производственную подпись сначала следует импортировать и проверить в двух параллельных цепочках. Для CI удалённого Mac базовой схемой должны быть временный macOS Keychain и readonly-синхронизация; права записи, продления и изменения хранилища следует оставить контролируемому процессу администратора.

Кому понадобится этот порядок

Материал предназначен для DevOps-инженеров, которые поддерживают iOS или macOS-пайплайны и хотят убрать зависимость от личного Mac.

Он также пригодится администратору сертификатов, закрытых ключей, профилей и разрешений публикации, а также руководителю разработки, планирующему использовать удалённый Mac как постоянный узел сборки или выпуска.

Карта подписей перед миграцией

До настройки fastlane match нужно составить инвентаризацию. Нельзя начинать с удаления старых сертификатов или с команды, которая меняет состояние всей команды Apple Developer.

Для каждого приложения и Target следует зафиксировать:

  • Bundle Identifier в формате <APP_BUNDLE_ID>;
  • идентификатор команды <TEAM_ID>;
  • тип сборки: Development, Ad Hoc, App Store или macOS Distribution;
  • имя сертификата и наличие соответствующего закрытого ключа;
  • профиль подписи и его назначение;
  • владелец производственного доступа;
  • текущий способ архивации и публикации;
  • резервный путь возврата к старому Mac или старому CI.

Apple разделяет распространение приложения на зарегистрированные устройства и распространение через магазин; описание этих вариантов приведено в официальной документации Apple по профилям и устройствам. Для macOS-пакета требования нужно проверять отдельно по документации Apple о подписании приложения для распространения.

Особое внимание требуется расширениям. Основное приложение может собираться успешно, пока Widget, Share Extension, Notification Extension или Watch App используют другой Bundle Identifier и другой профиль. Поэтому список Target должен включать не только приложение, которое отображается в Xcode первым.

Минимальный файл инвентаризации

APP_BUNDLE_ID=<APP_BUNDLE_ID>
EXTENSION_BUNDLE_ID=<EXTENSION_BUNDLE_ID>
TEAM_ID=<TEAM_ID>
SIGNING_OWNER=<SIGNING_OWNER>
DISTRIBUTION_CHANNEL=<APP_STORE_OR_AD_HOC>
CURRENT_RELEASE_PATH=<OLD_RELEASE_PATH>
ROLLBACK_OWNER=<ROLLBACK_OWNER>

В этот файл нельзя помещать реальные пароли, токены, адреса закрытого хранилища или содержимое сертификатов. Это карта ответственности, а не секретный конфигурационный файл.

Новый проект и единый источник подписей

Для нового приложения fastlane match подходит как первичный источник подписей, если ещё нет действующего производственного процесса, который нельзя прерывать. В этом случае сначала определяются владелец команды Apple Developer, администратор хранилища и лицо, ответственное за расшифровывающий пароль.

Секреты следует разделить по назначению:

  1. доступ к репозиторию или объектному хранилищу подписей;
  2. пароль расшифровки хранилища match;
  3. аутентификация сервисов Apple;
  4. токен публикации;
  5. локальные права удалённого узла.

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

Инициализация без привязки к реальным данным

Команда и идентификаторы должны передаваться через защищённые переменные:

bundle exec fastlane match development \
  --app_identifier "$APP_BUNDLE_ID" \
  --git_url "$MATCH_STORAGE_URL"

Конкретный способ хранения может отличаться: fastlane match официально документирует работу с Git и объектными хранилищами, а также импорт существующих сертификатов в руководстве match. Перед применением параметров следует сверить их с актуальной версией официальной документации, а не переносить старую команду из другого проекта.

Для каждой категории подписи нужно отдельно проверить:

  • Development для локальной или тестовой разработки;
  • Ad Hoc для установки на зарегистрированные устройства;
  • App Store для магазинной поставки;
  • macOS Distribution для соответствующего macOS-приложения.

Критерий завершения — не наличие файлов в каталоге. На чистом удалённом Mac должна пройти минимальная сборка с тем же Bundle Identifier, нужным Target и ожидаемым способом подписи. Если сертификат скачался, но закрытый ключ не импортировался в Keychain, такой результат нельзя считать успешным.

Миграция действующей подписи

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

fastlane match допускает импорт уже существующих сертификатов; соответствующий сценарий описан в официальной документации действия match. Это важнее, чем немедленный сброс подписей: импорт позволяет сохранить действующую идентичность и сначала проверить новую среду.

Рекомендуемый порядок выглядит так:

  1. зафиксировать последний успешный коммит и артефакт;
  2. экспортировать разрешённые резервные материалы по внутренней политике;
  3. сопоставить сертификат с закрытым ключом;
  4. создать отдельную ветку или изолированное хранилище для пробного импорта;
  5. выполнить импорт без изменения производственной цепочки;
  6. проверить Development, тестовый архив и производственный архив;
  7. сравнить подпись и установку с прежним результатом;
  8. только после этого переводить CI на новое хранилище.

Команды, меняющие или удаляющие все сертификаты, нельзя использовать как обычный шаг миграции. Перед потенциальным match nuke, отзывом сертификата или удалением Keychain должны быть письменно определены область воздействия, резервная копия, владелец восстановления и условие отката. Если эти пункты не подтверждены, разрушительное действие откладывается.

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

  • команда подписи;
  • наличие профилей для каждого Target;
  • результат установки на тестовое устройство или в канал распространения;
  • содержимое журнала;
  • возможность повторить архив без ручного вмешательства.

Импорт без отзыва производства

Да, fastlane match можно встроить в миграцию существующих сертификатов без немедленного отзыва производственной подписи, если закрытый ключ действительно доступен, импорт разрешён владельцем команды, а пробная цепочка изолирована от производственного хранилища.

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

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

Временный Keychain для удалённого CI

Удалённый узел отличается от личного Mac тем, что на нём могут меняться пользовательская сессия, состояние после перезагрузки и очередность заданий. Постоянный пользовательский Keychain с производственными ключами создаёт риск случайного чтения секретов соседней задачей.

Именно поэтому в CI применяется setup_ci: официальное действие fastlane предназначено для подготовки CI-среды, включая временный Keychain, после чего match синхронизирует подписи в контролируемое место. Подробности следует сверять с документацией setup_ci.

Общая логика задачи:

lane :build_ci do
  setup_ci
  match(
    type: "appstore",
    app_identifier: ENV["APP_BUNDLE_ID"],
    readonly: true
  )
  build_app(
    scheme: ENV["SCHEME_NAME"]
  )
end

Здесь приведён шаблон с переменными, а не готовая конфигурация для конкретного проекта. Реальные имена схем, идентификаторы и способы аутентификации должны оставаться вне текста конфигурации.

Порядок принципиален: сначала создаётся и подготавливается временный Keychain, затем синхронизируются сертификаты и профили, и только потом запускаются архивирование и публикация. Если сборка стартует первой, она может обратиться к пустому или неподходящему хранилищу и выдать ошибку, не связанную с самим проектом.

Режим readonly и неинтерактивная сборка

readonly следует включать в задачах CI, которые потребляют уже утверждённые подписи: на pull request, в тестовой сборке, при повторном архивировании и в производственном выпуске. Такой режим не должен самовольно создавать или обновлять сертификаты и профили.

Права записи нужны отдельному управляемому процессу:

  • первоначальная инициализация нового проекта;
  • согласованное добавление Target;
  • плановая ротация;
  • восстановление после подтверждённого истечения срока;
  • изменение профиля после изменения возможностей приложения.

Данные процессы не следует смешивать с каждым обычным запуском CI. В документации fastlane по интеграции с CI проверяется общая модель неинтерактивного запуска и переменных окружения.

Во время приёмки нужно искусственно проверить отсутствие графического вмешательства:

  • задача не должна ждать диалог Keychain;
  • не должна требовать ручного ввода двухфакторного подтверждения;
  • не должна зависеть от открытого рабочего стола;
  • после перезагрузки узла должна завершаться тем же способом;
  • журнал должен сохраняться для последующего анализа.

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

Несколько приложений и команд

В одном хранилище можно централизовать сертификаты, но это не означает, что все профили и все команды следует смешивать. Сертификат может использоваться несколькими приложениями одной команды в пределах разрешённой модели, тогда как provisioning profile обычно привязан к конкретному Bundle Identifier и набору возможностей.

Разделение требуется в следующих случаях:

  • разные Apple-команды;
  • разные владельцы производственного доступа;
  • разные договорённости о сроке жизни и ротации;
  • отдельные требования безопасности;
  • невозможность дать одной CI-роли доступ ко всем приложениям.

Конфигурация должна использовать только placeholders:

TEAM_ID=<TEAM_ID>
APP_BUNDLE_ID=<APP_BUNDLE_ID>
MATCH_BRANCH=<TEAM_OR_PRODUCT_BRANCH>
MATCH_STORAGE_URL=<CONTROLLED_STORAGE_URL>

Настройки команды fastlane Appfile следует проверять по официальной документации Appfile, особенно если один рабочий узел обслуживает несколько приложений.

Для каждой группы необходимо отдельно доказать:

  • основной Target видит правильный профиль;
  • расширения используют свои профили;
  • профиль для Watch App или Widget не подменяется профилем основного приложения;
  • переменная команды не наследуется соседним заданием;
  • ветка или хранилище не открывает доступ другой команде.

При параллельной работе нужно запускать два задания с разными Bundle Identifier и проверять, что сертификаты, профили, временные Keychain и рабочие каталоги не смешиваются. Последовательный запуск теми же пользователями выявляет другую проблему — остаточные файлы и переменные после завершения предыдущей задачи.

Пять этапов технической приёмки

Ниже приведён порядок, который можно превратить в обязательный лист допуска.

Этап 1. Проверка активов

  • [ ] У каждого Target указан Bundle Identifier.
  • [ ] Для каждого сертификата найден соответствующий закрытый ключ.
  • [ ] Определён владелец производственной подписи.
  • [ ] Зафиксирован последний рабочий архив.
  • [ ] Есть понятный маршрут отката.

Этап 2. Проверка изолированного импорта

  • [ ] Импорт выполнен не в единственное производственное хранилище.
  • [ ] Секреты переданы через защищённые переменные.
  • [ ] Реальные пароли и токены не попали в репозиторий.
  • [ ] Проверены приложение и дополнительные Target.
  • [ ] Старая цепочка всё ещё доступна.

Этап 3. Проверка удалённого узла

  • [ ] Создаётся временный Keychain.
  • [ ] Синхронизация выполняется до сборки.
  • [ ] Обычная задача использует readonly.
  • [ ] После перезагрузки поведение проверено.
  • [ ] Журнал доступен без интерактивной сессии.

Этап 4. Проверка результата Xcode

  • [ ] Архив создаётся на том же коммите, что и контрольный архив.
  • [ ] Команда подписи соответствует ожидаемому типу распространения.
  • [ ] Профиль найден для каждого Target.
  • [ ] Архив проходит проверку подписи.
  • [ ] Приложение устанавливается или передаётся в согласованный канал.

Этап 5. Проверка отказа

  • [ ] Временно недоступно хранилище подписей.
  • [ ] Отсутствует закрытый ключ.
  • [ ] Узел перезагружен между синхронизацией и сборкой.
  • [ ] Одновременно запущены изолированные задания.
  • [ ] Проверено, что ошибка не приводит к отзыву действующей подписи.

Сценарии миграции и итоговое решение

Сценарий Состояние подписей Режим fastlane match Условие допуска
Новый проект Производственной цепочки ещё нет Первичная запись, затем readonly в CI Минимальная сборка на чистом удалённом Mac
Существующий релиз Сертификат и закрытый ключ доступны Импорт в изолированную ветку или хранилище Два успешных архива и сохранённый откат
Несколько приложений одной команды Общая команда, разные Bundle Identifier Общие разрешённые сертификаты, отдельные профили Проверены расширения и дополнительные Target
Несколько команд Разные владельцы и полномочия Отдельные ветки или хранилища Ни одна роль не читает чужие подписи
CI без оператора Подписи утверждены заранее Временный Keychain и readonly Нет диалогов, ручного подтверждения и остаточных секретов
Истёкший профиль или сертификат Производство зависит от обновления Запланированная управляемая запись Есть резервный путь и окно отката
Хранилище недоступно Новая сборка не может синхронизироваться Сбой с сохранением журнала Старый релизный путь не уничтожен

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

Текущая машина и удалённый Mac

Личная машина удобна для ручного выпуска, но как постоянный CI-узел она создаёт несколько скрытых ограничений: доступ к ключам часто зависит от конкретного пользователя, состояние рабочего Keychain переживает задачи без прозрачной очистки, а перезагрузка или смена сессии могут остановить публикацию. Кроме того, локальный Mac не всегда доступен команде и не даёт предсказуемой точки восстановления.

Покупка отдельного Mac mini устраняет зависимость от личного устройства, но требует единовременных затрат, физической доставки, самостоятельной замены оборудования, контроля питания и отдельной процедуры удалённого восстановления. Виртуальный macOS-узел может добавить ограничения по аппаратным возможностям, совместимости и доступу к реальному Apple Silicon, что особенно заметно при проверке Xcode и подписания.

Если после инвентаризации нужен временный или постоянный удалённый узел с полными правами администратора, можно рассмотреть аренду удалённого Mac в RUVCLOUD. Такой вариант стоит проверять не рекламным обещанием, а теми же критериями: изоляция, перезагрузка, SSH-доступ, восстановление Keychain, доступность журналов и возможность безопасно откатить публикацию. Общие варианты тарифов аренды Mac имеет смысл оценивать только после определения нагрузки и срока использования.

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

Главный критерий здесь не наличие удалённого компьютера, а управляемость секретов: если CI не может объяснить, где создан временный Keychain, кто имеет право записи, как очищаются рабочие файлы и каким способом возвращается старый релизный путь, миграцию следует отложить.