
На macOS часто хочется сделать то же, что на Linux делается через systemd: запустить скрипт как сервис, автоматически стартовать его при входе в систему или выполнять по расписанию. В macOS для этого есть launchd — встроенный менеджер процессов и задач.
Если коротко: для пользовательских задач обычно нужен LaunchAgent, а для системных — LaunchDaemon. В этой статье разберём, как выбрать правильный вариант, как написать .plist и как убедиться, что всё работает.
Что такое launchd Link to heading
launchd — это базовый механизм запуска сервисов в macOS. Он умеет:
- запускать задачи при входе пользователя;
- держать процесс в фоне и перезапускать его при падении;
- запускать команды по расписанию;
- писать stdout/stderr в файлы;
- автоматически подхватывать конфигурацию из
.plist.
По сути, это не «отдельная утилита», а часть самой системы.
LaunchAgent или LaunchDaemon Link to heading
Самый важный выбор — где будет жить задача.
LaunchAgent Link to heading
Подходит, если скрипт:
- работает от имени текущего пользователя;
- должен иметь доступ к пользовательскому окружению;
- может открывать GUI-приложения;
- нужен только после логина.
Типичный путь:
~/Library/LaunchAgents/
LaunchDaemon Link to heading
Подходит, если задача:
- должна работать без логина пользователя;
- запускается от root или системной учётки;
- обслуживает системный сервис;
- не должна зависеть от GUI.
Типичный путь:
/Library/LaunchDaemons/
Для большинства «домашних» автоматизаций на Mac — бэкапы, синхронизации, периодические скрипты — почти всегда нужен LaunchAgent.
Минимальный пример Link to heading
Допустим, у вас есть скрипт:
/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>
Это уже полноценный сервис по расписанию.
Какие ключи чаще всего нужны Link to heading
Label
Link to heading
Уникальное имя сервиса. Обычно используют обратный DNS-стиль:
com.company.app-name
ProgramArguments
Link to heading
Команда, которую нужно запустить. Лучше указывать полный путь к скрипту или бинарнику.
WorkingDirectory
Link to heading
Полезно, если скрипт использует относительные пути.
StartCalendarInterval
Link to heading
Запуск по времени. Можно указать:
- только
HourиMinute— каждый день в это время; Weekday— по дням недели;Day— по числам месяца;- комбинации.
RunAtLoad
Link to heading
Если поставить true, сервис запустится сразу после загрузки агента.
KeepAlive
Link to heading
Если true, launchd будет пытаться держать процесс живым и перезапускать его. Для бесконечных демонов это удобно, но для одноразовых скриптов по расписанию обычно не нужно.
StandardOutPath и StandardErrorPath
Link to heading
Очень полезно для отладки. Без логов легко потерять ошибку.
Как установить сервис Link to heading
После создания .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.
Как проверить, что всё работает Link to heading
Проверка plist Link to heading
plutil -lint ~/Library/LaunchAgents/com.tatarinovms.youtube-cookies-sync.plist
Если файл валидный, увидите OK.
Проверка статуса Link to heading
launchctl print gui/$(id -u)/com.tatarinovms.youtube-cookies-sync
Там можно увидеть:
- путь к plist;
- состояние агента;
- путь к stdout/stderr;
- последний код выхода.
Ручной запуск Link to heading
Для теста удобно временно выполнить сам скрипт руками:
~/Documents/scripts/youtube-cookies/sync_yt_cookies.sh
Это особенно полезно перед тем, как отдавать задачу launchd.
Практические советы Link to heading
1. Делайте скрипт идемпотентным Link to heading
Сервис может запускаться повторно. Лучше, чтобы повторный запуск не ломал состояние.
2. Не полагайтесь на интерактивную среду Link to heading
В launchd обычно нет привычного shell-окружения из терминала. Поэтому:
- используйте абсолютные пути;
- явно задавайте
PATH, если он нужен; - не рассчитывайте на alias и функции shell.
3. Логируйте в файл Link to heading
Если задача не стартует, stdout/stderr в лог-файлы экономят много времени.
4. Не quitiть чужой браузер или приложение без проверки Link to heading
Если ваш скрипт запускает Firefox, Safari или другое GUI-приложение, сначала определите, запускали ли вы его сами. Иначе можно случайно закрыть то, что пользователь уже открыл вручную.
5. Для фона и GUI — LaunchAgent Link to heading
Если сервис должен открыть браузер, нажать что-то в UI или читать пользовательские cookies, LaunchAgent почти всегда правильнее LaunchDaemon.
Когда лучше не использовать launchd Link to heading
Иногда проще и честнее использовать:
- обычный
cron, если задача очень простая и не зависит от GUI; atили одноразовый запуск, если нужен только разовый таймер;shortcutsили Automator, если задача чисто пользовательская и без сложной логики.
Но если нужен нормальный сервис на macOS, launchd — стандартный путь.
Мой рабочий шаблон Link to heading
Я обычно придерживаюсь такой схемы:
- Скрипт делает одну вещь.
- У него есть полный путь к интерпретатору или бинарнику.
- Есть отдельный лог-файл.
- Сервис оформлен через LaunchAgent.
- Конфиг проверяется через
plutil -lint. - После установки я смотрю
launchctl printи тестирую вручную.
Так проще отлаживать и проще поддерживать.
Вывод и финал Link to heading
Если на macOS хочется «сделать сервис», почти всегда нужно идти через launchd.
- LaunchAgent — для пользовательских задач и GUI.
- LaunchDaemon — для системных фоновых сервисов.
.plist— это конфигурация запуска.launchctl bootstrapиlaunchctl print— базовые команды для установки и проверки.