my_voice_assistant_v3/deploy
Dieter Schlüter 77026bff1b deploy: Gateway als systemd-User-Dienst (Vorlage + Anleitung)
Vorlage deploy/voice-assistant.user.service + Anleitung (loginctl enable-linger,
systemctl --user). Laeuft ohne root und ueberlebt Logout/Reboot; .env wird aus dem
WorkingDirectory gelesen. va_llm-Container kommt via Docker restart-policy hoch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:09:23 +02:00
..
README.md deploy: Gateway als systemd-User-Dienst (Vorlage + Anleitung) 2026-06-18 09:09:23 +02:00
va.linix.de.nginx.conf feat(auth): Forward-Auth via JWT-Cookie (YunoHost yunohost.portal) 2026-06-18 08:44:33 +02:00
voice-assistant.env.example feat(web): Remote-Web-UI mit Mikrofon + Forward-Auth (YunoHost-SSO) 2026-06-18 04:37:16 +02:00
voice-assistant.service Initial commit: Voice Assistant Gateway mit Konfig-/Routing-Fundament 2026-06-17 01:48:56 +02:00
voice-assistant.user.service deploy: Gateway als systemd-User-Dienst (Vorlage + Anleitung) 2026-06-18 09:09:23 +02:00

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).