По официальной схеме GitLab Runner на macOS запускается как пользовательский LaunchAgent, а не как системный LaunchDaemon. Это означает: статус «онлайн» ещё не доказывает готовность к производственному CI. На этой неделе сначала создайте отдельную обычную учётную запись, затем зарегистрируйте Runner с shell executor, проверьте Xcode без подписи, после этого добавьте ключи и обязательно выполните тест после перезагрузки. (документация GitLab по установке Runner на macOS)
Эта статья для вас, если вы переносите iOS-сборку с ручного Mac в GitLab CI и хотите получить первую воспроизводимую pipeline. DevOps-инженерам важны постоянная работа Runner, права и восстановление. Если у команды нет постоянно доступного Mac, материал поможет проверить требования к удалённому Mac до аренды или покупки оборудования.
01Условия запуска и границы ответственности
Сначала определите, действительно ли проекту нужен macOS GitLab Runner. Такой узел оправдан, когда pipeline вызывает Xcode, Apple SDK, iOS Simulator, xcodebuild или операции подписи. Для обычной компиляции серверного кода, контейнеров и Linux-инструментов macOS добавляет стоимость и не решает проблему изоляции.
До установки зафиксируйте четыре ограничения:
- Среда выполнения.
shellexecutor выполняет команды непосредственно на хосте. Контейнерной границы между заданием и macOS нет. - Пользовательская сессия. Runner должен работать в сеансе вошедшего пользователя. Это особенно важно для Simulator, графических компонентов Xcode и доступа к Keychain.
- Доверие к коду. Задание из доверенного проекта может изменять файлы, устанавливать пакеты и читать доступные процессу секреты. Общий Runner для несвязанных проектов создаёт риск утечки.
- Постоянство хоста. Рабочие каталоги, кэш, DerivedData и логи остаются на статической машине, если вы не очищаете их после задания.
GitLab прямо относит shell executor к режимам с высоким риском для хоста и сети и рекомендует применять его только для доверенных сборок. Пользователь с правом изменять CI-скрипт потенциально может выполнить произвольные команды от имени Runner-пользователя. Поэтому проектный Runner обычно безопаснее общего, а Runner для релизных веток нужно отделять от Runner для внешних вкладов. Рекомендации GitLab по безопасности self-managed Runner подробно описывают эти границы.
Минимальные доказательства готовности
Не считайте установку завершённой, пока не получены все подтверждения:
- реальная сборка проекта завершается с кодом выхода
0; - тестовый job видит тот же Xcode и SDK, которые вы согласовали;
- после выхода из SSH Runner не исчезает из-за неверного способа запуска;
- после перезагрузки Mac Runner снова принимает задание;
- сертификаты и профили не попадают в Git-репозиторий;
- после неудачного job рабочий каталог можно очистить без ручного восстановления всей машины.
Отдельная учётная запись и регистрация
Создайте обычного пользователя, например ci-runner. Не используйте личную административную учётную запись разработчика. У CI-пользователя должна быть собственная домашняя директория, собственный ~/.gitlab-runner/config.toml, отдельный SSH-контекст и отдельная связка ключей.
Администратор понадобится только для установки системных компонентов и выбора активного каталога разработчика. Сами задания должны выполняться от имени обычного пользователя. Это снижает последствия ошибочного скрипта, но не превращает shell executor в изолированную песочницу.
Для Apple Silicon и Intel скачивайте соответствующий официальный бинарный файл. GitLab публикует отдельные варианты для darwin-arm64 и darwin-amd64; не подменяйте архитектуру случайным бинарником из стороннего скрипта. После загрузки проверьте права выполнения:
sudo chmod +x /usr/local/bin/gitlab-runner
/usr/local/bin/gitlab-runner --version
Команда регистрации должна выполняться локально в графическом терминале Mac, а не внутри SSH-сеанса. Официальная инструкция для macOS использует именно такой порядок: установка бинарника, регистрация, установка пользовательского сервиса и запуск.
В панели GitLab создайте Runner на уровне проекта или группы. Для первого внедрения выбирайте проектный Runner: его область действия уже, а ошибки конфигурации легче ограничить. Скопируйте выданный authentication token только во временную переменную оболочки. Современный authentication token имеет префикс glrt-, но сам токен нельзя публиковать в репозитории, логе или задаче поддержки. Документация регистрации Runner описывает текущий процесс и поля регистрации.
Пример интерактивной регистрации:
gitlab-runner register
В ответах укажите:
- URL вашего GitLab-инстанса;
- authentication token;
- описание вроде
ios-macos-arm64-project; - тег
macos; - executor
shell.
Тег нужен не для красоты. В .gitlab-ci.yml он ограничивает выбор узла:
build_ios:
stage: build
tags:
- macos
script:
- xcodebuild -version
- xcodebuild -project App.xcodeproj \
-scheme App \
-configuration Debug \
-sdk iphonesimulator \
build \
CODE_SIGNING_ALLOWED=NO
Если тег в pipeline не совпадает с тегом Runner, job останется в очереди. Если тег слишком общий, задача может попасть на неподходящий Mac.
03Пользовательская сессия и восстановление
Как GitLab Runner работает на удалённом Mac постоянно? Не через Linux-подобный системный демон. Поддерживаемый режим macOS — пользовательский LaunchAgent. Он запускается после входа пользователя и прекращает работу после выхода. Такой режим получает доступ к пользовательскому Keychain и графической сессии, которые нужны для Simulator и подписи.
После регистрации выполните:
cd ~
gitlab-runner install
gitlab-runner start
gitlab-runner status
Официальный установщик создаёт файл:
~/Library/LaunchAgents/gitlab-runner.plist
Конфигурация Runner находится отдельно:
~/.gitlab-runner/config.toml
Это два разных объекта. Изменение config.toml меняет параметры Runner, а plist отвечает за запуск пользовательского процесса. После редактирования остановите и запустите сервис заново, затем проверьте статус.
Почему Runner после перезагрузки оказывается офлайн
Обычно причина не в регистрации. Пользователь не вошёл в графическую сессию, сервис запускался из SSH или путь к логам в plist недоступен. Управление LaunchAgent через SSH может вызвать ошибку Could not find domain for, потому что у SSH-сеанса нет нужного пользовательского launchd-контекста.
Проверьте восстановление в таком порядке:
- Войдите в macOS локально или через доступ к графическому рабочему столу.
- Запустите
gitlab-runner status. - Убедитесь, что пользователь видит
~/Library/LaunchAgents/gitlab-runner.plist. - Выйдите из SSH, не завершая графический вход.
- Запустите безопасный диагностический job.
- Перезагрузите Mac.
- После входа повторите job и сохраните лог.
Автоматический вход после перезагрузки — компромисс, а не универсальный совет. Он поддерживает требуемую пользовательскую сессию, но снижает физическую защиту машины. Для изолированного Mac в контролируемом дата-центре это может быть приемлемо при ограниченном доступе к сети и отдельной учётной записи. Для хоста с несколькими командами, личными данными или доступом к production-секретам лучше предусмотреть ручное восстановление и не хранить релизные ключи на постоянном Runner.
Не переводите Runner в самодельный LaunchDaemon ради запуска «до входа». Официальная документация указывает, что системный режим не поддерживается: он работает от root и не получает пользовательский сеанс, необходимый для iOS Simulator и подписи.
Среда Xcode и первая GitLab CI pipeline
Перед подключением сертификатов проверьте инструменты:
xcode-select -p
xcodebuild -version
xcodebuild -runFirstLaunch
Если активен неправильный каталог разработчика, задайте его явно:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
Путь к приложению Xcode зависит от вашей установки. Поэтому не вшивайте его в pipeline без проверки на самом Runner. Также не устанавливайте зависимости интерактивно во время первого production job. Установите их заранее от имени CI-пользователя и зафиксируйте версии в проекте.
Для Swift Package Manager добавьте Package.resolved в репозиторий. При прямом вызове xcodebuild используйте:
xcodebuild \
-project App.xcodeproj \
-scheme App \
-configuration Debug \
-sdk iphonesimulator \
-disableAutomaticPackageResolution \
build \
CODE_SIGNING_ALLOWED=NO
Apple рекомендует фиксировать разрешённые версии пакетов и отключать автоматическое разрешение в CI, если pipeline должна повторять состояние Package.resolved. Для приватных зависимостей настройте SSH-ключ и known_hosts именно в домашней директории пользователя, который запускает CI. Руководство Apple по сборке Swift-проектов в CI
Начните с минимальной pipeline:
stages:
- environment
- build
- test
variables:
LANG: "en_US.UTF-8"
environment:
stage: environment
tags:
- macos
script:
- whoami
- uname -m
- xcode-select -p
- xcodebuild -version
build_unsigned:
stage: build
tags:
- macos
script:
- xcodebuild -project App.xcodeproj
-scheme App
-configuration Debug
-sdk iphonesimulator
-disableAutomaticPackageResolution
build
CODE_SIGNING_ALLOWED=NO
artifacts:
when: always
paths:
- build_logs/
test:
stage: test
tags:
- macos
script:
- xcodebuild test
-project App.xcodeproj
-scheme App
-destination 'platform=iOS Simulator,name=SIMULATOR_NAME'
CODE_SIGNING_ALLOWED=NO
Синтаксис команды адаптируйте под .xcworkspace, схему и доступный Simulator. Важна последовательность: сначала идентификация среды, затем сборка без подписи, потом тестирование. Только после успешных этапов добавляйте archive.
Варианты размещения Runner
В середине внедрения полезно сравнить не только цену, но и эксплуатационные ограничения. Для GitLab CI на macOS решающими становятся доступ к пользовательской сессии, контроль секретов и возможность быстро восстановить узел.
| Вариант | Сильные стороны | Ограничения | Когда выбирать |
|---|---|---|---|
| Собственный Mac в офисе | Полный физический доступ, удобно подключать устройства | Электропитание, сеть, обновления и ручное восстановление лежат на команде | Есть ответственный за оборудование и стабильный канал |
| Mac в дата-центре | Постоянное размещение, удалённое администрирование | Нужно отдельно проверить графический вход, Keychain и правила доступа | Нужен долгоживущий Runner для доверенного проекта |
| Удалённый Mac в аренду | Не требуется покупать и обслуживать оборудование, можно начать с ограниченного периода | Нужно заранее согласовать root-доступ, учётную запись, сеть и очистку после аренды | Нужно проверить CI-нагрузку или временно увеличить мощность |
| Общий macOS Runner | Быстро подключается к нескольким проектам | Высокий риск пересечения рабочих каталогов и секретов при shell executor |
Только при строгом доверии между проектами и контроле доступа |
Если вы рассматриваете аренду Mac mini для CI-задач, заранее запросите не рекламные характеристики, а технические условия: можно ли создать отдельного пользователя, сохраняется ли GUI-сессия после перезагрузки, доступен ли SSH, кто отвечает за очистку диска и как выполняется возврат узла в исходное состояние.
06Сертификаты, Keychain и релизные задания
Сборка без подписи нужна для проверки проекта. Релизный job требует отдельного контура. Не добавляйте сертификаты в pipeline на первом этапе, пока xcodebuild и тесты не работают.
Разделите задачи:
- Build и unit-тесты. По возможности запускайте с
CODE_SIGNING_ALLOWED=NO. - Архивация. Подключайте подпись только для доверенной ветки.
- Публикация. Разрешайте её отдельному Runner или тому же Runner, но только с protected variables и ограниченным набором веток.
Для подписи нужны сертификат с закрытым ключом, provisioning profile и настройки проекта. Apple указывает, что CODE_SIGN_IDENTITY должна ссылаться на действующий сертификат в доступной связке ключей, а CODE_SIGN_STYLE определяет автоматический или ручной способ получения signing assets. Справочник build settings Apple подтверждает эти параметры.
Практический порядок:
- Создайте отдельный Keychain для CI-пользователя.
- Импортируйте сертификат и закрытый ключ только в эту связку.
- Установите provisioning profile в домашний каталог CI-пользователя.
- Проверьте доступ к Keychain из локального графического сеанса.
- Уберите файлы сертификата из рабочего каталога после импорта.
- Храните секреты GitLab как protected и masked variables.
- Запускайте релиз только из защищённой ветки или тега.
Не разрешайте внешним merge request запускать job с релизными секретами. Fork или изменяемый .gitlab-ci.yml может вывести переменные в лог, отправить их наружу или изменить команды очистки. GitLab рассматривает self-managed Runner как инфраструктуру удалённого выполнения кода, поэтому доверие к проекту и защита переменных должны быть частью архитектуры, а не исправлением после утечки.
Apple отдельно отмечает, что профиль может потребовать регенерации после изменения возможностей приложения или истечения срока действия. Официальная документация по provisioning profiles пригодится при ротации релизных активов.
Как настроить сертификаты и Keychain для удалённого Mac Runner? Сначала привяжите их к отдельному пользователю и отдельной связке ключей, затем ограничьте релизный job protected-ветками. Не передавайте .p12, закрытый ключ или профиль через обычные артефакты и не храните их в репозитории.
Приёмка после первой перезагрузки
Проверка должна проходить на реальном проекте, а не на пустом репозитории. Пустой job покажет только связь с GitLab. Он не покажет проблемы с Xcode, кэшем, Simulator, рабочим каталогом и Keychain.
Составьте журнал приёмки:
- Runner принимает job после обычного входа в macOS;
- job продолжает работать после закрытия SSH;
xcodebuild -versionвозвращает согласованную среду;- unsigned build завершается успешно;
- тесты запускаются на нужном Simulator;
- артефакты и логи доступны в GitLab;
- релизная ветка видит сертификат, а обычная ветка — нет;
- после перезагрузки Runner возвращается в список доступных;
- неудачное задание не оставляет секреты в рабочей директории;
- повторный запуск не ломается из-за старого DerivedData или занятого Simulator.
Для очистки статического хоста включите отдельную процедуру удаления рабочих данных после job. Перед применением проверьте настройки очистки на вашем Runner и проекте: поведение зависит от версии Runner, используемого executor и структуры рабочего каталога.
Решение по итогам первой недели можно принимать так:
- Оставить один Runner, если реальные jobs проходят, восстановление после перезагрузки подтверждено, а очередь не мешает срокам.
- Добавить второй узел, если релизные и тестовые задачи конфликтуют или один Runner регулярно занят.
- Усилить очистку, если растут рабочие каталоги, DerivedData или кэш.
- Отключить автоматическую подпись, если сборки нестабильны из-за истёкших профилей.
- Вернуться к ручной сборке, если у команды нет владельца за обновление Xcode, сертификатов и самого Mac.
Ежедневная ответственность должна быть назначена заранее: кто обновляет Runner, кто проверяет Xcode, кто ротирует сертификаты, кто очищает диск, кто анализирует неудачные pipeline. Без этого macOS CI постепенно превращается в ручной сервер с непредсказуемым состоянием.
08Переход от текущего решения к удалённому Mac
Если сейчас сборка выполняется на личном Mac разработчика, у вас уже есть три слабых места: машина выключается или занята другой работой, Keychain связан с личной учётной записью, а повторить окружение после увольнения или замены ноутбука трудно. Если используется Linux-сервер, отсутствуют Apple SDK, Xcode и полноценный контур подписи. Если куплен отдельный Mac mini только под редкие релизы, он большую часть времени простаивает, но всё равно требует обновлений, электричества и удалённого восстановления.
Удалённый Mac имеет смысл, когда нужен постоянный macOS-узел, но вы не хотите сразу покупать и обслуживать отдельное оборудование. Перед заказом проверьте, что выбранная конфигурация позволяет создать CI-пользователя, сохранить графическую сессию и установить полный инструментальный стек. У VpsMesh можно начать с подходящего срока аренды Mac, а затем сравнить результат с покупкой собственного Mac mini по фактической очереди, частоте сборок и затратам на обслуживание.
Для временного проекта, миграции или проверки нагрузки аренда обычно практичнее покупки простаивающего Mac. Для постоянной тяжёлой нагрузки, подключения физических iOS-устройств или требований к локальному оборудованию собственный Mac всё ещё может быть разумнее. Решение принимайте после прохождения чек-листа приёмки, а не по одному индикатору «Runner online».