Автоматизация развёртывания шлюза OpenClaw через launchd на удалённом Mac

В пятницу вечером webhook'и внезапно краснеют. Вы заходите по SSH на удалённый Mac и видите, что процесс шлюза давно исчез — в прошлый раз вы запустили его вручную через openclaw gateway run, а после ночной перезагрузки никто не повторил команду. Discord-бот показывает «онлайн», но порт 18789 пуст, а в очереди GitHub уже 47 ответов 502.

Это не баг OpenClaw — это модель эксплуатации, застрявшая в «мышлении dev-машины»: интерактивный запуск, plist, правленный вручную, отдельная конфигурация на каждом хосте. В 2026 году шлюз нужно рассматривать как инфраструктуру, а на macOS для этого подходит launchd — автозапуск при загрузке, восстановление после сбоя, логи на диске, конфигурация в Git.

Эта статья даёт путь от нуля до приёмки: регистрация через onboard, продакшен-шаблон plist, bootstrap-скрипт в один клик, health-probe и порядок отката при обновлении. По устранению неполадок см. сопутствующий справочник по стабильности шлюза OpenClaw в launchd; по CI на нескольких машинах — OpenClaw: пошаговое развёртывание и автоматизация GitHub Actions.

Краткий ответ: три самых частых вопроса

Вопрос Прямой ответ На что обратить внимание
Как быстрее всего пережить перезагрузку? openclaw onboard с launchd или bootstrap-скрипт в конце статьи Сначала зелёный doctor, иначе получите crash loop
Можно ли совмещать ручной gateway run и launchd? Нет — двойная привязка порта Перед сменой: launchctl bootout + lsof для очистки порта
Как доказать, что это действительно HA? reboot → подождать 2 минуты → gateway probe + внешний curl Не ограничивайтесь localhost

1. Почему пора отказаться от ручной настройки

На удалённом постоянно включённом Mac ручная конфигурация ломается предсказуемо:

Подход Кажется удобным Реальная цена
nohup openclaw gateway run & по SSH Сервис за 5 секунд Пропадает после reboot; нет ротации логов; код выхода не виден
Ручное редактирование plist на каждой машине «Просто сменить порт» Конфликты Label, дрейф PATH, нет diff при обновлении
В документации: «нажать Старт после входа» Обход проблем TCC Отключение питания в праздники → гарантированный простой
Docker и launchd одновременно «Дополнительная страховка» Борьба за 18789; двойная блокировка каталога State
Главная мысль: шлюз — это долгоживущий сервис с состоянием, а не одноразовый скрипт. launchd берёт на себя надзор за процессом; вы сосредотачиваетесь на конфигурации и probe — вот что и есть автоматизация.

2. Что означает «высокая доступность» шлюза на Mac

Один Mac не даст HA в стиле Kubernetes с несколькими репликами, но эксплуатационную HA достичь можно:

  • Survive reboot: шлюз слушает в течение 2 минут после загрузки, без ручного SSH.
  • Survive crash: KeepAlive + ThrottleInterval — аварийный выход запускает перезапуск с откатом, без загрузки CPU на 100%.
  • Survive config drift: plist, переменные окружения и ~/.openclaw с резервными копиями и историей в Git.
  • Survive silent failure: внешний healthcheck ловит ситуацию «порт слушает, но probe падает».
  • Survive upgrade: фиксированный порядок backup → doctor --fix → gateway restart, каждый раз одинаковый и скриптуемый.

На практике: M4 Mac mini (24 ГБ) со шлюзом OpenClaw и лёгкими плагинами в простое потребляет около 4–6 Вт; память процесса шлюза обычно 200–450 МБ. Размещение машины по пути нативного удалённого развёртывания на Mac предсказуемее, чем оставлять iMac включённым в офисе.

3. LaunchAgent или LaunchDaemon

Параметр LaunchAgent (пользовательский домен) LaunchDaemon (системный домен)
Путь ~/Library/LaunchAgents/ /Library/LaunchDaemons/
Момент запуска После входа пользователя При загрузке системы, без GUI-входа
TCC / связка ключей Наследует разрешения пользователя Ограничен — подходит для чистых сетевых демонов
Лучше всего для Автоматизация браузера, плагины с доступом к каталогам пользователя Чистый HTTP/Webhook-шлюз без зависимости от GUI

Рекомендация на 2026 год: начните с LaunchAgent — прогоните через него onboard и doctor. Переходите на Daemon только после подтверждения, что сессия входа не нужна, и зафиксируйте это в runbook. В любом случае ProgramArguments должны содержать абсолютные путиlaunchd не читает ваш .zshrc.

4. Регистрация launchd через onboard в один клик

Мастер onboard OpenClaw записывает путь к Node, каталог State и порт шлюза в конфигурацию, а в новых версиях поддерживает прямую установку сервиса launchd. Рекомендуемый порядок:

# 1. 确认运行时(与 plist 内 PATH 一致)
node -v          # 期望 v22.x
which openclaw   # 记下绝对路径,如 /opt/homebrew/bin/openclaw

# 2. 预检
openclaw doctor

# 3. 交互式 onboard(按提示启用 Gateway + launchd)
openclaw onboard

# 4. 验收
launchctl print "gui/$(id -u)/com.openclaw.gateway" 2>/dev/null || launchctl list | grep -i openclaw
lsof -nP -iTCP:18789 -sTCP:LISTEN
openclaw gateway probe
Правило: интерактивный SSH работает → onboard закрепляет → reboot для приёмки. Пропустите второй шаг и сразу включите KeepAlive — получите только более быстрый crash loop.

5. Продакшен-шаблон plist (готов к Git)

Если onboard не создал plist, или вам нужно явно версионировать его в infra-репозитории, используйте шаблон ниже. Замените OPENCLAW_BIN на вывод which openclaw:

<?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.openclaw.gateway</string>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/homebrew/bin/openclaw</string>
    <string>gateway</string>
    <string>run</string>
  </array>
  <key>WorkingDirectory</key>
  <string>/Users/your-ci-user</string>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key>
    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
    <key>HOME</key>
    <string>/Users/your-ci-user</string>
  </dict>
  <key>StandardOutPath</key>
  <string>/Users/your-ci-user/Library/Logs/OpenClawGateway/gateway.stdout.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/your-ci-user/Library/Logs/OpenClawGateway/gateway.stderr.log</string>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <dict>
    <key>SuccessfulExit</key>
    <false/>
  </dict>
  <key>ThrottleInterval</key>
  <integer>30</integer>
</dict>
</plist>

Загрузка (Ventura+):

UID_NUM=$(id -u)
DOMAIN="gui/${UID_NUM}"
LABEL="com.openclaw.gateway"
PLIST="${HOME}/Library/LaunchAgents/${LABEL}.plist"

launchctl bootout "${DOMAIN}/${LABEL}" 2>/dev/null || true
launchctl bootstrap "${DOMAIN}" "${PLIST}"
launchctl kickstart -k "${DOMAIN}/${LABEL}"

Типичные ошибки:

  • На Intel Mac Homebrew часто в /usr/local/bin, на Apple Silicon — в /opt/homebrew/bin; пропишите в plist явно, а не «универсальный PATH».
  • Сначала mkdir -p для каталога логов — иначе launchd может не стартовать, а stderr некуда писать.
  • Не держите один и тот же Label одновременно в пользовательском и системном домене; перед обновлением выполните bootout старой задачи.

6. Bootstrap-скрипт в один клик

Объедините «установить CLI → doctor → записать plist → bootstrap → probe» в одну команду — удобно для только что арендованного облачного Mac или self-hosted runner GitHub Actions. Полный скрипт в каталоге ресурсов этой статьи:

resources/bootstrap-openclaw-gateway-launchd.sh

На удалённом Mac:

chmod +x bootstrap-openclaw-gateway-launchd.sh
./bootstrap-openclaw-gateway-launchd.sh

Краткая логика скрипта:

  1. Проверка node / openclaw; при отсутствии — npm i -g openclaw@latest
  2. Создание ~/Library/Logs/OpenClawGateway
  3. Приоритет openclaw onboard; если plist нет — запись резервного LaunchAgent
  4. launchctl bootstrap + kickstart -k
  5. Приёмка через lsof + openclaw gateway probe
Для команды: скрипт живёт в infra-репозитории; на каждый Mac внедряются только secrets (API-токен, регион). От «голого» железа до зелёного probe — меньше 5 минут. Вот что значит «хватит настраивать вручную».

7. Health-probe и внешнее самовосстановление

KeepAlive проверяет только наличие процесса, но не ситуацию «процесс завис, а порт всё ещё LISTEN». Добавьте лёгкий probe LaunchAgent (каждые 5 минут):

#!/bin/bash
# ~/bin/openclaw-gateway-healthcheck.sh
set -euo pipefail
PORT="${OPENCLAW_GATEWAY_PORT:-18789}"
LABEL="com.openclaw.gateway"
DOMAIN="gui/$(id -u)"

if ! openclaw gateway probe >/dev/null 2>&1; then
  logger -t openclaw-ha "probe failed, kickstart gateway"
  launchctl kickstart -k "${DOMAIN}/${LABEL}" || true
fi

Свяжите с plist через StartCalendarInterval в ~/Library/LaunchAgents/com.openclaw.gateway-healthcheck.plist. Можно подключить UptimeRobot или Prometheus blackbox exporter — но probe должен идти тем же путём, что и webhook'и (с reverse proxy и TLS), а не только curl localhost.

Ротируйте логи через newsyslog или усечение по размеру, чтобы один файл не заполнил NVMe — при переполнении диска дочерние процессы launchd выходят с ENOSPC, и это выглядит как «случайные обрывы».

8. Конфигурация в Git и поэтапные обновления

Храните в версионном контроле и ревьюйте в PR, а не правьте по SSH:

Файл Назначение
launchagents/com.openclaw.gateway.plist Основной сервис шлюза
scripts/bootstrap-openclaw-gateway-launchd.sh Настройка нового узла
scripts/upgrade-openclaw-gateway.sh Фиксированная последовательность обновления
docs/runbook-gateway.md Справочник для дежурного

Минимальный порядок скрипта обновления (согласован со статьёй о многоканальной стабильности шлюза):

openclaw backup create
npm update -g openclaw@latest   # 或锁版本号
openclaw doctor --fix
launchctl kickstart -k "gui/$(id -u)/com.openclaw.gateway"
openclaw gateway probe
# 多插件环境:逐个 browser/cron doctor

9. Семишаговый чеклист приёмки

Перед релизом или выводом новой машины в прод зайдите по SSH и отметьте:

  • openclaw doctor без ERROR
  • launchctl print gui/$(id -u)/com.openclaw.gateway в состоянии running
  • lsof -nP -iTCP:18789 -sTCP:LISTEN — PID совпадает с plist
  • openclaw gateway probe успешен
  • curl публичной точки входа из офисной сети / мобильного интернета (не только 127.0.0.1)
  • После sudo reboot probe снова успешен в течение 2 минут
  • Намеренный kill PID шлюза — автовосстановление за 30 секунд без спама Throttle

10. Почему для этой автоматизации по-прежнему лучше Mac mini

Шлюзу нужны постоянная работа, тишина и единые пути. Mac mini M4 на Apple Silicon в простое потребляет около 4 Вт — круглосуточная работа на порядок дешевле настольного ПК. launchd, Homebrew и ~/.openclaw OpenClaw совпадают с локальной dev-машиной, поэтому не придётся вести два runbook.

Если вы переносите OpenClaw с «эксперимента на ноутбуке» на выделенный удалённый Mac, сначала отладьте автоматизацию на одном узле, затем добавляйте ноды. На главной Macstripe можно взять Mac mini с посуточной оплатой в разных регионах — один раз прогоните bootstrap, и это удобнее, чем держать машину включённой в офисе.

FAQ

Обязательно ли использовать launchd для шлюза OpenClaw?

Нет — но на macOS launchd — официальный менеджер демонов, и для работы без присмотра он подходит лучше, чем nohup. Docker удобен для параллельных версий; launchd — для шлюза с меньшей задержкой и без лишней виртуализации.

Что выбрать — LaunchAgent или LaunchDaemon?

LaunchAgent — если нужны TCC пользователя или связка ключей; LaunchDaemon (root) — если сервис должен слушать без входа в систему. Большинство команд начинают с Agent.

Конфликтует ли onboard с plist, написанным вручную?

Label должен быть уникальным. Перед обновлением выполните bootout, чтобы два процесса не боролись за 18789. Финальный plist лучше хранить в Git.

Probe падает, но порт слушает?

Проверьте аутентификацию, TLS и адрес привязки. Выполните doctor, затем сверьте ~/.openclaw; подробности — в справочнике по устранению неполадок launchd.

Как развернуть на нескольких Mac?

Self-hosted runner'ы + один bootstrap-скрипт, secrets для токенов, поэтапная приёмка через probe. См. совместную работу нескольких runner в GitHub Actions.

Итог

Хватит настраивать вручную — не значит «никогда не открывать терминал», а значит в терминале только скрипты, никаких импровизаций: onboard регистрирует launchd, plist уходит в Git, bootstrap настраивает новые машины, probe принимает, healthcheck страхует краевые случаи. Шлюз перестаёт быть «процессом на переднем плане в SSH-сессии» и становится повторяемой инфраструктурой.

Следующий шаг: прогоните bootstrap на одном удалённом Mac, сделайте учебную перезагрузку и вставьте семишаговый чеклист в wiki команды. Когда понадобится выделенный узел — выберите регион на главной Macstripe.