2026 DeepSeek Harness: как настроить автозапуск через launchd?
Материал предназначен для разработчиков, DevOps-инженеров и ответственных за сдачу удалённого Mac с постоянно работающим DeepSeek Harness. Мы показываем, как выбрать правильный объект для управления, настроить LaunchAgent без root-доступа, проверить окружение, логи, Web UI, аварийный перезапуск и поведение после перезагрузки.
В README проекта команда для запуска MCP-варианта DeepSeek Harness указана как npx -y @deepseek-harness/mcp, то есть запуск зависит не только от самого процесса, но и от корректного окружения Node.js и npm. (github.com) Поэтому DeepSeek Harness launchd автозапуск следует строить через обычный пользовательский LaunchAgent, а не через root-сервис по умолчанию: сначала фиксируются абсолютные пути, рабочая папка, профиль и источник ключа, затем отдельно проверяются процесс, порт, модельный вызов и восстановление после сбоя.
Кому стоит читать материал:
- пользователям удалённого Mac, которым нужно автоматически возвращать DeepSeek Harness после входа или перезагрузки;
- платформенным инженерам, отвечающим за единый процесс запуска, учетную запись, журналы и остановку;
- ответственным за сдачу окружения, которым недостаточно статуса «процесс запущен» и нужен повторяемый сквозной тест.
01 Что зафиксировать до настройки launchd
Главная ошибка в подобных внедрениях — пытаться одним plist управлять процессами с разным жизненным циклом. Web UI должен оставаться доступным и принимать запросы, Headless-задача может завершаться после формирования результата, а автоматизация с очередью может требовать отдельного состояния, блокировок и повторной обработки. Эти сценарии нельзя считать взаимозаменяемыми.
Apple описывает LaunchAgent как процесс, работающий от имени вошедшего пользователя, тогда как LaunchDaemon работает в системном контексте и не должен зависеть от конкретной пользовательской сессии. (developer.apple.com) Для Web-рабочего процесса на удалённом Mac это важное ограничение: агент обычно лучше соответствует доступу к пользовательскому каталогу, профилю, SSH-ключам, Keychain и рабочей области.
| Что определить | Почему это важно | Что записать в акте настройки |
|---|---|---|
| Объект управления | Web UI, Headless-вызов и очередь задач требуют разного поведения при остановке | Название процесса и ожидаемый жизненный цикл |
| Исполняемый файл | launchd не обязан искать node, npx или dsh так же, как интерактивный shell |
Полный путь к бинарному файлу или wrapper-скрипту |
| Рабочий каталог | Относительные пути к Profile, конфигурации и результатам могут перестать работать | Абсолютный путь к проекту или рабочей области |
| Учетная запись | От неё зависят файлы, сетевые разрешения, Keychain и доступ к пользовательской сессии | Имя пользователя и группа |
| Остановка | Без штатной команды остановки можно оставить порт, lock-файл или дочерний процесс | Команда и ожидаемый результат остановки |
| Восстановление | Перезапуск процесса не означает продолжение незавершённой задачи | Условия повторного запуска и ручного вмешательства |
До создания plist выполните инвентаризацию в интерактивной сессии:
whoami
pwd
command -v node
command -v npx
command -v dsh
Если одна из команд ничего не возвращает, не переносите команду запуска в launchd «как есть». Сначала выясните, каким способом установлен Node.js или CLI, где находится нужная версия, и какой именно файл запускает текущий рабочий сценарий.
Для проекта из README возможны разные формы поставки: Python-библиотека, CLI с командой dsh, npm-пакет и MCP-сервер. (github.com) В конфигурации нужно выбрать одну форму, а не смешивать npx, dsh и пользовательский shell-скрипт в одном процессе.
02 Первый этап: выбрать LaunchAgent вместо root-службы
Если DeepSeek Harness должен работать в пользовательском Web-профиле или использовать рабочую область конкретного разработчика, начинайте с ~/Library/LaunchAgents. Apple указывает, что пользовательский агент запускается в контексте вошедшего пользователя и работает только пока эта сессия активна. (developer.apple.com) Это не недостаток, который нужно немедленно исправлять root-доступом, а часть модели безопасности и жизненного цикла.
| Вариант | Подходящий сценарий | Основной риск |
|---|---|---|
LaunchAgent |
Web UI, пользовательский Profile, рабочая папка проекта, запуск после входа | Не работает как пользовательский процесс до входа в систему |
LaunchDaemon |
Независимый системный сервис, которому не нужны UI и пользовательская сессия | Сложнее с правами, секретами, рабочими каталогами и доступом к пользовательским данным |
Wrapper-скрипт под LaunchAgent |
npx, Node.js, виртуальное окружение, несколько переменных окружения | Ошибки внутри скрипта могут быть менее заметны без отдельного stderr |
| Ручной запуск в Terminal | Разовая проверка или отладка | После выхода из сессии, закрытия терминала или перезагрузки процесс не гарантирован |
Пример ниже намеренно не является готовым файлом для копирования. Все пути, учетные записи, идентификатор и параметры должны быть заменены после проверки конкретного Mac:
<?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.example.deepseek-harness</string>
<key>ProgramArguments</key>
<array>
<string>/ABSOLUTE/PATH/TO/WRAPPER</string>
<string>--profile</string>
<string>/ABSOLUTE/PATH/TO/PROFILE</string>
</array>
<key>WorkingDirectory</key>
<string>/ABSOLUTE/PATH/TO/WORKSPACE</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/ABSOLUTE/PATH/TO/NODE/BIN:/usr/bin:/bin</string>
<key>DEEPSEEK_API_KEY_FILE</key>
<string>/ABSOLUTE/PATH/TO/SECRET/REFERENCE</string>
</dict>
<key>StandardOutPath</key>
<string>/ABSOLUTE/PATH/TO/LOG/stdout.log</string>
<key>StandardErrorPath</key>
<string>/ABSOLUTE/PATH/TO/LOG/stderr.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
В этом шаблоне намеренно нет настоящего API Key. В README CLI-поставки используется переменная DEEPSEEK_API_KEY, но способ безопасного хранения зависит от того, как именно запускается выбранная форма Harness. (github.com) Нельзя автоматически считать, что любой режим прочитает Keychain или файл .env.
Практически безопаснее использовать wrapper, который:
- получает секрет из заранее определённого защищённого источника;
- экспортирует его только в окружение дочернего процесса;
- переходит в нужный рабочий каталог;
- запускает DeepSeek Harness по абсолютному пути;
- не печатает окружение и аргументы в stdout или stderr.
Файл с секретом должен принадлежать нужной учетной записи, иметь минимальные права чтения и не находиться в общей рабочей папке. Root-права не решают проблему неправильной модели доступа: они лишь увеличивают последствия утечки ключа или ошибки в пути.
03 Второй этап: подготовить пути, окружение и журналы
launchd запускает процесс не из интерактивного терминала. Поэтому не следует полагаться на .zshrc, .zprofile, менеджер версий Node.js или случайно расширенный PATH. Apple также указывает, что процесс не будет запущен, если исполняемый файл не удовлетворяет системным ограничениям запуска. (developer.apple.com)
Проверьте следующие параметры:
| Параметр | Проверка | Что считать ошибкой |
|---|---|---|
| Node.js | command -v node, затем запуск по абсолютному пути |
В plist указан node, но путь доступен только в shell |
| npm/npx | command -v npx и тест запуска без UI |
npx пытается скачать пакет в неожиданную папку |
| CLI | command -v dsh, проверка версии или dsh doctor, если команда поддерживается |
CLI установлен для другого пользователя |
| Profile | Открытие файла и проверка владельца | Агент видит пустой или чужой профиль |
| Рабочая папка | cd в каталог от имени целевого пользователя |
Относительные результаты создаются в другом месте |
| Логи | Запись в отдельную папку | stderr смешан с системными журналами или недоступен пользователю |
Создайте отдельную папку журналов и проверьте запись от имени целевого пользователя:
mkdir -p /ABSOLUTE/PATH/TO/LOG
touch /ABSOLUTE/PATH/TO/LOG/stdout.log
touch /ABSOLUTE/PATH/TO/LOG/stderr.log
Если используется npx, лучше сначала выполнить запуск вручную с тем же абсолютным путем и теми же переменными. Для постоянно работающего Web UI разумно закрепить версию пакета или использовать заранее проверенный локальный запуск, чтобы автоматический процесс не получил неожиданное обновление при очередном старте.
Не добавляйте KeepAlive на первой итерации только потому, что процесс завершился. Сначала прочитайте stderr: постоянный перезапуск может скрывать отсутствие Node.js, неверный Profile, занятую рабочую папку, ошибку API или неправильный аргумент. Apple рассматривает launchd как механизм управления жизненным циклом процессов, но он не исправляет ошибку самого приложения. (developer.apple.com)
04 Третий этап: загрузить агент и проверить личность процесса
Сохраните plist в каталоге пользователя, например:
mkdir -p "$HOME/Library/LaunchAgents"
cp /ABSOLUTE/PATH/TO/com.example.deepseek-harness.plist \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
Перед загрузкой проверьте синтаксис и права:
plutil -lint "$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
chmod 600 "$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
Для управления агентами Apple рекомендует использовать launchctl; с ним выполняются загрузка, выгрузка и проверка состояния job. (support.apple.com) Конкретная команда зависит от версии macOS и выбранного режима загрузки, поэтому в рабочей инструкции фиксируйте именно ту форму команды, которая успешно прошла на целевой системе.
После загрузки проверяйте не одну строку статуса, а несколько независимых признаков:
launchctl print "gui/$(id -u)/com.example.deepseek-harness"
pgrep -af "deepseek|dsh|node"
tail -n 100 /ABSOLUTE/PATH/TO/LOG/stderr.log
tail -n 100 /ABSOLUTE/PATH/TO/LOG/stdout.log
Нужно установить:
- процесс запущен от ожидаемого пользователя;
- рабочий каталог соответствует plist;
- путь к Node.js или
dshразрешается без интерактивного shell; - stdout и stderr сохраняются в предсказуемых файлах;
- в журнале нет реального ключа, полного окружения и чувствительных аргументов;
- завершение процесса не вызывает бесконечный цикл скрытых перезапусков.
Если процесс сразу исчезает, временно отключите автоматическое удержание и повторите запуск вручную через тот же wrapper. В первую очередь исправляются абсолютные пути, разрешения, профиль и переменные среды; только после этого оценивается политика повторного запуска.
05 Четвёртый этап: отделить доступный Web UI от работающего Harness
Открытая страница Web UI доказывает только то, что некоторый процесс слушает адрес и порт. Она не подтверждает наличие Profile, доступ к рабочей области, чтение ключа или успешный вызов модели. В предыдущих проверках Web UI мы бы не смешивали эти уровни диагностики; для launchd это особенно важно, поскольку окружение после входа пользователя может отличаться от окружения в Terminal.
| Уровень приемки | Минимальная проверка | Ожидаемый результат |
|---|---|---|
| Процесс | launchctl print, pgrep, владелец процесса |
Запущен нужный экземпляр под нужной учетной записью |
| HTTP-доступ | Проверка адреса прослушивания из разрешённого канала | Web UI отвечает без вывода секрета в URL |
| Рабочая область | Открытие заранее выбранного каталога или Profile | Harness видит именно ожидаемые файлы |
| Модельный вызов | Одна низкорисковая задача | Есть корректный ответ и нет ошибки авторизации |
| Headless-режим | Проверка кода выхода и результата | Процесс завершается предсказуемо, артефакт сохранён |
| Логи | Просмотр stdout и stderr после теста | Ошибки доступны, секреты не раскрыты |
Для Web-режима зафиксируйте адрес прослушивания и способ доступа. Если интерфейс привязан только к 127.0.0.1, удалённый Mac не станет доступен напрямую из внешней сети; потребуется безопасный туннель или иной предусмотренный инфраструктурой канал. Не открывайте панель наружу только ради того, чтобы «проверить порт».
Для Headless-режима нельзя использовать KeepAlive как замену очереди. Если задача завершилась с ошибкой после частичного создания результата, launchd может запустить новый процесс, но не знает, на каком шаге остановилась бизнес-операция. Для возобновления нужны отдельный журнал состояния, идемпотентные файлы результатов и правило повторного запуска.
Если применяете CLI-вариант, используйте поддерживаемую команду диагностики из документации проекта, например dsh doctor, но проверяйте её в том же окружении, что и агент. (github.com) Успешная команда в вашем shell не является доказательством успешного запуска через launchd.
06 Пятый этап: проверить вход, сбой и перезагрузку
Тест восстановления должен состоять из отдельных испытаний, потому что у каждого свой смысл.
Остановка вручную. Остановите агент штатной командой, убедитесь, что дочерний процесс и порт исчезли, затем загрузите агент снова. Если старый процесс остался, сначала исправьте схему остановки и поиск дочерних процессов.
Аварийное завершение. Завершите основной процесс тестовым способом и наблюдайте, что происходит дальше. Если KeepAlive выключен, фиксируется ожидаемая остановка. Если включён, фиксируется факт повторного старта, но не восстановление незавершённой задачи.
Повторный вход. Выйдите из пользовательской сессии и войдите снова. LaunchAgent связан с вошедшим пользователем, поэтому этот тест показывает, возвращается ли Web UI и корректно ли создаётся новое окружение. (developer.apple.com)
Перезагрузка Mac. После перезагрузки проверьте, когда появляется процесс, отвечает ли Web UI, читается ли Profile и выполняется ли минимальная задача. Не фиксируйте выдуманное время восстановления: измеряйте его на конкретном Mac и записывайте точку отсчёта — завершение загрузки системы, вход пользователя или доступность удалённого канала.
Отдельно отмечайте, какие состояния не восстанавливаются:
- незавершённый Headless-вызов;
- временный контекст Web-сессии;
- дочерний процесс, не управляемый основным PID;
- блокировка рабочей папки;
- частично записанный результат;
- одноразовое подтверждение или ручное разрешение.
Именно здесь чаще всего появляется ложная формулировка «задача восстановилась». На самом деле восстановился только процесс. Чтобы утверждать продолжение задачи, нужно увидеть корректный итоговый артефакт или подтверждённую идемпотентную повторную обработку.
07 Чек-лист перед передачей удалённого Mac
- [ ] Определён один конкретный объект: Web UI, Headless или автоматизация.
- [ ] Записаны учетная запись, абсолютный путь запуска, Profile и рабочий каталог.
- [ ]
node,npxилиdshвызываются по проверенному пути. - [ ] В plist нет настоящего API Key и других секретных значений.
- [ ] Источник
DEEPSEEK_API_KEYили другой секретной переменной описан отдельно. - [ ] Права на plist, wrapper и файл секрета проверены.
- [ ] stdout и stderr направлены в обслуживаемые журналы.
- [ ] После загрузки подтверждён владелец процесса.
- [ ] Web UI проверен отдельно от модельного вызова.
- [ ] Выполнена одна низкорисковая задача с проверкой результата.
- [ ] Для Headless-процесса записаны код выхода и путь к артефакту.
- [ ] Испытаны ручная остановка и аварийное завершение.
- [ ] Испытаны повторный вход пользователя и перезагрузка Mac.
- [ ] Зафиксировано, что именно восстанавливается, а что требует ручного вмешательства.
- [ ] Есть процедура отключения, обновления и возврата к предыдущей команде.
- [ ] После смены версии проверено отсутствие второго экземпляра и конфликта порта.
08 Шестой этап: оформить отключение, обновление и откат
До передачи окружения подготовьте три короткие инструкции: как остановить агент, как полностью убрать его из автозапуска и как вернуть предыдущую версию.
При обновлении сначала остановите старый экземпляр, убедитесь, что порт и состояние больше не удерживаются, затем замените путь в wrapper или plist и снова выполните проверку plutil. Не меняйте одновременно исполняемый файл, рабочий каталог, Profile и источник секрета без промежуточного теста — иначе при сбое будет невозможно определить причину.
Отдельно храните:
- копию plist без секретных значений;
- версию wrapper-скрипта;
- команду ручного запуска;
- команду проверки Web UI;
- описание ожидаемого результата Headless-задачи;
- расположение журналов;
- условие, при котором требуется ручное вмешательство.
Если новая версия не проходит минимальный модельный вызов, откат должен возвращать старый абсолютный путь и прежний Profile, а не просто перезапускать тот же неисправный процесс. Одновременные экземпляры особенно опасны для Web UI: они могут конкурировать за один порт, временные файлы и состояние рабочей области.
Для дополнительной проверки удалённого сценария полезно сопоставить этот материал с руководством по приёмке DeepSeek Harness в облачном Mac-окружении, а перед передачей пользователю — с правилами доступа к удалённому Mac. Если тестовая машина не выдерживает постоянную пользовательскую сессию, отдельно оцените аренду Mac в регионе US East, но не подменяйте выбор региона предположением о доступности конкретного процесса.
09 Частые вопросы при эксплуатации
Если Web UI доступен, а задача не выполняется, не начинайте с увеличения KeepAlive. Сначала проверьте рабочую папку, Profile, путь к Node.js, переменную API и stderr. Если агент работает после ручного входа, но не после перезагрузки, определите, требуется ли вошедшая пользовательская сессия. Это может быть ограничением выбранного режима, а не неисправностью launchd.
Для долгих фоновых операций зафиксируйте границу ответственности: launchd отвечает за запуск процесса, но не за бизнес-состояние задачи. Последнее должно сохраняться самим приложением или отдельным механизмом очереди. Поэтому в акте сдачи не пишите «задачи возобновляются» только на основании того, что PID появился снова.
10 Почему текущий Mac может быть хуже постоянного удалённого окружения
Если DeepSeek Harness запускается на личном Mac, у текущего подхода обычно есть несколько реальных недостатков: перезагрузка зависит от ручного входа, рабочая станция может уходить в сон, обновление Node.js меняет путь запуска, а доступ к Web UI часто требует дополнительного туннеля и контроля локальной сети. При этом root-служба не устраняет эти ограничения, а добавляет риск неправильных прав и утечки ключа.
Для разового теста, отладки или короткой автоматизации локальный Mac остаётся разумным вариантом. Но если нужен предсказуемый удалённый доступ, фиксированная рабочая папка и заранее согласованная процедура восстановления, аренда Mac через JEXCLOUD может быть удобнее постоянного удержания личной машины включённой. Перед оформлением стоит сначала завершить описанный чек-лист, а затем передать в заявку не секреты, а обезличенную схему запуска, требования к пользовательской сессии и критерии успешной перезагрузки.
Как сделать так, чтобы DeepSeek Harness запускался после перезагрузки Mac?
Для обычного пользовательского сценария разместите plist в каталоге LaunchAgents нужной учетной записи и загружайте его через launchctl после входа пользователя в систему. В ProgramArguments укажите абсолютный путь к реальному исполняемому файлу или к отдельному wrapper-скрипту. После загрузки проверьте не только наличие процесса, но и Web UI либо минимальный Headless-вызов.
Почему LaunchAgent не видит npx или Node.js?
launchd не обязан использовать тот же PATH, что интерактивный shell. Поэтому команда, работающая в Terminal, может завершаться сразу после запуска агента. Найдите реальные пути командами command -v node и command -v npx, затем укажите абсолютный путь в plist или создайте wrapper с явным PATH. Дополнительно проверьте рабочий каталог и права запуска.
Где хранить API Key при запуске DeepSeek Harness через launchd?
Не помещайте реальное значение ключа в plist, репозиторий или аргументы командной строки. Используйте отдельный файл окружения с ограниченными правами, macOS Keychain либо секретный механизм вашей инфраструктуры, если приложение умеет его читать. В конфигурации оставляйте только ссылку на источник или имя переменной DEEPSEEK_API_KEY, а в логах маскируйте значение.
Что делать, если Web UI открывается, но задача DeepSeek Harness не выполняется?
Разделите проверку интерфейса и рабочего процесса. Сначала подтвердите учетную запись процесса, рабочий каталог, доступность Node или dsh и чтение API Key. Затем выполните одну низкорисковую модельную задачу и проверьте код выхода, stderr и созданный результат. Открытый порт доказывает только работу HTTP-слоя, но не готовность Harness к выполнению.
Запустите постоянно работающий сервис на удалённом Mac от JEXCLOUD
Арендуйте выделенный физический Mac на Apple Silicon для надёжной работы фоновых процессов и задач автоматизации.
Настройте LaunchAgent через launchd без root-доступа и сохраняйте рабочее окружение после перезагрузки macOS.
Арендовать сейчас