# 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.
```bash
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).

## 4. Identität: YunoHost liefert sie im Cookie (nicht als Header)
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:

```ini
# .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 <SSO-User>".
- 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).
```bash
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 ~3–4 GB GPU-Speicher.
