Modelos de inicialização local para qwen serve (v0.16-alpha)
Modelos de referência para executar qwen serve como um processo de segundo plano de longa duração em uma estação de trabalho de desenvolvedor. Complementa as limitações conhecidas da v0.16-alpha — apenas local, usuário único, BYO bearer token. Implantações conteinerizadas / multi-host / com TLS são adiadas para v0.16.x.
Público-alvo: desenvolvedores em dogfooding que desejam o daemon ativo após reinicializações, com logs indo para um local durável e uma estratégia limpa de
restart-on-failure. Se você precisa apenas do daemon durante uma única sessão de shell, o simplesqwen serve(primeiro plano, Ctrl-C para parar) é suficiente.
Gerar um bearer token (uma vez)
openssl rand -hex 32 > ~/.qwen-serve-token # gerenciado pelo usuário, NÃO é um caminho embutido
chmod 600 ~/.qwen-serve-token
export QWEN_SERVER_TOKEN="$(cat ~/.qwen-serve-token)"O caminho / nome do arquivo é sua escolha; a v0.16-alpha não gera automaticamente nem localiza um arquivo de token (adiado para v0.16.x). Consulte a seção Autenticação do guia do usuário para a configuração BYO canônica.
Mantenha este
exportescopo apenas na sessão atual do shell. Não o adicione ao~/.bashrc/~/.zshrc— uma exportação em nível de perfil expõe o bearer token a todo processo iniciado a partir desse shell (subprocessos da IDE, depuradores do navegador, scriptsnpmde projetos não relacionados). Para configurações de longa duração, use os mecanismosEnvironmentFile=do systemd /EnvironmentVariablesdo launchd abaixo — ambos limitam o token apenas ao processo do daemon.
O daemon lê o bearer token de --token <valor> na linha de comando ou da variável de ambiente QWEN_SERVER_TOKEN (espaços em branco são removidos de ambos). O construtor DaemonClient do SDK TypeScript recorre a QWEN_SERVER_TOKEN quando nenhuma opção token é passada (fallback do PR 27 — clientes com a variável de ambiente definida nunca precisam passar o valor em seus scripts).
Uma única export no nível do shell cobre tanto a inicialização do servidor quanto a construção do cliente SDK (apenas mantenha-a com escopo na sessão, conforme a observação acima).
Linux: unit systemd de usuário
Encontre seu binário
qwenprimeiro. OExecStart=do arquivo unit deve conter um caminho absoluto — os gerenciadores de serviço não leem oPATHdo seu shell. Executewhich qwenpara descobri-lo. Locais comuns:/usr/local/bin/qwen(Linuxbrew, instalações manuais),~/.nvm/versions/node/vX.Y.Z/bin/qwen(nvm),~/.fnm/aliases/default/bin/qwen(fnm),~/.volta/bin/qwen(Volta). Substitua pelo caminho real em todos os lugares onde os modelos abaixo mostram/PATH/TO/qwen.
~/.config/systemd/user/qwen-serve.service:
[Unit]
Description=Daemon do Qwen Code (loopback HTTP + SSE)
After=network.target
[Service]
Type=simple
# Substitua pelo seu projeto; %h expande para $HOME em units de usuário.
WorkingDirectory=%h/seu-projeto
# Execute `which qwen` para encontrar o caminho absoluto. systemd NÃO lê $PATH.
ExecStart=/PATH/TO/qwen serve --hostname 127.0.0.1 --port 4170
# Leia o bearer token de um arquivo chmod 600 em vez de colocá-lo inline
# na unit. `Environment=` exporia o token no arquivo da unit
# (tipicamente 644 = legível por todos). EnvironmentFile mantém o token no
# arquivo secreto de propriedade do usuário que você já criou com `chmod 600`.
EnvironmentFile=%h/.qwen-serve-token-env
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.targetCrie o arquivo env uma vez (o arquivo de token da etapa de configuração contém o valor bruto; este o envolve na forma KEY=value para que o systemd o leia como uma atribuição de ambiente):
echo "QWEN_SERVER_TOKEN=$(cat ~/.qwen-serve-token)" > ~/.qwen-serve-token-env
chmod 600 ~/.qwen-serve-token-envGerenciamento:
systemctl --user daemon-reload
systemctl --user enable --now qwen-serve.service
loginctl enable-linger "$(whoami)" # manter o gerenciador de usuário em execução após logout / reinicialização
journalctl --user -u qwen-serve -f # exibir logs em tempo real
systemctl --user restart qwen-serve.service # após rotação de token
systemctl --user disable --now qwen-serve.serviceSem loginctl enable-linger, a instância systemd em nível de usuário é desligada quando o usuário sai e só reinicia no próximo login — em uma máquina de desenvolvimento sem cabeça, o daemon não sobreviveria ao término de uma sessão SSH. enable-linger é o que faz com que “funcione entre reinicializações” de fato.
Alternativa para todo o sistema (máquinas de desenvolvimento compartilhadas, menos comum): coloque a unit em /etc/systemd/system/qwen-serve@.service com User=%i, gerencie via sudo systemctl enable --now qwen-serve@<username>.service. O corpo [Service] é o mesmo — mas a exposição de Environment= legível por todos é ainda mais problemática nesse nível, portanto sempre use EnvironmentFile= apontando para o arquivo chmod 600 do usuário. Escolha nível de usuário + linger para estações de trabalho de um único usuário.
macOS: agente launchd de usuário
Encontre seu binário
qwenprimeiro. Mesma restrição do systemd —ProgramArgumentsdeve conter um caminho absoluto. Executewhich qwenpara descobri-lo. Locais comuns no macOS:/opt/homebrew/bin/qwen(Homebrew no Apple Silicon),/usr/local/bin/qwen(Homebrew no Intel, instalações manuais),~/.nvm/versions/node/vX.Y.Z/bin/qwen(nvm),~/.volta/bin/qwen(Volta). Substitua abaixo onde o modelo mostra/PATH/TO/qwen.~/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>
<!-- Run `which qwen` to find the absolute path; launchd does NOT read $PATH. -->
<string>/PATH/TO/qwen</string>
<string>serve</string>
<string>--hostname</string>
<string>127.0.0.1</string>
<string>--port</string>
<string>4170</string>
</array>
<!-- launchd does NOT expand `~` or `$HOME` — use absolute paths. -->
<key>WorkingDirectory</key>
<string>/Users/YOUR-USERNAME/your-project</string>
<key>EnvironmentVariables</key>
<dict>
<!-- DO NOT COMMIT this file with a real token. Also chmod 600 the
plist itself so the inlined token is not world-readable. -->
<key>QWEN_SERVER_TOKEN</key>
<string>PASTE-YOUR-TOKEN-HERE</string>
</dict>
<key>RunAtLoad</key>
<true/>
<!-- Restart only on non-zero exits (matches systemd Restart=on-failure).
A bare `<true/>` would respawn even after a clean SIGTERM, making
`kill <pid>` impossible to use as a stop signal — operator would
have to `launchctl unload`. SuccessfulExit=false fixes that. -->
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<!-- Throttle restart storms on persistent failures (mirrors systemd
RestartSec=5; launchd's default would respawn every <1s). -->
<key>ThrottleInterval</key>
<integer>10</integer>
<!-- Log into the user's Library, not /tmp. /tmp is world-writable
(symlink-attack risk on shared workstations) and gets cleaned by
periodic-daily after 3 days; `~/Library/Logs/qwen-serve/` is
user-scoped and survives. launchd truncates these on every
`load`, so the unload→load token-rotation cycle wipes prior
diagnostic logs — back them up if you need post-incident
inspection. -->
<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>Gerenciamento:
mkdir -p ~/Library/Logs/qwen-serve # apenas na primeira vez
chmod 600 ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # o plist contém o token inline
launchctl load ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist
launchctl unload ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # para parar
tail -f ~/Library/Logs/qwen-serve/out.log ~/Library/Logs/qwen-serve/err.logApós editar o plist (por exemplo, rotacionando o token), você deve executar unload e depois load novamente — o launchctl não recarrega automaticamente com mudanças no plist como o systemd daemon-reload faz. Nota: cada load trunca os arquivos de log, então salve‑os se estiver investigando um incidente antes de rotacionar.
sessão tmux (supervisão interativa)
Assume que QWEN_SERVER_TOKEN já está exportado no seu shell (veja a seção de configuração acima):
tmux new -d -s qwen-serve "cd ~/seu-projeto && qwen serve --hostname 127.0.0.1"
tmux attach -t qwen-serve # veja os logs ao vivo; Ctrl-b d para desanexar
tmux kill-session -t qwen-servetmux new -d herda o ambiente do shell pai, então QWEN_SERVER_TOKEN flui automaticamente. Melhor para quando você quiser ocasionalmente observar a saída padrão do daemon (avisos de autenticação, progresso de descoberta MCP, avisos de cliente lento) sem se prender a uma unit de serviço. Sobrevive ao fechamento do terminal, mas não à reinicialização da máquina.
nohup one‑liner (rápido e sujo)
Assume que QWEN_SERVER_TOKEN já está exportado no seu shell:
nohup bash -c 'cd ~/seu-projeto && qwen serve --hostname 127.0.0.1' > qwen-serve.log 2>&1 &
echo $! # PID do daemon; capture se quiser dar `kill` depoisO encapsulamento bash -c '...' garante que o daemon se vincule a ~/seu-projeto em vez de qualquer lugar onde você executou o comando. Sem esse cd, o qwen serve usa process.cwd() por padrão e um POST /session de um cliente que espera seu diretório de projeto retorna 400 workspace_mismatch — uma armadilha silenciosa.
OK para usos pontuais do tipo “deixe isso rodando em segundo plano enquanto testo a API”. Não recomendado para algo além de uma única sessão — sem reinicialização em caso de falha, o arquivo de log cresce sem limites, nenhuma maneira limpa de encontrar o daemon se você esquecer o PID. Prefira tmux para supervisão interativa ou systemd / launchd para qualquer coisa que deva sobreviver a uma reinicialização.
Verificando se o daemon está rodando
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 # conjunto de recursos do daemonQuando a autenticação está configurada (ou seja, o daemon foi iniciado com --token / QWEN_SERVER_TOKEN definido, OU --require-auth=true), toda rota exceto /health no loopback exige Authorization: Bearer <token>. Se você iniciou o daemon sem token no padrão loopback (o caminho zero‑config do qwen serve), nenhuma chamada precisa de cabeçalho. Os templates acima configuram um token, portanto o cabeçalho Authorization é necessário na prática. Se /capabilities retornar 401, o token da unit / plist não corresponde ao token exportado no ambiente que seu curl está usando.
Rotação de token
- Gere um novo token + escreva o arquivo env que a unit referencia:
(Para os templates launchd / nohup / tmux: edite o valor
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>no plist ou re-export QWEN_SERVER_TOKEN. Não esqueça ochmod 600no plist se o regenerar.) - Reinicie o daemon:
- 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>e então reexecute com o novo token na env
- systemd:
- Atualize quaisquer SDKs / scripts dos clientes. O
DaemonClientdo SDK TypeScript lêQWEN_SERVER_TOKENautomaticamente (fallback do PR 27) — re-exporto novo valor em qualquer shell de cliente e reconstrua o cliente.
Comportamento de reinicialização e falha
As semânticas de reinicialização do gerenciador de serviço diferem entre os templates:
- systemd
Restart=on-failure— reinicia apenas em saída não-zero / sinal. Um SIGTERM limpo (systemctl stop) não dispara um loop de reinicialização. - launchd
KeepAlivecomSuccessfulExit=false(o template acima) — corresponde ao comportamento do systemd. Um<true/>simples teria reincidido mesmo após uma saída limpa.ThrottleInterval=10limita a taxa de reinicializações em rajadas para falhas persistentes, espelhando oRestartSec=5do systemd. - tmux / nohup — sem reinicialização automática. Uma falha do daemon deixa você com um PID morto até reexecutar.
Dentro de um único tempo de vida do processo do daemon, desconexões de cliente se recuperam via retomada SSE Last-Event-ID conforme a seção Modelo de durabilidade do guia do usuário — o anel de replay está na memória.
Uma reinicialização do daemon descarta todas as sessões na memória; clientes reconectam e começam do zero. A durabilidade entre reinicializações do conteúdo da sessão (prompts, chamadas de ferramenta, histórico de conversa) NÃO está na v0.16-alpha.
Fora do escopo (adiado para v0.16.x ou posterior)
- Implantação conteinerizada — Dockerfile, docker-compose, manifestos Kubernetes, nginx + proxy reverso TLS, isolamento de token multi-instância. Adiado para v0.16.x assim que um piloto empresarial for confirmado; o documento apodreceria sem ninguém validando.
- Federação entre hosts / coordenação multi-daemon em um host —
1 daemon = 1 workspace × N sessõesé aplicado. Atribuição de token por caminho de instância + limpeza de tokens obsoletos adiados para v0.16.x. - Tokens de daemon gerados automaticamente — alfa é BYO-token. Auto-geração + infraestrutura de armazenamento de tokens adiadas para v0.16.x.
- Serviço nativo do Windows (
nssm, wrapper do Service Control Manager) — por enquanto use WSL2 e siga a seção systemd acima.
Veja o aviso Limitações conhecidas da v0.16-alpha no guia do usuário principal para a lista completa de funcionalidades adiadas, e #4175 para a issue de acompanhamento do lançamento da v0.16-alpha.