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:
Dieter Schlüter 2026-07-01 10:26:12 +02:00
commit 6ab24977f1
2 changed files with 336 additions and 0 deletions

195
deploy/install_va_on_yunohost.sh Executable file
View 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 "=================================================================="