my_voice_assistant_v3_jamulix/deploy/README.md
Dieter Schlüter 56114309f5 docs(deploy): Chatterbox-TTS-Dienst (GPU-Pinning per UUID, no_playback) ergaenzen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:58:15 +02:00

4.8 KiB
Raw Blame History

Remote-Betrieb über YunoHost (va.linix.de)

Web-UI + Mikrofon vom Handy/Browser, abgesichert per HTTPS und YunoHost-SSO. Topologie: https://va.linix.de → nginx@YunoHost (TLS, SSO) → über LAN → http://<GPU-Box>:8003 (Gateway). Der interne LAN-Hop ist unverschlüsselt (vertrautes LAN, wie bei Ollama).

Warum HTTPS Pflicht ist: Browser geben das Mikrofon (getUserMedia) nur in einem „secure context" frei also HTTPS oder http://localhost. Über einfaches http:// vom Handy gibt es kein Mikrofon.

1. Gateway auf der GPU-Box

deploy/voice-assistant.env.example/etc/voice-assistant/voice-assistant.env anpassen:

  • HOST = LAN-IP der GPU-Box (nicht 0.0.0.0), PORT=8003
  • AUTH_ENABLED=true
  • TRUSTED_AUTH_HEADER (nach Discovery, s. u.), TRUSTED_PROXY_IPS = LAN-IP von linix.de
  • ADMIN_USERS=atoor,dieterschlueter,dschlueter, SSO_LOGOUT_URL=…

Kein --forwarded-allow-ips setzen: Das Identitäts-Vertrauen prüft die direkte Quell-IP (= der Proxy). Würde uvicorn den Client aus X-Forwarded-For überschreiben, schlüge der Proxy-IP-Check fehl.

Dauerhaft laufen lassen (systemd-User-Dienst, ohne root): Vorlage deploy/voice-assistant.user.service. Die App liest .env aus dem WorkingDirectory.

cp deploy/voice-assistant.user.service ~/.config/systemd/user/voice-assistant.service
loginctl enable-linger "$USER"          # sudo -> ueberlebt Logout/Reboot
systemctl --user daemon-reload
systemctl --user enable --now voice-assistant
systemctl --user status voice-assistant
journalctl --user -u voice-assistant -f # Logs

Der llama.cpp-Container va_llm läuft separat via Docker (restart unless-stopped) und kommt nach einem Reboot von selbst hoch.

2. Drei Sicherheits-Pflichten

  1. Bind: Gateway nur an die LAN-IP (Schritt 1).
  2. Firewall: Port 8003 der GPU-Box nur von der linix.de-IP erlauben, z. B.:
    sudo ufw allow from <linix.de_LAN_IP> to any port 8003 proto tcp
    sudo ufw deny 8003
    
  3. Header überschreiben: nginx setzt den Identitäts-Header selbst; Client-Eingaben werden verworfen (siehe nginx-Conf). App-seitig zusätzlich der Proxy-IP-Check.

3. nginx auf YunoHost

deploy/va.linix.de.nginx.conf als Vorlage → auf dem YunoHost-Server unter /etc/nginx/conf.d/va.linix.de.d/assistant.conf ablegen, GPU_BOX_LAN_IP eintragen, die map $http_upgrade … einmalig im http{}-Kontext anlegen, dann nginx -t && systemctl reload nginx. Subdomain va.linix.de in YunoHost anlegen (Let's Encrypt) und per SSO schützen (nur erlaubte Tester/Gruppe).

Discovery (/api/admin/request-headers?key=<ADMIN_API_KEY> hinter dem SSO) zeigt: YunoHost reicht den Usernamen nicht als eigenen Header durch, sondern im JWT-Cookie yunohost.portal (Claim user). Das Gateway liest diesen Cookie direkt — daher:

# .env auf der GPU-Box:
TRUSTED_AUTH_COOKIE=yunohost.portal
TRUSTED_AUTH_COOKIE_CLAIM=user
TRUSTED_PROXY_IPS=<LAN-IP des YunoHost-Servers>
# optional (Härtung): HS256-Secret des Portals -> Signaturpruefung
# TRUSTED_AUTH_JWT_SECRET=...

In der nginx-Conf ist keine proxy_set_header-Identitätszeile nötig — Cookies werden ohnehin durchgereicht.

Sicherheit: Ohne TRUSTED_AUTH_JWT_SECRET wird die Cookie-Payload ungeprüft gelesen. Das ist nur sicher, weil (a) nur die Proxy-Quell-IP akzeptiert wird und (b) die Subdomain per SSO geschützt sein muss (dann lässt YunoHost nur validierte Cookies durch). Für Härtung das Portal-HS256-Secret in TRUSTED_AUTH_JWT_SECRET setzen → die Signatur wird dann selbst geprüft.

5. Test

  • https://va.linix.de/ lädt die UI, links „Angemeldet als ".
  • Text-Chat funktioniert; Mikrofon-Button nimmt auf und spielt die Antwort ab.
  • Admins (ADMIN_USERS) sehen den Admin-Bereich (Nutzerliste).

6. Chatterbox-TTS (optional, hohe Qualität + Voice-Cloning)

Eigener HTTP-Dienst (~/chatterbox-tts-cli, Conda-Env, GPU). systemd-User-Unit chatterbox-tts.service (Port 9999). Wichtig:

  • GPU pinnen per UUID (CUDA-Default-Order ist „fastest first" → Index trügt): Environment="CUDA_VISIBLE_DEVICES=GPU-<uuid der freien 3090>" (UUID via nvidia-smi -L).
  • TTS_PRELOAD_LANG=de lädt das Modell beim Start (Warm-up).
  • Der Dienst wird vom Gateway mit no_playback=true aufgerufen → er spielt nicht lokal ab, sondern liefert nur die WAV (tts_service.py unterstützt das Flag).
systemctl --user enable --now chatterbox-tts
curl -s http://127.0.0.1:9999/health     # {"status":"ok","device":"cuda:0"}

Gateway: tts_provider=chatterbox (pro Request/Session) + CHATTERBOX_VOICE = Referenz-WAV.

Chatterbox ist ~echtzeit-langsam → als Qualitäts-Option gedacht; piper bleibt der schnelle Default. Braucht ~34 GB GPU-Speicher.