Mac доступен по SSH после перезагрузки, но подписывающая сборка остаётся офлайн.
Самое быстрое решение — не запускать весь CI Agent как LaunchDaemon: системный слой должен выполнять проверки и восстановление, а пользовательский LaunchAgent выделенной учётной записи — запускать сборку с Keychain и Simulator. Именно такую двухслойную схему следует закладывать в корпоративную эксплуатацию.
Кому нужен этот разбор
Материал предназначен для IT-руководителей, отвечающих за безнадзорную работу Mac-сборочных узлов и восстановление CI после перезапуска. Он также полезен руководителям платформенных команд, которым необходимо разделить права для подписи, тестирования Simulator и управления очередью.
Если организация закупает удалённые Mac-ресурсы, здесь можно найти критерии проверки: есть ли отдельная пользовательская сессия, доступен ли удалённый перезапуск и подтверждается ли восстановление реальной сборкой, а не только ответом SSH.
Сначала разделите пять состояний узла
В эксплуатации часто смешивают понятия «Mac запущен» и «Mac готов собирать». Для Mac-сборочной машины это разные контрольные точки:
- система загрузилась;
- загрузочный том разблокирован;
- сервисная учётная запись вошла в пользовательскую сессию;
- CI Agent зарегистрировался и принимает задания;
- подписанная сборка успешно завершилась.
Apple описывает LaunchDaemon как сервис системного контекста, а LaunchAgent — как процесс, связанный с пользовательской сессией. Это различие следует считать архитектурной границей, а не формальностью конфигурации. Подробное описание жизненного цикла служб приведено в документации Apple по созданию заданий launchd.
| Критерий | LaunchDaemon | Глобальный LaunchAgent | LaunchAgent сервисной учётной записи |
|---|---|---|---|
| Контекст запуска | Системный | Пользовательский, применяемый широко | Конкретная пользовательская сессия |
| Запуск до входа пользователя | Подходит | Ограниченно применим по задаче | Не является гарантией готовности |
| Доступ к пользовательскому Keychain | Не следует предполагать | Зависит от сессии и прав | Наиболее естественный контекст |
| Подходит для Simulator и GUI-зависимостей | Обычно нет | Только после отдельной проверки | Да, если агент официально поддерживает такой режим |
| Основная роль в CI | Проверки, мониторинг, восстановление | Специальные общие пользовательские задачи | Управляемая сборка и регистрация CI Agent |
| Главный риск | Слишком широкие права или отсутствие пользовательского контекста | Ошибка выбора учётной записи | Зависимость от входа и политики разблокировки |
Отдельно необходимо проверить режим конкретного агента. Например, официальная документация одного из распространённых CI-инструментов для macOS описывает установку в пользовательском режиме LaunchAgent; это нельзя автоматически переносить на любой другой агент или версию macOS. Инструкция по установке macOS Runner должна использоваться именно как проверка поддерживаемого режима, а не как универсальное правило для всех платформ.
Первая проверка: какие зависимости принадлежат пользователю
Выбор launchd определяется не названием продукта, а тем, что происходит внутри задания. Подписывающая сборка обычно обращается к сертификату, закрытому ключу, временным связкам Keychain, рабочему каталогу пользователя и инструментам, которые рассчитывают на пользовательский графический контекст.
Keychain и кодовая подпись
Если процесс запущен от root или системной учётной записи, это ещё не даёт ему права использовать Keychain разработчика. Нужно установить:
- в каком контексте создана связка;
- кто владеет сертификатом и закрытым ключом;
- разблокируется ли связка после входа;
- какие ACL и разрешения применяются к инструментам подписи;
- удаляются ли временные ключи после задания.
Apple отдельно рассматривает особенности Keychain на современных системах, поэтому перенос сертификатов в другое хранилище без проверки модели доступа является плохой практикой. При проектировании следует опираться на техническую записку Apple о Keychain на Mac, а не на то, что процесс успешно стартует в терминале администратора.
Для проверки контекста сервисная команда может использовать минимальный диагностический блок:
id
printf 'HOME=%s\n' "$HOME"
launchctl print gui/$(id -u)
security list-keychains
security find-identity -v -p codesigning
Этот вывод не доказывает успех сборки. Он только показывает, от имени кого работает процесс, какую пользовательскую область он видит и обнаруживаются ли подходящие идентификаторы подписи. Дополнительным доказательством должен быть результат настоящего задания CI.
Simulator, окна и каталоги
Simulator и некоторые инструменты тестирования могут зависеть от пользовательского графического сервиса, временных файлов и домашнего каталога. Поэтому переход от ручного запуска в терминале к launchd следует проверять отдельно: одинаковая команда может работать в интерактивной сессии и завершаться ошибкой в фоне.
Та же граница относится к рабочему каталогу. Если агент создаёт checkout в /Users/имя/…, запуск от другой учётной записи изменит права на файлы, расположение кэша и доступ к сохранённым настройкам. Системный сервис не должен получать полный доступ к пользовательской области только ради того, чтобы «сборка не падала».
Вторая проверка: восстановление важнее автозапуска
Параметр KeepAlive может перезапускать процесс, но он не превращает сломанный контекст в рабочий. Если агент стартует до входа пользователя, не видит Keychain и завершается, постоянный перезапуск будет только маскировать причину сбоя.
В конфигурации следует оставить лишь минимальные поля, необходимые для понимания владельца, команды и журнала:
<key>Label</key>
<string>com.company.ci-agent</string>
<key>ProgramArguments</key>
<array>
<string>/Users/ci/bin/agent</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/ci/var/agent.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/ci/var/agent.err.log</string>
Фактические пути, имя учётной записи и способ регистрации должны соответствовать официальной документации выбранного CI Agent. Не следует копировать пример без проверки того, кто загружает plist, в каком домене он размещён и откуда запускается команда.
Что именно проверять после разных сбоев
Холодный запуск показывает, может ли узел пройти полный путь от питания до агента. Плановая перезагрузка проверяет обычный эксплуатационный сценарий. Неожиданное отключение питания выявляет повреждённые рабочие каталоги, задержку сети и проблемы с повторной регистрацией.
Выход сервисного пользователя особенно важен: процесс может исчезнуть вместе с сессией, даже если после загрузки системы SSH отвечает. При включённом FileVault добавляется ещё одна граница — том может оставаться заблокированным. В руководстве Apple по FileVault описывается необходимость учитывать разблокировку диска как отдельную часть восстановления, а не считать доступность питания достаточной.
Третья проверка: минимальные права и изоляция секретов
Для предприятия опасен не только простой. Если LaunchDaemon работает с широкими правами и одновременно получает доступ к исходному коду, сетевым ресурсам и ключам подписи, одна ошибка в агенте увеличивает радиус ущерба.
Разделение обязанностей следует оформить так:
- системный слой проверяет доступность диска, сети, локального процесса и журналов;
- системный слой может инициировать безопасное восстановительное действие без чтения сертификатов;
- пользовательский слой запускает CI Agent от выделенной сервисной учётной записи;
- ключи подписи доступны только там, где реально выполняется подписывающая команда;
- интерактивный администраторский аккаунт не используется как общий исполнитель сборок.
Apple описывает защитные свойства платформы и границы доступа к данным в документации по безопасности macOS. Для самой подписи полезно сверять поток создания distribution-signed кода с официальным руководством Apple по подписанному коду.
Важное ограничение: успешное выполнение security find-identity не означает, что производственная подпись разрешена. Доступность идентификатора, разблокировка Keychain и авторизация инструмента — разные проверки, поэтому их нужно фиксировать раздельно.
Четвёртая проверка: логи должны объяснять причину отказа
Для каждого агента необходимо сохранять:
- домен launchd, в котором он загружен;
- имя plist и фактическую команду;
- владельца процесса;
- пути стандартного вывода и ошибок;
- код завершения;
- время последнего запуска;
- число повторных запусков;
- версию агента, Xcode и конфигурации после изменения.
Базовая проверка состояния может выглядеть так:
launchctl print gui/$(id -u)/com.company.ci-agent
ps -axo user,pid,ppid,command | grep '[a]gent'
tail -n 100 "$HOME/var/agent.err.log"
Если команда launchctl print не находит сервис, проблема относится к загрузке или домену. Если сервис найден, но процесс завершается, нужно изучать код выхода и окружение. Если агент онлайн, но сборка не подписывается, расследование перемещается к Keychain, сертификату, разрешениям и рабочему каталогу.
Обновление macOS, CI Agent, Xcode или plist нельзя проводить сразу на единственном производственном узле. Сначала изменения проверяются на изолированном узле, затем выполняются холодный запуск и настоящая тестовая сборка. Особенно опасно считать зелёным результатом постоянный статус KeepAlive: он способен скрыть непрерывный цикл падений.
Сравнение архитектур перед допуском в производство
| Архитектура | Когда допустима | Что она не решает | Причина отклонения |
|---|---|---|---|
| Только LaunchAgent | Агент официально поддерживает пользовательский режим, а вход и разблокировка контролируются | Системный мониторинг до входа и восстановление хоста | Нет отдельного слоя ранней диагностики |
| Только LaunchDaemon | Агент полностью системный и не использует пользовательский Keychain, Simulator или каталог | Пользовательскую подпись и графические зависимости | Слишком слабая гарантия пользовательского контекста |
| LaunchDaemon + LaunchAgent | Производственный CI использует пользовательские ресурсы, а узел должен восстанавливаться без оператора | Не отменяет требований FileVault и резервирования | Требует точной настройки ролей и журналирования |
Для большинства корпоративных iOS-сценариев базовым вариантом является двухслойная схема. LaunchDaemon не должен читать публикационные ключи или выполнять произвольную сборку. Его задача — сообщить о состоянии, проверить предпосылки и передать сигнал восстановлению. LaunchAgent выделенной учётной записи отвечает за очередь, checkout, тестирование, подпись и публикацию результата в пределах выданных прав.
Пятая проверка: приёмка Mac-сборочной машины
Перед включением узла в очередь инженер должен пройти чек-лист, а не ограничиться ручным запуском из терминала:
- [ ] Зафиксирован официальный режим запуска выбранного CI Agent.
- [ ] Определён владелец LaunchAgent и подтверждена его пользовательская сессия.
- [ ] LaunchDaemon не получает доступ к ключам подписи без документированной необходимости.
- [ ] Проверено, что рабочий каталог и кэш принадлежат нужной сервисной учётной записи.
- [ ] Выполнен холодный запуск с проверкой SSH, пользовательской сессии и агента.
- [ ] Выполнена плановая перезагрузка и подтверждена регистрация агента.
- [ ] Проверено восстановление после внезапного отключения питания.
- [ ] Отдельно проверен выход пользователя.
- [ ] При включённом FileVault задокументирован путь разблокировки тома.
- [ ] Keychain найден именно из контекста агента.
- [ ] Выполнена настоящая тестовая сборка с кодовой подписью.
- [ ] Записаны логи, коды выхода и время каждого этапа.
- [ ] Проверен удалённый перезапуск без физического доступа.
- [ ] Определён запасной узел или подтверждено, почему он не нужен.
Итогом приёмки должен быть не скриншот панели CI, а цепочка доказательств: хост доступен, сессия создана, агент зарегистрирован, секреты доступны в разрешённом контексте, а сборка завершилась ожидаемым артефактом.
Для планирования самой инфраструктуры полезно заранее сопоставить требования к корпоративной аренде Mac с этим чек-листом. Вопросы о способе удалённого доступа и восстановлении следует уточнить через центр помощи NodeMini, а не считать их автоматически решёнными по факту наличия VNC или SSH.
Когда текущий узел лучше заменить
Если существующий Mac нельзя удалённо перезапустить, он требует ручного входа после каждого сбоя или не имеет независимого пользовательского контекста, исправление plist не устранит архитектурный риск. Та же проблема возникает, когда вся команда делит один администраторский аккаунт, а ключи подписи лежат в общей среде без аудита.
Покупка физического Mac оправдана для длительной стабильной нагрузки, постоянного использования локальных интерфейсов и сценариев, где предприятие уже умеет обслуживать запасные устройства. Но она несёт расходы на закупку, замену, простой при ремонте, резервную ёмкость и контроль удалённого восстановления.
Если узел нужен для временного проекта, миграции, сезонного CI или нескольких независимых команд, аренда Mac через NodeMini может быть рациональнее: IT получает удалённый ресурс без немедленной закупки каждой рабочей станции, а требования к правам, перезапуску и восстановлению можно включить в процедуру приёмки. Это не заменяет проверку конкретного CI Agent и политики FileVault, но позволяет требовать от поставщика подтверждаемую цепочку восстановления, а не только доступ к удалённому рабочему столу.
Частые вопросы перед внедрением
Ответы на вопросы о запуске CI Agent нужно привязывать к реальному контексту macOS, версии инструмента и политике доступа. Универсальная замена LaunchDaemon на LaunchAgent без проверки Keychain, Simulator и FileVault создаёт только видимость автоматизации.
Запуск после перезагрузки должен означать не появление процесса, а возможность принять и успешно выполнить производственную задачу. Поэтому окончательное решение принимается по результатам приёмочного чек-листа, журналов и подписанной тестовой сборки.
Если текущий узел не проходит проверку независимого входа, удалённого перезапуска или восстановления после FileVault, следующим шагом становится сравнение его с удалённым Mac-ресурсом, где заранее определены права, процедура восстановления и резервная ёмкость. Для временной или расширяемой CI-нагрузки можно изучить варианты аренды Mac для команды и запросить у NodeMini именно те доказательства восстановления, которые перечислены выше.