logo

На 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

Я обычно придерживаюсь такой схемы:

  1. Скрипт делает одну вещь.
  2. У него есть полный путь к интерпретатору или бинарнику.
  3. Есть отдельный лог-файл.
  4. Сервис оформлен через LaunchAgent.
  5. Конфиг проверяется через plutil -lint.
  6. После установки я смотрю launchctl print и тестирую вручную.

Так проще отлаживать и проще поддерживать.

Вывод и финал Link to heading

Если на macOS хочется «сделать сервис», почти всегда нужно идти через launchd.

  • LaunchAgent — для пользовательских задач и GUI.
  • LaunchDaemon — для системных фоновых сервисов.
  • .plist — это конфигурация запуска.
  • launchctl bootstrap и launchctl print — базовые команды для установки и проверки.