Skip to Content
Руководство для пользователейЛокальное развертывание

Локальные шаблоны запуска для qwen serve (v0.16-alpha)

Эталонные шаблоны для запуска qwen serve как долгоживущего фонового процесса на рабочей станции разработчика. Используются совместно с известными ограничениями v0.16-alpha — только локально, для одного пользователя, со своим собственным bearer token. Контейнерные развертывания, многохостовая и с TLS-терминацией откладываются до v0.16.x.

Аудитория: разработчики, использующие dogfooding, которым нужен демон, работающий после перезагрузки, с логами в надежном месте и чистой историей «перезапуск при сбое». Если демон нужен только на время одной сессии в терминале, можно просто запустить qwen serve (в интерактивном режиме, Ctrl-C для остановки).

Сгенерируйте bearer token (один раз)

openssl rand -hex 32 > ~/.qwen-serve-token # управляется пользователем, НЕ встроенный путь chmod 600 ~/.qwen-serve-token export QWEN_SERVER_TOKEN="$(cat ~/.qwen-serve-token)"

Путь и имя файла вы выбираете сами; v0.16-alpha не генерирует и не ищет токен автоматически (отложено до v0.16.x). См. раздел Аутентификация в руководстве пользователя для канонической настройки BYO.

Ограничьте этот export только текущей сессией в терминале. Не добавляйте его в ~/.bashrc или ~/.zshrc — экспорт на уровне профиля откроет доступ к bearer token для всех процессов, порожденных из этой оболочки (подпроцессы IDE, отладчики браузера, скрипты npm из несвязанных проектов). Для долгоживущих настроек используйте механизмы EnvironmentFile= (systemd) / EnvironmentVariables (launchd), описанные ниже — они ограничивают токен только процессом демона.

Демон читает bearer token либо из --token <значение> в командной строке, либо из переменной окружения QWEN_SERVER_TOKEN (пробелы в начале и конце игнорируются). Конструктор DaemonClient из TypeScript SDK использует QWEN_SERVER_TOKEN как фолбэк, если не передан параметр token (фолбэк PR 27 — клиентам с установленной переменной окружения не нужно передавать значение через свой скрипт).

Один export на уровне оболочки покрывает как запуск сервера, так и создание клиента SDK (просто держите его ограниченным сессией, как указано выше).

Жизненный цикл рабочих пространств и границы процессов

Один демон может размещать несколько изолированных сред выполнения рабочих пространств под одним слушателем. Повторите --workspace с абсолютными каталогами для создания явных стартовых сред выполнения; первое является основным. Основное и другие явные стартовые/статические среды выполнения не могут быть удалены без перезапуска процесса.

Дополнительные рабочие пространства также могут быть зарегистрированы во время работы демона через POST /workspaces. Передайте persist: true, чтобы сохранить динамическое вторичное рабочее пространство в хранилище регистраций на уровне пользователя, чтобы оно было восстановлено при следующем запуске. Ненадёжные регистрации остаются видимыми для диагностики, ограниченного чтения файлов и объявленных сохранённых чтений, но не могут запускать ACP. Динамические и восстановленные из сохранённых вторичные рабочие пространства можно удалять: обычное удаление отклоняется, пока среда выполнения занята, а принудительное удаление запрашивает завершение активных ресурсов и фиксирует логическое удаление до того, как тот же cwd может быть добавлен повторно. Очистка ограничена и выполняется по мере возможности после точки фиксации сохранения; ошибки записываются в лог, а не восстанавливают удалённую среду выполнения.

Изоляция сред выполнения охватывает cwd, оверлей окружения, границу файловой системы/доверия, сервисы рабочего пространства, бридж, состояние аренды Voice, воркер канала и границу ресурсов ACP/MCP. Продакшен пытается прогреть дочерний процесс ACP основного рабочего пространства и повторяет попытку при первом использовании после сбоя; доверенные вторичные рабочие пространства запускают свой ACP по требованию, а ненадёжные вторичные не запускают ACP. Аутентификация, HTTP rate limits, лимиты слушателя и допуска Voice, допуск общих сессий, метрики, завершение работы и радиус сбоя процесса остаются общими для всего демона. Запускайте отдельные демоны, когда эти границы на уровне процессов должны быть независимыми.

Linux: пользовательский модуль systemd

Сначала найдите ваш исполняемый файл qwen и доверенные каталоги инструментов. ExecStart= файла модуля должен содержать абсолютный путь, а явный PATH должен включать доверенные каталоги для инструментов, которые нужны сессиям демона, таких как gh, git, npm и интерпретатор node, используемый скриптовым лаунчером qwen. Менеджеры служб не читают ваш профиль оболочки. Выполните which qwen gh git npm node в вашей обычной оболочке, затем подставьте фактические исполняемые файлы и каталоги везде, где в шаблоне ниже указано /PATH/TO/qwen и /PATH/TO/USER/BIN.

~/.config/systemd/user/qwen-serve.service:

[Unit] Description=Демон Qwen Code (loopback HTTP + SSE) After=network.target [Service] Type=simple # Замените на ваш проект; %h раскрывается в $HOME для пользовательских модулей. WorkingDirectory=%h/project-a # Выполните `which qwen`, чтобы найти абсолютный путь. systemd НЕ читает $PATH. ExecStart=/PATH/TO/qwen serve --hostname 127.0.0.1 --port 4170 --workspace %h/project-a --workspace %h/project-b # Замените первую запись доверенными каталогами, содержащими интерпретатор qwen # и пользовательские инструменты. systemd не читает профили оболочки. Environment=PATH=/PATH/TO/USER/BIN:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin # Читать bearer token из файла с chmod 600, а не встраивать в модуль. # `Environment=` раскроет токен в файле модуля (обычно 644 = читается всеми). # EnvironmentFile хранит токен в защищенном файле, который вы уже создали с `chmod 600`. EnvironmentFile=%h/.qwen-serve-token-env Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=default.target

Создайте файл окружения один раз (файл токена из шага настройки хранит сырое значение; здесь он оборачивается в форму KEY=value, чтобы systemd прочитал его как присваивание переменной окружения):

echo "QWEN_SERVER_TOKEN=$(cat ~/.qwen-serve-token)" > ~/.qwen-serve-token-env chmod 600 ~/.qwen-serve-token-env

Управление:

systemctl --user daemon-reload systemctl --user enable --now qwen-serve.service loginctl enable-linger "$(whoami)" # оставить менеджер пользователя работающим после выхода из системы / после перезагрузки journalctl --user -u qwen-serve -f # просмотр логов в реальном времени systemctl --user restart qwen-serve.service # после ротации токена systemctl --user disable --now qwen-serve.service

Без loginctl enable-linger пользовательский экземпляр systemd выключается при выходе пользователя из системы и запускается снова только при следующем входе — на безголовой dev-машине демон не переживет завершение SSH-сессии. enable-linger — это то, что действительно обеспечивает работу «после перезагрузок».

Системная альтернатива (общие dev-хосты, встречается реже): поместите модуль в /etc/systemd/system/qwen-serve@.service с User=%i, управляйте через sudo systemctl enable --now qwen-serve@<имя_пользователя>.service. Тело [Service] в остальном то же — но общедоступное Environment= на этом уровне ещё более проблематично, поэтому всегда используйте EnvironmentFile=, указывающий на файл с chmod 600 пользователя. Для однопользовательских рабочих станций выбирайте пользовательский уровень + linger.

macOS: пользовательский агент launchd

Сначала найдите ваш исполняемый файл qwen и доверенные каталоги инструментов. То же ограничение, что и для systemd: ProgramArguments должен содержать абсолютный путь, а EnvironmentVariables.PATH должен включать доверенные каталоги с инструментами, которые нужны сессиям демона. Выполните which qwen gh git npm node в вашей обычной оболочке. Типичные расположения на macOS: /opt/homebrew/bin (Homebrew на Apple Silicon), /usr/local/bin (Homebrew на Intel и ручные установки), ~/.nvm/versions/node/vX.Y.Z/bin (nvm) и ~/.volta/bin (Volta). Подставьте фактические абсолютные пути ниже; launchd не раскрывает ~ или переменные оболочки.

~/Library/LaunchAgents/com.qwenlm.qwen-serve.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.qwenlm.qwen-serve</string> <key>ProgramArguments</key> <array> <!-- Выполните `which qwen`, чтобы найти абсолютный путь; launchd НЕ читает $PATH. --> <string>/PATH/TO/qwen</string> <string>serve</string> <string>--hostname</string> <string>127.0.0.1</string> <string>--port</string> <string>4170</string> <string>--workspace</string> <string>/Users/YOUR-USERNAME/project-a</string> <string>--workspace</string> <string>/Users/YOUR-USERNAME/project-b</string> </array> <!-- launchd НЕ раскрывает `~` или `$HOME` — используйте абсолютные пути. --> <key>WorkingDirectory</key> <string>/Users/YOUR-USERNAME/project-a</string> <key>EnvironmentVariables</key> <dict> <!-- НЕ КОММИТЬТЕ этот файл с реальным токеном. Также установите chmod 600 на сам plist, чтобы встроенный токен не был виден всем. --> <key>QWEN_SERVER_TOKEN</key> <string>ВСТАВЬТЕ-ВАШ-ТОКЕН-СЮДА</string> </dict> <key>RunAtLoad</key> <true/> <!-- Перезапускать только при ненулевых кодах завершения (аналог systemd Restart=on-failure). Простое `<true/>` перезапускало бы даже после корректного SIGTERM, что не позволило бы использовать `kill <pid>` для остановки — оператору пришлось бы выполнять `launchctl unload`. SuccessfulExit=false исправляет это. --> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <!-- Ограничение частоты перезапусков при постоянных сбоях (аналог systemd RestartSec=5; поведение launchd по умолчанию перезапускало бы каждую секунду). --> <key>ThrottleInterval</key> <integer>10</integer> <!-- Логировать в библиотеку пользователя, а не в /tmp. /tmp доступен для записи всем (риск симлинк-атаки на общих рабочих станциях) и очищается фоновой задачей periodic-daily через 3 дня; `~/Library/Logs/qwen-serve/` привязан к пользователю и сохраняется. launchd усекает эти файлы при каждом `load`, поэтому цикл unload→load при ротации токена стирает предыдущие диагностические логи — сделайте резервную копию, если нужно исследование после инцидента. --> <key>StandardOutPath</key> <string>/Users/YOUR-USERNAME/Library/Logs/qwen-serve/out.log</string> <key>StandardErrorPath</key> <string>/Users/YOUR-USERNAME/Library/Logs/qwen-serve/err.log</string> </dict> </plist>

Управление:

mkdir -p ~/Library/Logs/qwen-serve # только первый раз chmod 600 ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # plist содержит встроенный токен launchctl load ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist launchctl unload ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # для остановки tail -f ~/Library/Logs/qwen-serve/out.log ~/Library/Logs/qwen-serve/err.log

После редактирования plist (например, ротации токена) необходимо выполнить unload, затем load снова — launchctl не перезагружает plist автоматически, как это делает systemd daemon-reload. Обратите внимание: каждый load усекает файлы журналов, поэтому перед ротацией сохраните их, если расследуете инцидент.

Сессия tmux (интерактивное наблюдение)

Предполагается, что QWEN_SERVER_TOKEN уже экспортирован в вашем shell (см. раздел настройки выше):

tmux new -d -s qwen-serve "qwen serve --hostname 127.0.0.1 --workspace /absolute/path/project-a --workspace /absolute/path/project-b" tmux attach -t qwen-serve # просмотр логов в реальном времени; Ctrl-b d для открепления tmux kill-session -t qwen-serve

tmux new -d наследует окружение родительского shell, поэтому QWEN_SERVER_TOKEN передаётся автоматически. Лучше всего подходит, когда вы хотите время от времени смотреть stdout демона (предупреждения аутентификации, прогресс обнаружения MCP, предупреждения о медленных клиентах) без необходимости создавать системную службу. Переживает закрытие терминала, но не перезагрузку хоста.

Однострочник с nohup (быстро и грязно)

Предполагается, что QWEN_SERVER_TOKEN уже экспортирован в вашем shell:

nohup qwen serve --hostname 127.0.0.1 \ --workspace /absolute/path/project-a \ --workspace /absolute/path/project-b \ > qwen-serve.log 2>&1 & echo $! # PID демона; запомните, если хотите потом чисто завершить через `kill`

Явные абсолютные значения --workspace делают демон независимым от текущего каталога оболочки. Клиент должен выбрать одну из объявленных записей capabilities.workspaces[] и передать её cwd при создании сессии.

Подходит для разовых рабочих процессов «дай запустить это в фоне, пока я тыкаю API». Не рекомендуется для чего-либо, выходящего за рамки одной сессии — нет перезапуска при сбое, файл лога неограниченно растёт, нет чистого способа найти демона, если вы забыли PID. Для интерактивного наблюдения используйте tmux, а для того, что должно пережить перезагрузку — systemd или launchd.

Проверка работоспособности демона

curl http://127.0.0.1:4170/health # → {"status":"ok"} curl -H "Authorization: Bearer $QWEN_SERVER_TOKEN" \ http://127.0.0.1:4170/capabilities | jq .protocolVersions # набор возможностей демона

Когда настроена аутентификация (--token или QWEN_SERVER_TOKEN), каждый обычный API-маршрут, кроме /health при привязке к обычной loopback, требует Authorization: Bearer <token>; ingress вебхуков каналов всегда использует настроенный x-qwen-webhook-secret, а маршруты документов и ассетов Web Shell остаются без аутентификации. --require-auth=true требует токен при запуске и дополнительно перемещает loopback /health за bearer-гейт, не изменяя аутентификацию вебхуков. Если вы запустили демон без токена на loopback по умолчанию (путь qwen serve без конфигурации), ни один вызов не требует заголовка, и любой локальный процесс, который может достичь основного слушателя, получает полный доступ к API оператора, включая выполнение кода от имени пользователя демона. Все шаблоны выше настраивают токен, поэтому на практике требуется заголовок Authorization. Если /capabilities возвращает 401, токен в модуле/plist не совпадает с экспортированным в окружении токеном, который использует ваш curl.

Ротация токена

  1. Создайте новый токен и запишите файл окружения, на который ссылается модуль:
    openssl rand -hex 32 > ~/.qwen-serve-token chmod 600 ~/.qwen-serve-token echo "QWEN_SERVER_TOKEN=$(cat ~/.qwen-serve-token)" > ~/.qwen-serve-token-env chmod 600 ~/.qwen-serve-token-env
    (Для launchd / nohup / tmux: отредактируйте значение <string> в plist или заново выполните export QWEN_SERVER_TOKEN. Не забудьте chmod 600 для plist, если вы его пересоздаёте.)
  2. Перезапустите демона:
    • systemd: systemctl --user restart qwen-serve.service
    • launchd: launchctl unload ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist && launchctl load ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist
    • tmux / nohup: kill <pid>, затем запустите заново с новым токеном в окружении
  3. Обновите клиентские SDK / скрипты. TypeScript SDK DaemonClient автоматически читает QWEN_SERVER_TOKEN (запасной вариант PR 27) — повторно экспортируйте новое значение в shell клиента и создайте клиент заново.

Поведение при перезапуске и сбое

Семантика перезапуска менеджером служб различается для разных шаблонов:

  • systemd Restart=on-failure — перезапуск только при ненулевом коде завершения / сигнале. Чистый SIGTERM (systemctl stop) не вызывает цикл перезапуска.
  • launchd KeepAlive с SuccessfulExit=false (шаблон выше) — соответствует поведению systemd. Простое <true/> перезапускало бы даже после чистого завершения. ThrottleInterval=10 ограничивает частоту перезапусков при постоянных сбоях, аналогично RestartSec=5 в systemd.
  • tmux / nohup — автоматический перезапуск отсутствует. Сбой демона оставляет вас с мёртвым PID, пока вы не запустите заново.

В течение времени жизни одного процесса демона при отключениях клиентов восстановление происходит через SSE Last-Event-ID resume, как описано в разделе Модель устойчивости руководства пользователя — кольцо воспроизведения находится в памяти.

Перезапуск демона приводит к потере всех сессий в памяти; клиенты переподключаются и начинают с чистого листа. Устойчивость содержимого сессий (подсказки, вызовы инструментов, история диалогов) при перезапуске НЕ реализована в v0.16-alpha.

Вне рамок (откладывается до v0.16.x или позже)

  • Контейнерное развертывание — Dockerfile, docker-compose, манифесты Kubernetes, nginx + обратный прокси TLS, изоляция токенов для нескольких экземпляров. Откладывается до v0.16.x, как только появится обязательство по пилотному проекту для предприятия; иначе документация устареет из-за отсутствия проверки.
  • Федерация на разных хостах / координация нескольких демонов на одном хосте — один демон может размещать несколько зарегистрированных сред выполнения рабочих пространств, но демоны не координируются. Привязка токенов к пути экземпляра + очистка устаревших токенов откладывается до v0.16.x.
  • Общее хранилище токенов демона — Local Control использует отзываемые токены сопряжения, принадлежащие демону, но долгоживущее хранилище токенов среды выполнения остаётся BYO-token. Инфраструктура постоянного хранилища токенов откладывается до v0.16.x.
  • Собственная служба Windows (nssm, обёртка Service Control Manager) — пока используйте WSL2  и следуйте разделу про systemd выше.

См. предупреждение известные ограничения v0.16-alpha в основном руководстве пользователя для полного списка отложенных возможностей и #4175  для отслеживания статуса v0.16-alpha.

Last updated on