
На macOS часто хочется сделать то же, что на Linux делается через systemd: запустить скрипт как сервис, автоматически стартовать его при входе в систему или выполнять по расписанию. В macOS для этого есть launchd — встроенный менеджер процессов и задач.
Если коротко: для пользовательских задач обычно нужен LaunchAgent, а для системных — LaunchDaemon. В этой статье разберём, как выбрать правильный вариант, как написать .plist и как убедиться, что всё работает.
Что такое launchd#
launchd — это базовый механизм запуска сервисов в macOS. Он умеет:
- запускать задачи при входе пользователя;
- держать процесс в фоне и перезапускать его при падении;
- запускать команды по расписанию;
- писать stdout/stderr в файлы;
- автоматически подхватывать конфигурацию из
.plist.
По сути, это не «отдельная утилита», а часть самой системы.
LaunchAgent или LaunchDaemon#
Самый важный выбор — где будет жить задача.
LaunchAgent#
Подходит, если скрипт:
- работает от имени текущего пользователя;
- должен иметь доступ к пользовательскому окружению;
- может открывать GUI-приложения;
- нужен только после логина.
Типичный путь:
~/Library/LaunchAgents/LaunchDaemon#
Подходит, если задача:
- должна работать без логина пользователя;
- запускается от root или системной учётки;
- обслуживает системный сервис;
- не должна зависеть от GUI.
Типичный путь:
/Library/LaunchDaemons/Для большинства «домашних» автоматизаций на Mac — бэкапы, синхронизации, периодические скрипты — почти всегда нужен LaunchAgent.
Минимальный пример#
Допустим, у вас есть скрипт:
/Users/beaverblogger/Documents/scripts/youtube-cookies/sync_yt_cookies.shИ вы хотите запускать его раз в сутки. Для этого создаём файл:
~/Library/LaunchAgents/com.tatarinovms.youtube-cookies-sync.plistПример содержимого:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.tatarinovms.youtube-cookies-sync</string>
<key>ProgramArguments</key>
<array>
<string>/Users/beaverblogger/Documents/scripts/youtube-cookies/sync_yt_cookies.sh</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/beaverblogger/Documents/scripts/youtube-cookies</string>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/tatarinovms/Library/Logs/youtube-cookies-sync.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/tatarinovms/Library/Logs/youtube-cookies-sync.err.log</string>
</dict>
</plist>Это уже полноценный сервис по расписанию.
Какие ключи чаще всего нужны#
Label#
Уникальное имя сервиса. Обычно используют обратный DNS-стиль:
com.company.app-nameProgramArguments#
Команда, которую нужно запустить. Лучше указывать полный путь к скрипту или бинарнику.
WorkingDirectory#
Полезно, если скрипт использует относительные пути.
StartCalendarInterval#
Запуск по времени. Можно указать:
- только
HourиMinute— каждый день в это время; Weekday— по дням недели;Day— по числам месяца;- комбинации.
RunAtLoad#
Если поставить true, сервис запустится сразу после загрузки агента.
KeepAlive#
Если true, launchd будет пытаться держать процесс живым и перезапускать его. Для бесконечных демонов это удобно, но для одноразовых скриптов по расписанию обычно не нужно.
StandardOutPath и StandardErrorPath#
Очень полезно для отладки. Без логов легко потерять ошибку.
Как установить сервис#
После создания .plist нужно загрузить его в launchd:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.tatarinovms.youtube-cookies-sync.plist
launchctl enable gui/$(id -u)/com.tatarinovms.youtube-cookies-syncЕсли конфиг уже был загружен и вы его меняете, обычно удобно сначала выгрузить старую версию:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.tatarinovms.youtube-cookies-sync.plistПотом снова выполнить bootstrap.
Как проверить, что всё работает#
Проверка plist#
plutil -lint ~/Library/LaunchAgents/com.tatarinovms.youtube-cookies-sync.plistЕсли файл валидный, увидите OK.
Проверка статуса#
launchctl print gui/$(id -u)/com.tatarinovms.youtube-cookies-syncТам можно увидеть:
- путь к plist;
- состояние агента;
- путь к stdout/stderr;
- последний код выхода.
Ручной запуск#
Для теста удобно временно выполнить сам скрипт руками:
~/Documents/scripts/youtube-cookies/sync_yt_cookies.shЭто особенно полезно перед тем, как отдавать задачу launchd.
Практические советы#
1. Делайте скрипт идемпотентным#
Сервис может запускаться повторно. Лучше, чтобы повторный запуск не ломал состояние.
2. Не полагайтесь на интерактивную среду#
В launchd обычно нет привычного shell-окружения из терминала. Поэтому:
- используйте абсолютные пути;
- явно задавайте
PATH, если он нужен; - не рассчитывайте на alias и функции shell.
3. Логируйте в файл#
Если задача не стартует, stdout/stderr в лог-файлы экономят много времени.
4. Не quitiть чужой браузер или приложение без проверки#
Если ваш скрипт запускает Firefox, Safari или другое GUI-приложение, сначала определите, запускали ли вы его сами. Иначе можно случайно закрыть то, что пользователь уже открыл вручную.
5. Для фона и GUI — LaunchAgent#
Если сервис должен открыть браузер, нажать что-то в UI или читать пользовательские cookies, LaunchAgent почти всегда правильнее LaunchDaemon.
Когда лучше не использовать launchd#
Иногда проще и честнее использовать:
- обычный
cron, если задача очень простая и не зависит от GUI; atили одноразовый запуск, если нужен только разовый таймер;shortcutsили Automator, если задача чисто пользовательская и без сложной логики.
Но если нужен нормальный сервис на macOS, launchd — стандартный путь.
Мой рабочий шаблон#
Я обычно придерживаюсь такой схемы:
- Скрипт делает одну вещь.
- У него есть полный путь к интерпретатору или бинарнику.
- Есть отдельный лог-файл.
- Сервис оформлен через LaunchAgent.
- Конфиг проверяется через
plutil -lint. - После установки я смотрю
launchctl printи тестирую вручную.
Так проще отлаживать и проще поддерживать.
Вывод и финал#
Если на macOS хочется «сделать сервис», почти всегда нужно идти через launchd.
- LaunchAgent — для пользовательских задач и GUI.
- LaunchDaemon — для системных фоновых сервисов.
.plist— это конфигурация запуска.launchctl bootstrapиlaunchctl print— базовые команды для установки и проверки.


