Задание запускается при входе, в журнале видно запуск клиента, но туннель так и не поднимается. Или поднимается и исчезает через несколько секунд. В обоих случаях список свойств, скорее всего, был в порядке. launchd дал два обещания, принимая задание: он будет следить за процессом, и этот процесс будет продолжать работать. VPN нужно и то и другое, а корректный XML-файл не даёт ни одного.

Корректный список свойств — ещё не работающий сервис

plutil -lint проверяет структуру. Он не может сказать, существует ли бинарный файл, поддерживает ли аргументы эта версия клиента и запускается ли задание в контексте, где доступны учётные данные. Это семантические вопросы, и они объясняют большинство сбоев, которые выглядят как проблемы launchd.

plutil -lint /Library/LaunchDaemons/com.example.vpn.plist
launchctl print system/com.example.vpn

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

Агенты наследуют сеанс пользователя, демоны — нет

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

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

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

Надзор работает только тогда, когда процесс остаётся на переднем плане

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

Используйте документированный режим переднего плана или сервиса. Если у продукта есть собственная поддерживаемая функция запуска, она уже знает, как оставаться на переднем плане и как передать управление сетевому стеку; собственная задача — отдельный проект, а не её улучшение.

Дайте заданию уникальную метку, нужный исполняемый файл и осознанный массив аргументов. Массив — не командная строка оболочки, поэтому конвейеры и перенаправления там не работают, а кавычки ведут себя иначе. Команда, которая успешно выполняется в терминале администратора, — в лучшем случае предварительная проверка.

Учётные данные должны работать без запроса

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

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

Определите, что значит готово, и проверяйте именно там

Успешный запуск доказывает, что процесс начался. Он не доказывает, что сеть поднята, что шлюз принял идентичность или что частное приложение доступно. Если что-то другое зависит от туннеля, ему нужен сигнал готовности, отражающий соединение, а не время запуска задания.

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

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

Похожие статьи