feat(deploy): Installations-Skript + Anleitung für natives YunoHost-SSO (jamulix.net)
Alternative zum Authelia-Target (voice.jamulix.de): eigenständiges Ein-Maschinen-Setup, das YunoHosts Portal-Cookie (yunohost.portal) direkt nutzt statt Authelia. deploy/install_va_on_yunohost.sh automatisiert Code- Deploy, venv, systemd-Service, YunoHost-Domain und nginx-Reverse-Proxy inkl. WebSocket-Support; INSTALLATION_AUF_YUNOHOST_SERVER.md dokumentiert Ablauf, Sicherheitsmodell und offene manuelle Schritte. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
55d07187bb
commit
6ab24977f1
2 changed files with 336 additions and 0 deletions
141
INSTALLATION_AUF_YUNOHOST_SERVER.md
Normal file
141
INSTALLATION_AUF_YUNOHOST_SERVER.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# Installation auf einem YunoHost-Server (natives SSO, ohne Authelia)
|
||||
|
||||
Alternative Installationsvariante zu `DEPLOYMENT.md` (jamulix.de, Authelia 2FA).
|
||||
Hier läuft die Anmeldung stattdessen über **YunoHosts eigenes Portal-SSO** — kein
|
||||
separat installiertes/gepflegtes Authelia nötig. Zielsystem in diesem Fall:
|
||||
Debian-Server mit YunoHost, IP `82.153.225.151`, Domain `jamulix.net`.
|
||||
|
||||
Repository: `https://kitux.de/forgejo/dschlueter/my_voice_assistant_v3_jamulix`
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Browser --HTTPS--> nginx (YunoHost, TLS + Portal-SSO)
|
||||
|
|
||||
v
|
||||
127.0.0.1:8003 (Gateway, systemd, User "voice")
|
||||
```
|
||||
|
||||
Gateway und nginx laufen auf **derselben Maschine** — anders als beim bereits
|
||||
produktiven `va.linix.de`-Aufbau (`deploy/README.md`), wo Gateway und
|
||||
YunoHost-Reverse-Proxy auf zwei getrennten Rechnern im LAN sitzen. Das
|
||||
vereinfacht die Firewall-Frage (kein LAN-Hop, kein offener Port nach außen)
|
||||
und erlaubt zusätzliche Härtung (siehe unten).
|
||||
|
||||
**Identität:** YunoHost reicht den eingeloggten Benutzernamen nicht als
|
||||
HTTP-Header, sondern im **JWT-Cookie `yunohost.portal`** (Claim `user`)
|
||||
durch — empirisch bestätigt bei der `va.linix.de`-Installation. Das Gateway
|
||||
liest diesen Cookie direkt (`app/auth.py`, Einstellungen `TRUSTED_AUTH_COOKIE`
|
||||
/ `TRUSTED_AUTH_COOKIE_CLAIM` / `TRUSTED_AUTH_JWT_SECRET`, siehe
|
||||
`.env.example`). Ein zusätzlicher YunoHost-Header ist **nicht** nötig.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- SSH-Root-Zugriff auf den YunoHost-Server.
|
||||
- DNS: A-Record der gewünschten Subdomain (z. B. `voice.jamulix.net`) zeigt
|
||||
bereits auf `82.153.225.151`.
|
||||
- Dein YunoHost-Benutzername (das Konto, mit dem du dich später einloggst),
|
||||
z. B. `dschlueter`.
|
||||
- Ein OpenRouter-API-Key (Cloud-Profil: STT/LLM/TTS laufen über OpenRouter,
|
||||
keine lokale GPU/Modelle nötig).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
ssh root@82.153.225.151
|
||||
```
|
||||
|
||||
Skript auf den Server holen (z. B. per `scp` von diesem Repo, oder direkt aus
|
||||
Forgejo, falls Netzzugriff besteht) und ausführen:
|
||||
|
||||
```bash
|
||||
scp deploy/install_va_on_yunohost.sh root@82.153.225.151:~/
|
||||
ssh root@82.153.225.151
|
||||
sudo bash ~/install_va_on_yunohost.sh voice.jamulix.net dschlueter
|
||||
```
|
||||
|
||||
Das Skript (`deploy/install_va_on_yunohost.sh`) fragt einmalig nach dem
|
||||
`OPENROUTER_API_KEY` (Enter = später manuell in
|
||||
`/etc/voice-assistant/voice-assistant.env` nachtragen) und erledigt:
|
||||
|
||||
1. System-Pakete (`git`, `python3-venv`, `python3-dev`, `build-essential`) +
|
||||
Python-Versionscheck (Projekt braucht `>=3.11`).
|
||||
2. System-User `voice` (falls nicht vorhanden).
|
||||
3. `git clone` des Forgejo-Repos nach `/opt/voice-assistant`
|
||||
(bei erneutem Lauf: `git pull --ff-only`).
|
||||
4. Python-venv + Abhängigkeiten (nur Cloud-Profil — kein `faster-whisper`/
|
||||
`piper`/GPU-Kram nötig).
|
||||
5. `/etc/voice-assistant/voice-assistant.env`: `HOST=127.0.0.1`, `PORT=8003`,
|
||||
`VA_PROFILE=cloud`, `AUTH_ENABLED=true`, generiertes `ADMIN_API_KEY`,
|
||||
`TRUSTED_AUTH_COOKIE=yunohost.portal`, `TRUSTED_AUTH_COOKIE_CLAIM=user`,
|
||||
`TRUSTED_AUTH_JWT_SECRET` (aus `/etc/yunohost/.ssowat_cookie_secret` —
|
||||
dadurch wird die Cookie-Signatur echt geprüft, nicht nur ungeprüft
|
||||
gelesen), `TRUSTED_PROXY_IPS=127.0.0.1`, `ADMIN_USERS=<dein Name>`.
|
||||
Existiert die Datei schon, wird sie **nicht** überschrieben.
|
||||
6. systemd-Unit `voice-assistant.service` (`Restart=always`), aktiviert +
|
||||
gestartet.
|
||||
7. `yunohost domain add <subdomain>` (übersprungen, falls schon registriert).
|
||||
8. nginx-Snippet unter `/etc/nginx/conf.d/<subdomain>.d/voice-assistant.conf`
|
||||
(überlebt `yunohost tools regen-conf nginx`, da app-eigener Pfad) mit
|
||||
WebSocket-Upgrade-Headern für `/ws/voice` und `/ws/chat` (Mikrofon-Button).
|
||||
|
||||
## Manuell danach (bewusst nicht automatisiert)
|
||||
|
||||
```bash
|
||||
dig +short voice.jamulix.net # zeigt auf 82.153.225.151?
|
||||
yunohost domain cert-install voice.jamulix.net # Let's-Encrypt-Zertifikat
|
||||
```
|
||||
|
||||
Danach einmal im Browser einloggen (setzt das `yunohost.portal`-Cookie):
|
||||
|
||||
```
|
||||
https://voice.jamulix.net/yunohost/sso/
|
||||
```
|
||||
|
||||
und anschließend `https://voice.jamulix.net/` öffnen — die App sollte dich
|
||||
automatisch als `dschlueter` erkennen (Admin-Bereich sichtbar). Fehlt der
|
||||
`OPENROUTER_API_KEY`: in `/etc/voice-assistant/voice-assistant.env`
|
||||
nachtragen, dann `systemctl restart voice-assistant`.
|
||||
|
||||
## Sicherheitsmodell (Unterschied zu `va.linix.de`)
|
||||
|
||||
Die Subdomain ist **nicht** über YunoHosts Permission-System gesperrt — es
|
||||
gibt (noch) keinen automatischen Redirect zur Login-Seite bei Erstaufruf, wie
|
||||
ihn ein echtes YunoHost-App-Package via SSOwat-Permission bekäme. Sicherheit
|
||||
kommt stattdessen aus zwei Faktoren:
|
||||
|
||||
1. **`TRUSTED_PROXY_IPS=127.0.0.1`** — die Identität aus dem Cookie wird nur
|
||||
akzeptiert, wenn die Anfrage direkt vom lokalen nginx kommt (kein anderer
|
||||
Rechner kann sich als "der Proxy" ausgeben).
|
||||
2. **`TRUSTED_AUTH_JWT_SECRET`** — die Cookie-Signatur wird mit YunoHosts
|
||||
echtem Portal-Secret geprüft (HS256). Ein gefälschter `yunohost.portal`-
|
||||
Cookie eines Angreifers wird verworfen.
|
||||
|
||||
Das ist strenger als bei `va.linix.de` (dort blieb das Secret mangels
|
||||
Root-Zugriff auf dem Gateway-Rechner offen). Praktischer Nachteil: ohne
|
||||
SSOwat-Redirect musst du dich **zuerst** manuell über `/yunohost/sso/`
|
||||
einloggen, bevor `/` dich erkennt — es gibt keinen automatischen
|
||||
"nicht eingeloggt → zur Login-Seite"-Sprung.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Ursache | Fix |
|
||||
|---|---|---|
|
||||
| `yunohost: command not found` | Skript läuft auf dem falschen Rechner | Nur auf dem YunoHost-Server selbst ausführen |
|
||||
| App erkennt dich nicht als eingeloggt | `yunohost.portal`-Cookie fehlt/falsche Domain | Erst `https://<domain>/yunohost/sso/` besuchen; Cookie-Domain im Browser-DevTools prüfen |
|
||||
| 401 trotz Login | `TRUSTED_AUTH_JWT_SECRET` falsch/veraltet (Secret rotiert?) | `/etc/yunohost/.ssowat_cookie_secret` erneut auslesen und in die env eintragen |
|
||||
| Mikrofon-Button reagiert nicht | WebSocket-Upgrade fehlt in nginx | `/etc/nginx/conf.d/<domain>.d/voice-assistant.conf` prüfen, `nginx -t && systemctl reload nginx` |
|
||||
| Zertifikat schlägt fehl | DNS zeigt noch nicht auf den Server | `dig +short <domain>` prüfen, ggf. TTL abwarten |
|
||||
| Dienst startet nicht | Python < 3.11 oder fehlende venv-Pakete | `journalctl -u voice-assistant -f`; Python-Version prüfen (`python3 --version`) |
|
||||
|
||||
Logs: `journalctl -u voice-assistant -f`
|
||||
|
||||
## Ausblick (noch nicht umgesetzt)
|
||||
|
||||
Geplant: Einbindung als `va.jamulix.net` über YunoHosts eigenes
|
||||
**„Redirect"-App-Tool** statt des manuellen nginx-Snippets, inklusive einer
|
||||
eigenen **„Alexis"-Kachel** im YunoHost-Dashboard/Portal. Das würde echtes
|
||||
SSOwat-Permission-Handling (automatischer Login-Redirect, Portal-Kachel)
|
||||
bringen, erfordert aber ein YunoHost-App-Package (`manifest.toml` +
|
||||
Install-/Remove-/Upgrade-Skripte) statt des heutigen Ad-hoc-Skripts. Folgt in
|
||||
einem späteren Schritt.
|
||||
195
deploy/install_va_on_yunohost.sh
Executable file
195
deploy/install_va_on_yunohost.sh
Executable file
|
|
@ -0,0 +1,195 @@
|
|||
#!/usr/bin/env bash
|
||||
# Installiert den Voice-Assistant-Gateway auf einem YunoHost-Server und bindet
|
||||
# die Identitaet ueber YunoHosts eigenes Portal-SSO an (Cookie "yunohost.portal"),
|
||||
# analog zur bereits produktiven va.linix.de-Installation (siehe deploy/README.md),
|
||||
# aber als Ein-Maschinen-Variante OHNE Authelia - nur natives YunoHost-Login.
|
||||
#
|
||||
# Voraussetzungen (manuell, VOR dem Ausfuehren):
|
||||
# - SSH-Login auf dem YunoHost-Server als root (oder sudo-faehiger User).
|
||||
# - DNS: A-Record der Subdomain zeigt bereits auf die Server-IP.
|
||||
# - Du kennst deinen YunoHost-Benutzernamen (Authelia/LDAP-Konto in YunoHost),
|
||||
# z. B. "dschlueter".
|
||||
#
|
||||
# Aufruf:
|
||||
# sudo bash install_va_on_yunohost.sh <subdomain> <yunohost-user[,user2,...]>
|
||||
#
|
||||
# Beispiel:
|
||||
# sudo bash install_va_on_yunohost.sh voice.jamulix.net dschlueter
|
||||
#
|
||||
# Nach dem Lauf noch manuell noetig (siehe Ausgabe am Ende):
|
||||
# - Zertifikat: yunohost domain cert-install <subdomain>
|
||||
# - OPENROUTER_API_KEY nachtragen, falls beim Lauf leer gelassen.
|
||||
# - Einmal ueber https://<domain>/yunohost/sso/ einloggen, dann die App oeffnen.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN="${1:?Nutzung: $0 <subdomain, z.B. voice.jamulix.net> <yunohost-user[,user2,...]>}"
|
||||
ADMIN_USERS="${2:?Nutzung: $0 <subdomain> <yunohost-user[,user2,...]>}"
|
||||
|
||||
REPO_URL="https://kitux.de/forgejo/dschlueter/my_voice_assistant_v3_jamulix.git"
|
||||
APP_DIR="/opt/voice-assistant"
|
||||
APP_USER="voice"
|
||||
PORT="8003"
|
||||
ENV_DIR="/etc/voice-assistant"
|
||||
ENV_FILE="$ENV_DIR/voice-assistant.env"
|
||||
DB_DIR="/var/lib/voice-assistant"
|
||||
COOKIE_SECRET_FILE="/etc/yunohost/.ssowat_cookie_secret"
|
||||
|
||||
# --- Vorbedingungen ----------------------------------------------------------
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Bitte als root ausfuehren (sudo bash $0 ...)" >&2
|
||||
exit 1
|
||||
fi
|
||||
command -v yunohost >/dev/null 2>&1 || {
|
||||
echo "yunohost-CLI nicht gefunden - laeuft das hier wirklich auf dem YunoHost-Server?" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "== 1/8: System-Pakete =="
|
||||
apt-get update -qq
|
||||
apt-get install -y git python3-venv python3-pip python3-dev build-essential >/dev/null
|
||||
|
||||
PYVER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')"
|
||||
if [ "$(printf '%s\n' "3.11" "$PYVER" | sort -V | head -n1)" != "3.11" ]; then
|
||||
echo "WARNUNG: python3 ist $PYVER, das Projekt braucht >=3.11." >&2
|
||||
echo " Pruefe 'apt list -a python3.11' bzw. dein Debian-Release; danach das venv" >&2
|
||||
echo " in Schritt 4 manuell mit 'python3.11 -m venv .venv' anlegen." >&2
|
||||
fi
|
||||
|
||||
echo "== 2/8: System-User '$APP_USER' =="
|
||||
id -u "$APP_USER" >/dev/null 2>&1 || useradd --system --create-home --shell /usr/sbin/nologin "$APP_USER"
|
||||
|
||||
echo "== 3/8: Code holen ($REPO_URL) =="
|
||||
if [ -d "$APP_DIR/.git" ]; then
|
||||
sudo -u "$APP_USER" git -C "$APP_DIR" pull --ff-only
|
||||
else
|
||||
mkdir -p "$APP_DIR"
|
||||
chown "$APP_USER:$APP_USER" "$APP_DIR"
|
||||
sudo -u "$APP_USER" git clone "$REPO_URL" "$APP_DIR"
|
||||
fi
|
||||
|
||||
echo "== 4/8: Python-venv + Abhaengigkeiten (Cloud-Profil, kein lokales STT/TTS) =="
|
||||
VENV_SETUP=/tmp/va_venv_setup.sh
|
||||
cat > "$VENV_SETUP" <<'INNER'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$1"
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -U pip
|
||||
.venv/bin/pip install -e '.'
|
||||
INNER
|
||||
chmod +x "$VENV_SETUP"
|
||||
sudo -u "$APP_USER" "$VENV_SETUP" "$APP_DIR"
|
||||
rm -f "$VENV_SETUP"
|
||||
|
||||
echo "== 5/8: Konfiguration ($ENV_FILE) =="
|
||||
mkdir -p "$ENV_DIR" "$DB_DIR"
|
||||
chown "$APP_USER:$APP_USER" "$DB_DIR"
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
ADMIN_API_KEY="$(openssl rand -hex 32)"
|
||||
read -srp "OpenRouter API Key (OPENROUTER_API_KEY, Enter = spaeter manuell nachtragen): " OPENROUTER_API_KEY
|
||||
echo
|
||||
COOKIE_SECRET=""
|
||||
if [ -r "$COOKIE_SECRET_FILE" ]; then
|
||||
COOKIE_SECRET="$(cat "$COOKIE_SECRET_FILE")"
|
||||
else
|
||||
echo " WARNUNG: $COOKIE_SECRET_FILE nicht lesbar - TRUSTED_AUTH_JWT_SECRET bleibt leer" >&2
|
||||
echo " (Cookie wird dann ungeprueft gelesen, siehe deploy/README.md Abschnitt 4)." >&2
|
||||
fi
|
||||
cat > "$ENV_FILE" <<EOF
|
||||
# Erzeugt von install_va_on_yunohost.sh am $(date -Is)
|
||||
HOST=127.0.0.1
|
||||
PORT=$PORT
|
||||
OPENROUTER_API_KEY=$OPENROUTER_API_KEY
|
||||
VA_PROFILE=cloud
|
||||
AUTH_ENABLED=true
|
||||
ADMIN_API_KEY=$ADMIN_API_KEY
|
||||
|
||||
# Identitaet: natives YunoHost-Portal-SSO (Cookie "yunohost.portal"), KEIN Authelia.
|
||||
# nginx und Gateway laufen auf derselben Maschine -> Quell-IP ist immer 127.0.0.1.
|
||||
TRUSTED_AUTH_COOKIE=yunohost.portal
|
||||
TRUSTED_AUTH_COOKIE_CLAIM=user
|
||||
TRUSTED_AUTH_JWT_SECRET=$COOKIE_SECRET
|
||||
TRUSTED_PROXY_IPS=127.0.0.1
|
||||
ADMIN_USERS=$ADMIN_USERS
|
||||
SSO_LOGOUT_URL=https://$DOMAIN/yunohost/sso/?action=logout
|
||||
|
||||
DB_PATH=$DB_DIR/voice-assistant.db
|
||||
EOF
|
||||
chown root:"$APP_USER" "$ENV_FILE"
|
||||
chmod 640 "$ENV_FILE"
|
||||
else
|
||||
echo " $ENV_FILE existiert bereits - unveraendert gelassen."
|
||||
fi
|
||||
|
||||
echo "== 6/8: systemd-Service =="
|
||||
cat > /etc/systemd/system/voice-assistant.service <<EOF
|
||||
[Unit]
|
||||
Description=Voice Assistant Gateway
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$APP_USER
|
||||
WorkingDirectory=$APP_DIR
|
||||
EnvironmentFile=$ENV_FILE
|
||||
ExecStart=$APP_DIR/.venv/bin/uvicorn app.main:app --host \${HOST} --port \${PORT}
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now voice-assistant.service
|
||||
|
||||
echo "== 7/8: YunoHost-Domain '$DOMAIN' =="
|
||||
if yunohost domain list 2>/dev/null | grep -qxF " - $DOMAIN"; then
|
||||
echo " Domain ist bereits registriert."
|
||||
else
|
||||
yunohost domain add "$DOMAIN"
|
||||
fi
|
||||
|
||||
echo "== 8/8: nginx-Reverse-Proxy =="
|
||||
mkdir -p "/etc/nginx/conf.d/$DOMAIN.d"
|
||||
if [ ! -f /etc/nginx/conf.d/websocket-upgrade.conf ]; then
|
||||
cat > /etc/nginx/conf.d/websocket-upgrade.conf <<'EOF'
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
cat > "/etc/nginx/conf.d/$DOMAIN.d/voice-assistant.conf" <<EOF
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:$PORT;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
|
||||
# WebSocket-Upgrade (zwingend fuer /ws/voice, /ws/chat - Mikrofon-Button):
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection \$connection_upgrade;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
|
||||
# Identitaet: KEIN eigener Header noetig, das Cookie "yunohost.portal" wird
|
||||
# ohnehin durchgereicht; das Gateway liest/prueft es selbst (siehe .env).
|
||||
}
|
||||
EOF
|
||||
nginx -t && systemctl reload nginx
|
||||
|
||||
echo
|
||||
echo "=================================================================="
|
||||
echo "Fertig. Manuell noch offen:"
|
||||
echo " 1) DNS pruefen: dig +short $DOMAIN (muss auf diese Server-IP zeigen)"
|
||||
echo " 2) Zertifikat: yunohost domain cert-install $DOMAIN"
|
||||
echo " 3) Login testen: https://$DOMAIN/yunohost/sso/ (einloggen als $ADMIN_USERS),"
|
||||
echo " danach https://$DOMAIN/ oeffnen - sollte dich erkennen."
|
||||
echo " 4) Falls OPENROUTER_API_KEY leer gelassen wurde: in $ENV_FILE nachtragen,"
|
||||
echo " dann: systemctl restart voice-assistant"
|
||||
echo " Logs: journalctl -u voice-assistant -f"
|
||||
echo "=================================================================="
|
||||
Loading…
Add table
Add a link
Reference in a new issue