Lokale Startvorlagen für qwen serve (v0.16-alpha)
Referenzvorlagen, um qwen serve als langlebigen Hintergrundprozess auf einer Entwickler-Workstation auszuführen. Ergänzt die v0.16-alpha bekannten Einschränkungen – lokal, Einzelbenutzer, BYO-Bearer-Token. Containerisierte/Multi-Host/TLS-Proxy-Bereitstellungen werden auf v0.16.x verschoben.
Zielgruppe: Entwickler, die den Daemon über Neustarts hinweg laufen lassen möchten, mit dauerhafter Protokollierung und sauberem
restart-on-failure-Verhalten. Wenn der Daemon nur für die Dauer einer einzigen Shell-Sitzung benötigt wird, reicht ein einfachesqwen serve(Vordergrund, Strg-C zum Beenden).
Ein Bearer-Token generieren (einmalig)
openssl rand -hex 32 > ~/.qwen-serve-token # benutzerverwaltet, KEIN eingebauter Pfad
chmod 600 ~/.qwen-serve-token
export QWEN_SERVER_TOKEN="$(cat ~/.qwen-serve-token)"Der Pfad/Dateiname kann frei gewählt werden; v0.16-alpha generiert oder sucht keine Token-Datei automatisch (verschoben auf v0.16.x). Siehe den Abschnitt Authentifizierung im Benutzerhandbuch für das kanonische BYO-Setup.
Binden Sie dieses
exportnur an die aktuelle Shell-Sitzung. Fügen Sie es nicht in~/.bashrc/~/.zshrcein – ein export auf Profilebene legt den Bearer-Token für jeden von dieser Shell gestarteten Prozess offen (IDE-Unterprozesse, Browser-Debugger,npm-Skripte aus anderen Projekten). Verwenden Sie für langlebige Setups die unten beschriebenen MechanismenEnvironmentFile=(systemd) /EnvironmentVariables(launchd) – beide beschränken den Token auf den Daemon-Prozess.
Der Daemon liest den Bearer-Token entweder aus --token <wert> auf der CLI oder aus der Umgebungsvariable QWEN_SERVER_TOKEN (Leerzeichen werden in beiden Fällen entfernt). Der TypeScript SDK DaemonClient-Konstruktor fällt auf QWEN_SERVER_TOKEN zurück, wenn keine token-Option übergeben wird (PR 27 Fallback – Clients mit gesetzter Umgebungsvariable müssen den Wert nie durch ihr Skript schleusen).
Ein export auf Shell-Ebene deckt sowohl den Server-Start als auch die SDK-Client-Konstruktion ab (halten Sie es nur auf die Sitzung beschränkt, gemäß dem obigen Hinweis).
Workspace-Lifecycle und Prozessgrenzen
Ein Daemon kann mehrere isolierte Workspace-Runtimes unter demselben Listener hosten.
Wiederhole --workspace mit absoluten Verzeichnissen, um explizite
Startup-Runtimes zu erstellen; das erste ist primär. Primäre und andere
explizite Startup-/statische Runtimes können ohne Neustart des Prozesses
nicht entfernt werden.
Zusätzliche Workspaces können auch während der Laufzeit des Daemons über
POST /workspaces registriert werden. Übergib persist: true, um eine
dynamische sekundäre in der Benutzer-Registrierungsdatenbank zu behalten,
sodass sie beim nächsten Start wiederhergestellt wird. Nicht vertrauenswürdige
Registrierungen bleiben für Diagnostik, begrenzte Dateilesungen und
deklarierte persistierte Lesungen sichtbar, können aber keinen ACP starten.
Dynamische und persistiert-wiederhergestellte Sekundärworkspaces sind
entfernbar: Eine normale Entfernung wird abgelehnt, während die Runtime
beschäftigt ist, während eine erzwungene Entfernung die Beendigung aktiver
Ressourcen anfordert und die logische Entfernung vor dem erneuten Hinzufügen
desselben cwd zusagt. Die Bereinigung ist begrenzt und best-effort nach dem
Persistierungs-Commit-Punkt; Fehler werden protokolliert, anstatt die
entfernte Runtime wiederherzustellen.
Die Runtime-Isolation umfasst cwd, Umgebung-Overlay, Dateisystem-/Trust-Grenze, Workspace-Services, Bridge, Voice-Lease-State, Channel-Worker und die ACP/MCP-Ressource-Grenze. Production versucht, das primäre ACP-Child vorzuwärmen und wiederholt den ersten Versuch nach einem Fehlschlag; vertrauenswürdige Sekundärworkspaces starten ihres bei Bedarf, und nicht vertrauenswürdige Sekundärworkspaces starten keinen ACP. Authentifizierung, HTTP-Rate-Limits, Listener- und Voice-Admission-Caps, Gesamt-Session-Admission, Metriken, Shutdown und der Prozess-Fehlerradius bleiben daemon-global. Verwende separate Daemons, wenn diese Prozessgrenzen unabhängig sein müssen.
Linux: systemd-Benutzer-Unit
Finden Sie zuerst Ihr
qwen-Binary. DerExecStart=in der Unit-Datei muss einen absoluten Pfad enthalten – Dienstmanager lesen nicht dasPATHIhrer Shell. Führen Siewhich qwenaus, um ihn zu finden. Häufige Orte:/usr/local/bin/qwen(Linuxbrew, manuelle Installationen),~/.nvm/versions/node/vX.Y.Z/bin/qwen(nvm),~/.fnm/aliases/default/bin/qwen(fnm),~/.volta/bin/qwen(Volta). Ersetzen Sie den tatsächlichen Pfad überall dort, wo die Vorlagen/PFAD/ZU/qwenzeigen.
~/.config/systemd/user/qwen-serve.service:
[Unit]
Description=Qwen Code-Daemon (Loopback-HTTP + SSE)
After=network.target
[Service]
Type=simple
# Ersetzen Sie durch Ihr Projekt; %h wird unter Benutzer-Units zu $HOME expandiert.
WorkingDirectory=%h/project-a
# Führen Sie `which qwen` aus, um den absoluten Pfad zu finden. systemd liest $PATH nicht.
ExecStart=/PFAD/ZU/qwen serve --hostname 127.0.0.1 --port 4170 --workspace %h/project-a --workspace %h/project-b
# Lesen Sie den Bearer-Token aus einer chmod 600-Datei, anstatt ihn in der Unit
# inline zu setzen. `Environment=` würde den Token in der Unit-Datei offenlegen
# (normalerweise 644 = weltlesbar). EnvironmentFile hält den Token in der
# benutzereigenen Secret-Datei, die Sie bereits mit `chmod 600` erstellt haben.
EnvironmentFile=%h/.qwen-serve-token-env
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.targetErstellen Sie die Umgebungsdatei einmalig (die Token-Datei aus dem Setup-Schritt enthält den rohen Wert; diese Datei verpackt ihn in KEY=value-Form, damit systemd sie als Umgebungszuweisung liest):
echo "QWEN_SERVER_TOKEN=$(cat ~/.qwen-serve-token)" > ~/.qwen-serve-token-env
chmod 600 ~/.qwen-serve-token-envVerwaltung:
systemctl --user daemon-reload
systemctl --user enable --now qwen-serve.service
loginctl enable-linger "$(whoami)" # Benutzer-Manager nach Abmeldung / über Neustart hinweg aktiv halten
journalctl --user -u qwen-serve -f # Logs mitverfolgen
systemctl --user restart qwen-serve.service # nach Token-Rotation
systemctl --user disable --now qwen-serve.serviceOhne loginctl enable-linger wird die systemd-Instanz auf Benutzerebene heruntergefahren, wenn sich der Benutzer abmeldet, und startet erst beim nächsten Login neu – auf einem headless Entwicklungsrechner würde der Daemon das Ende einer SSH-Sitzung nicht überleben. enable-linger ist das, was „über Neustarts hinweg“ tatsächlich funktionieren lässt.
Systemweite Alternative (gemeinsam genutzte Entwicklungsrechner, seltener): Legen Sie die Unit unter /etc/systemd/system/qwen-serve@.service mit User=%i ab, verwalten Sie sie via sudo systemctl enable --now qwen-serve@<benutzername>.service. Ansonsten gleicher [Service]-Rumpf – aber weltlesbare Environment=-Offenlegung ist auf dieser Ebene noch problematischer, verwenden Sie daher immer EnvironmentFile= mit Verweis auf die chmod 600-Datei des Benutzers. Wählen Sie für Einzelbenutzer-Workstations die Benutzerebene + linger.
macOS: launchd-Benutzer-Agent
Finden Sie zuerst Ihr
qwen-Binary. Gleiche Einschränkung wie bei systemd –ProgramArgumentsmuss einen absoluten Pfad enthalten. Führen Siewhich qwenaus, um ihn zu finden. Häufige Orte auf macOS:/opt/homebrew/bin/qwen(Homebrew auf Apple Silicon),/usr/local/bin/qwen(Homebrew auf Intel, manuelle Installationen),~/.nvm/versions/node/vX.Y.Z/bin/qwen(nvm),~/.volta/bin/qwen(Volta). Ersetzen Sie unten, wo die Vorlage/PFAD/ZU/qwenzeigt.
~/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>
<!-- Führen Sie `which qwen` aus, um den absoluten Pfad zu finden; launchd liest $PATH nicht. -->
<string>/PFAD/ZU/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/IHR-BENUTZERNAME/project-a</string>
<string>--workspace</string>
<string>/Users/IHR-BENUTZERNAME/project-b</string>
</array>
<!-- launchd expandiert `~` oder `$HOME` NICHT – absolute Pfade verwenden. -->
<key>WorkingDirectory</key>
<string>/Users/IHR-BENUTZERNAME/project-a</string>
<key>EnvironmentVariables</key>
<dict>
<!-- COMMITEN Sie diese Datei NICHT mit einem echten Token. Setzen Sie außerdem chmod 600
auf die plist selbst, damit der inline-Token nicht weltlesbar ist. -->
<key>QWEN_SERVER_TOKEN</key>
<string>TOKEN-HIER-EINFÜGEN</string>
</dict>
<key>RunAtLoad</key>
<true/>
<!-- Nur bei Nicht-Null-Exit neu starten (entspricht systemd Restart=on-failure).
Ein bloßes `<true/>` würde auch nach einem sauberen SIGTERM neu starten, was
`kill <pid>` als Stoppsignal unmöglich macht – der Bediener müsste
stattdessen `launchctl unload` ausführen. SuccessfulExit=false behebt das. -->
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<!-- Drosselt Neustart-Stürme bei dauerhaften Fehlern (entspricht systemd
RestartSec=5; launchds Standard würde <1s neu starten). -->
<key>ThrottleInterval</key>
<integer>10</integer>
<!-- Loggen in die Benutzer-Bibliothek, nicht nach /tmp. /tmp ist weltbeschreibbar
(Symlink-Angriffsrisiko auf gemeinsam genutzten Workstations) und wird von
periodic-daily nach 3 Tagen bereinigt; `~/Library/Logs/qwen-serve/` ist
benutzerbezogen und überlebt. launchd kürzt diese bei jedem `load`,
daher löscht der Unload→Load-Token-Rotationszyklus vorherige Diagnose-Logs –
sichern Sie sie, wenn Sie eine Untersuchung nach einem Vorfall benötigen. -->
<key>StandardOutPath</key>
<string>/Users/IHR-BENUTZERNAME/Library/Logs/qwen-serve/out.log</string>
<key>StandardErrorPath</key>
<string>/Users/IHR-BENUTZERNAME/Library/Logs/qwen-serve/err.log</string>
</dict>
</plist>Verwaltung:
mkdir -p ~/Library/Logs/qwen-serve # nur beim ersten Mal
chmod 600 ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # plist enthält den inline-Token
launchctl load ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist
launchctl unload ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # zum Stoppen
tail -f ~/Library/Logs/qwen-serve/out.log ~/Library/Logs/qwen-serve/err.logNach einer Bearbeitung der plist (z. B. Token-Rotation) müssen Sie unload und dann load ausführen – launchctl lädt plist-Änderungen nicht automatisch neu wie systemd daemon-reload. Hinweis: Jedes load kürzt die Logdateien, also sichern Sie sie, wenn Sie vor der Rotation einen Vorfall untersuchen.
tmux-Sitzung (interaktive Überwachung)
Setzt voraus, dass QWEN_SERVER_TOKEN bereits in Ihrer Shell exportiert ist (siehe Setup-Abschnitt oben):
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 # Live-Logs anzeigen; Strg-b d zum Trennen
tmux kill-session -t qwen-servetmux new -d erbt die Umgebung der Eltern-Shell, sodass QWEN_SERVER_TOKEN automatisch durchfließt. Am besten geeignet, wenn Sie gelegentlich die stdout des Daemons (Auth-Warnungen, MCP-Erkennungsfortschritt, Warnungen zu langsamen Clients) sehen möchten, ohne sich auf eine Service-Unit festzulegen. Überlebt das Schließen des Terminals, aber nicht einen Host-Neustart.
nohup-Einzeiler (schnell und schmutzig)
Setzt voraus, dass QWEN_SERVER_TOKEN bereits in Ihrer Shell exportiert ist:
nohup qwen serve --hostname 127.0.0.1 \
--workspace /absolute/path/project-a \
--workspace /absolute/path/project-b \
> qwen-serve.log 2>&1 &
echo $! # Daemon-PID; merken, wenn Sie später sauber `kill` möchtenExplizite absolute --workspace-Werte halten den Daemon unabhängig vom aktuellen Verzeichnis der Shell. Ein Client sollte einen der angezeigten capabilities.workspaces[]-Einträge auswählen und dessen cwd beim Erstellen einer Sitzung übergeben.
In Ordnung für einmalige „Lass mich das im Hintergrund laufen lassen, während ich an der API herumprobiere”-Workflows. Nicht empfohlen für alles, was über eine einzelne Sitzung hinausgeht – kein Neustart bei Absturz, Logdatei wächst unbegrenzt, keine saubere Möglichkeit, den Daemon zu finden, wenn Sie die PID vergessen haben. Bevorzugen Sie tmux für interaktive Überwachung oder systemd/launchd für alles, was einen Neustart überdauern soll.
Überprüfen, ob der Daemon läuft
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 # Feature-Set des DaemonsWenn Auth konfiguriert ist (d. h. der Daemon wurde mit --token / gesetztem QWEN_SERVER_TOKEN gestartet, ODER --require-auth=true), benötigt jede Route außer /health auf Loopback-Binds Authorization: Bearer <token>. Wenn du den Daemon ohne Token auf dem Loopback-Standard gestartet hast (der qwen serve-Nullkonfigurationspfad), benötigt keiner der Aufrufe einen Header. Die obigen Vorlagen konfigurieren alle einen Token, daher wird der Authorization-Header in der Praxis benötigt. Wenn /capabilities 401 zurückgibt, stimmt der Token in der Unit/plist nicht mit dem per export gesetzten Token überein, den dein curl verwendet.
Token-Rotation
- Generieren Sie einen neuen Token + schreiben Sie die Umgebungsdatei, auf die die Unit verweist:
(Für die launchd-/nohup-/tmux-Vorlagen: Bearbeiten Sie den
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<string>-Wert in der plist oder exportieren SieQWEN_SERVER_TOKENneu. Vergessen Sie nichtchmod 600auf die plist, wenn Sie sie neu generieren.) - Starten Sie den Daemon neu:
- 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>und dann mit dem neuen Token in der Umgebung neu ausführen
- systemd:
- Aktualisieren Sie alle Client-SDKs/Skripte. Der TypeScript SDK
DaemonClientliestQWEN_SERVER_TOKENautomatisch (PR 27 Fallback) – exportieren Sie den neuen Wert in jeder Client-Shell neu und erstellen Sie den Client neu.
Neustart- und Absturzverhalten
Die Neustart-Semantik der Dienstmanager unterscheidet sich zwischen den Vorlagen:
- systemd
Restart=on-failure– Neustart nur bei Nicht-Null-Exit/Signal. Ein sauberes SIGTERM (systemctl stop) löst keine Neustart-Schleife aus. - launchd
KeepAlivemitSuccessfulExit=false(obige Vorlage) – entspricht systemd-Verhalten. Ein bloßes<true/>hätte auch nach einem sauberen Exit neu gestartet.ThrottleInterval=10begrenzt die Rate von Neustart-Stürmen bei dauerhaften Fehlern, analog zu systemdsRestartSec=5. - tmux / nohup – kein automatischer Neustart. Ein Daemon-Absturz hinterlässt eine tote PID, bis Sie ihn neu starten.
Innerhalb der Lebensdauer eines einzelnen Daemon-Prozesses erholen sich Client-Trennungen über SSE-Last-Event-ID-Resume gemäß dem Abschnitt Durabilitätsmodell im Benutzerhandbuch – der Wiedergabe-Ring befindet sich im Arbeitsspeicher.
Ein Neustart des Daemons verwirft alle In-Memory-Sitzungen; Clients verbinden sich neu und beginnen frisch. Die dauerhafte Speicherung von Sitzungsinhalten (Prompts, Tool-Aufrufe, Gesprächsverlauf) über Neustarts hinweg ist NICHT in v0.16-alpha.
Außerhalb des Rahmens (verschoben auf v0.16.x oder später)
- Containerisierte Bereitstellung – Dockerfile, docker-compose, Kubernetes-Manifeste, nginx + TLS-Reverse-Proxy, Multi-Instanz-Token-Isolation. Verschiebt sich auf v0.16.x, sobald ein Enterprise-Pilot festgelegt ist; das Dokument würde sonst veralten, da es niemand validiert.
- Hostübergreifende Föderation / Multi-Daemon-Koordination auf einem Host – ein Daemon kann mehrere registrierte Workspace-Runtimes hosten, aber Daemons koordinieren sich nicht untereinander. Token-Verknüpfung auf Instanzebene + Bereinigung alter Token verschiebt sich auf v0.16.x.
- Allgemeine Daemon-Token-Speicherung – Local Control verwendet revokierbare, Daemon-besessene Pairing-Tokens, aber die langlebige Runtime-Token-Speicherung bleibt BYO-Token. Persistente Token-Store-Infrastruktur wird auf v0.16.x verschoben.
- Windows-nativer Dienst (
nssm, Service Control Manager-Wrapper) – verwenden Sie vorerst WSL2 und folgen Sie dem obigen systemd-Abschnitt.
Siehe den Hinweis zu den v0.16-alpha bekannten Einschränkungen im Hauptbenutzerhandbuch für die vollständige Liste der verschobenen Funktionen und #4175 für das v0.16-alpha-Rollout-Tracking-Issue.