docs+install: vollständiger Installer + aktuelle README

Mac-Worker:
- install-launchagent.sh legt jetzt selbst venv + Dependencies an, statt
  das vorauszusetzen — ein Befehl reicht für komplette Erstinstallation
- modernes launchctl bootout/bootstrap/kickstart statt unload/load
  (alte Variante wirft auf neueren macOS-Versionen "Input/output error 5")
- neuer Sub-Befehl "restart" zum Neustarten ohne Reinstall
- neues update.sh: git pull + pip install + kickstart + Health in einem Schritt

LXC:
- install.sh ALLOWED_EXTS-Check verhindert wiederholtes ",webm"-Anhängen
- bessere Abschluss-Ausgabe mit echter Host-IP, Update-Anweisung, Diagnose-Hinweis

Docs:
- Haupt-README: aktueller Funktionsstand (Auth, PDF, Profile, Mobile/PWA,
  Diagnose, Retry alle als done markiert), Installation auf neuen Stand gebracht
- Profile-Tabelle mit allen 17 Profilen
- API-Listen vervollständigt (diag, preload, log, pdf, profiles)
- Datenfluss-Skizze mit Preload + Heartbeat + X-Job-Id beschrieben

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-14 20:36:35 +00:00
co-authored by Claude Opus 4.7
parent f408a555f8
commit bc497a1665
6 changed files with 390 additions and 174 deletions
+44 -40
View File
@@ -2,19 +2,14 @@
Stateless FastAPI-Service auf dem Mac. Macht die schwere Arbeit: Whisper-Transkription und Ollama-Zusammenfassung. Wird vom LXC-Frontend per HTTP angesprochen.
## Setup (Apple Silicon)
## Voraussetzungen
```bash
cd mac-worker
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
cp .env.example .env
# Bei Bedarf .env anpassen
```
- Apple Silicon (M1/M2/M3/M4), 16 GB RAM minimum
- macOS mit Xcode-CLI-Tools (`xcode-select --install`)
- Homebrew
- Python 3.11+ (`brew install python@3.11` — falls nicht vorhanden)
## Ollama vorbereiten
## Schritt 1 — Ollama vorbereiten
```bash
# Ollama installieren, falls noch nicht geschehen
@@ -28,47 +23,40 @@ launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
brew services restart ollama
```
## Worker starten — Vordergrund (Test)
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8080
```
Health-Check vom LXC:
```bash
curl http://<MAC-IP>:8080/health
```
## Worker als Dienst (LaunchAgent — empfohlen für Dauerbetrieb)
Der Worker soll dauerhaft laufen, nach Login automatisch starten und bei Crash automatisch neu starten. Dafür ein **LaunchAgent** (kein LaunchDaemon — MLX/Metal-GPU-Zugriff braucht User-Context).
**Installation** (einmalig, nachdem `.venv` und `.env` eingerichtet sind):
## Schritt 2 — Repo + LaunchAgent installieren
```bash
# Repo holen
git clone https://git.cynfo.net/christian/voice-agent.git ~/voice-agent
cd ~/voice-agent/mac-worker
# Vollständige Installation in einem Befehl:
./deploy/install-launchagent.sh
```
Das Skript:
Das Skript macht **alles in einem Rutsch**, idempotent:
1. prüft, ob `.venv/bin/uvicorn` existiert
2. legt `.env` aus Vorlage an, falls fehlt
3. generiert `~/Library/LaunchAgents/com.voiceagent.worker.plist` mit den passenden Pfaden
4. lädt den Agent (`launchctl load -w …`)
5. testet `http://127.0.0.1:8080/health`
1. legt `.venv` an (wenn fehlt) und installiert `requirements.txt`
2. legt `.env` aus Vorlage an (wenn fehlt)
3. generiert `~/Library/LaunchAgents/com.voiceagent.worker.plist` mit den richtigen Pfaden
4. registriert den Agent via modernem `launchctl bootstrap`
5. wartet auf `http://127.0.0.1:8080/health` und gibt die Status-Antwort aus
**Verwaltung:**
| Aktion | Befehl |
|---|---|
| Status + Logs anzeigen | `./deploy/install-launchagent.sh status` |
| Status + letzte Logs | `./deploy/install-launchagent.sh status` |
| Worker neu starten (kein Reinstall) | `./deploy/install-launchagent.sh restart` |
| **Update** (git-pull + pip + restart + Health) | `./deploy/update.sh` |
| Vollständige Neuinstallation | `./deploy/install-launchagent.sh` |
| Entfernen | `./deploy/install-launchagent.sh uninstall` |
| Update (neue Version pullen) | `git pull && pip install -r requirements.txt && ./deploy/install-launchagent.sh` |
| Stoppen | `launchctl unload -w ~/Library/LaunchAgents/com.voiceagent.worker.plist` |
| Starten | `launchctl load -w ~/Library/LaunchAgents/com.voiceagent.worker.plist` |
| Live-Logs | `tail -f ~/Library/Logs/voice-agent-worker.{out,err}.log` |
> **Hinweis (modernes macOS):** `launchctl unload -w …` wirft auf neueren Versionen
> oft `Input/output error 5`. Die Skripte nutzen daher `launchctl bootout` /
> `bootstrap` / `kickstart -k` — das funktioniert zuverlässig.
**Eigenschaften des Agents:**
- `RunAtLoad` — startet beim Login
@@ -81,15 +69,31 @@ Das Skript:
- Auto-Login aktivieren (System-Settings → Users), oder
- in einen LaunchDaemon umbauen — dann fällt allerdings die Apple-Neural-Engine-Beschleunigung weg, MLX läuft nur auf der CPU.
## Worker im Vordergrund starten (für Entwicklung)
```bash
cd ~/voice-agent/mac-worker
source .venv/bin/activate
uvicorn app.main:app --host 0.0.0.0 --port 8080
```
## API-Endpunkte
| Methode | Pfad | Zweck |
|---|---|---|
| GET | `/health` | Status + Ollama-Reachability |
| GET | `/api/profiles` | Verfügbare YAML-Profile auflisten |
| POST | `/api/transcribe` | multipart: `audio`, `language` → Transkript-JSON |
| POST | `/api/summarize` | JSON: `transcript`, `title` → Summary-JSON |
| POST | `/api/protocol` | JSON: `transcript`, `summary`, `title` → Protokoll-JSON |
| POST | `/api/export/docx` | JSON: Protokoll → DOCX-Binary |
| POST | `/api/summarize` | JSON: `transcript`, `title`, `profile` → Summary-JSON |
| POST | `/api/protocol` | JSON: `transcript`, `summary`, `title`, `profile` → Protokoll-JSON |
| POST | `/api/export/docx` | JSON: `data`, `profile` → DOCX-Binary |
| POST | `/api/export/pdf` | JSON: `data`, `profile` → PDF-Binary |
| POST | `/api/preload` | JSON: `profile` → forciert Ollama-Modell-Load (`keep_alive 30 m`) |
| GET | `/api/jobs/{id}/log` | Letzte 200 Logzeilen für genau diesen Job |
Alle relevanten Calls dürfen einen Header `X-Job-Id: <int>` mitschicken — der
Mac-Worker schreibt dann jede Logzeile dieses Requests in einen per-Job
Ringbuffer, den `/api/jobs/{id}/log` für die LXC-UI ausliefert.
## Firewall am Mac
+140 -54
View File
@@ -1,103 +1,189 @@
#!/usr/bin/env bash
# Installiert den Voice-Agent Mac-Worker als LaunchAgent.
# Idempotent — kann zum Update wiederholt laufen.
# Voice-Agent Mac-Worker — vollständiger Installer.
#
# Nutzung:
# ./install-launchagent.sh # installieren / aktualisieren
# ./install-launchagent.sh uninstall # entfernen
# ./install-launchagent.sh status # Status + letzte Logs
# Macht alles, was nötig ist, damit der Worker dauerhaft läuft:
# - Python-venv anlegen + Dependencies installieren (idempotent)
# - .env aus Vorlage anlegen (falls fehlt)
# - LaunchAgent registrieren / aktualisieren / starten
# - Health-Check
#
# Kann beliebig oft erneut laufen (z. B. nach git pull).
#
# Befehle:
# ./install-launchagent.sh # installieren / aktualisieren
# ./install-launchagent.sh uninstall # entfernen
# ./install-launchagent.sh status # Status + letzte Logs
# ./install-launchagent.sh restart # Worker neu starten (kein Reinstall)
set -euo pipefail
LABEL="com.voiceagent.worker"
PLIST_DST="$HOME/Library/LaunchAgents/${LABEL}.plist"
LOG_DIR="$HOME/Library/Logs"
DOMAIN="gui/$(id -u)"
SERVICE_TARGET="${DOMAIN}/${LABEL}"
# Verzeichnis dieses Skripts → Repo-Root/mac-worker
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKDIR="$(cd "$SCRIPT_DIR/.." && pwd)"
TEMPLATE="$SCRIPT_DIR/com.voiceagent.worker.plist.template"
cmd="${1:-install}"
# Bevorzugter Python-Interpreter (venv-Anlage). Reihenfolge: 3.11, 3.12, 3
pick_python() {
for p in python3.11 python3.12 python3; do
if command -v "$p" >/dev/null 2>&1; then
echo "$p"
return 0
fi
done
echo ""; return 1
}
ensure_venv() {
local py
py="$(pick_python)"
if [ -z "$py" ]; then
echo "FEHLER: kein python3 gefunden. Bitte 'brew install python@3.11' ausführen." >&2
exit 1
fi
if [ ! -x "$WORKDIR/.venv/bin/python" ]; then
echo "[venv] Erzeuge $WORKDIR/.venv mit $py"
"$py" -m venv "$WORKDIR/.venv"
fi
echo "[venv] Pip aktualisieren + Dependencies installieren"
"$WORKDIR/.venv/bin/pip" install --upgrade pip >/dev/null
"$WORKDIR/.venv/bin/pip" install -r "$WORKDIR/requirements.txt"
}
ensure_env() {
if [ ! -f "$WORKDIR/.env" ]; then
echo "[env] Lege $WORKDIR/.env aus .env.example an (bitte ggf. anpassen)."
cp "$WORKDIR/.env.example" "$WORKDIR/.env"
fi
}
write_plist() {
mkdir -p "$LOG_DIR" "$HOME/Library/LaunchAgents"
sed \
-e "s|__WORKDIR__|$WORKDIR|g" \
-e "s|__HOME__|$HOME|g" \
"$TEMPLATE" > "$PLIST_DST"
}
# Modernes launchctl: bootout/bootstrap statt unload/load
# (auf neueren macOS-Versionen wirft "launchctl unload -w" Input/output error 5).
service_unload() {
launchctl bootout "$SERVICE_TARGET" 2>/dev/null || true
}
service_load() {
launchctl bootstrap "$DOMAIN" "$PLIST_DST"
}
service_kickstart() {
# -k = Prozess hart killen + neu starten (greift auch wenn schon läuft).
launchctl kickstart -k "$SERVICE_TARGET"
}
uninstall() {
if [ -f "$PLIST_DST" ]; then
echo "[*] Stoppe und entlade LaunchAgent…"
launchctl unload "$PLIST_DST" 2>/dev/null || true
service_unload
rm -f "$PLIST_DST"
echo "[✓] $PLIST_DST entfernt."
echo "[OK] $PLIST_DST entfernt."
else
echo "[i] Kein LaunchAgent installiert ($PLIST_DST nicht vorhanden)."
fi
}
show_status() {
echo "── launchctl list | grep $LABEL ─────────────────────────"
launchctl list | grep "$LABEL" || echo "(nicht geladen)"
echo "── launchctl print $SERVICE_TARGET ─────────────────────"
launchctl print "$SERVICE_TARGET" 2>/dev/null | head -20 || echo "(nicht geladen)"
echo
echo "── Health-Check ─────────────────────────────────────────"
curl -fsS http://127.0.0.1:8080/health 2>/dev/null && echo || echo "(nicht erreichbar)"
if curl -fsS http://127.0.0.1:8080/health 2>/dev/null; then
echo
else
echo "(nicht erreichbar)"
fi
echo
echo "── stderr.log (letzte 30 Zeilen) ────────────────────────"
tail -n 30 "$LOG_DIR/voice-agent-worker.err.log" 2>/dev/null || echo "(keine Logs)"
}
install_agent() {
# Vorbedingungen prüfen
if [ ! -x "$WORKDIR/.venv/bin/uvicorn" ]; then
echo "FEHLER: $WORKDIR/.venv/bin/uvicorn nicht gefunden." >&2
echo "Bitte zuerst die Python-Umgebung einrichten:" >&2
echo " cd $WORKDIR && python3.11 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt" >&2
restart_only() {
if [ ! -f "$PLIST_DST" ]; then
echo "[!] Kein LaunchAgent installiert — bitte zuerst './install-launchagent.sh' ausführen." >&2
exit 1
fi
if [ ! -f "$WORKDIR/.env" ]; then
echo "WARNUNG: $WORKDIR/.env fehlt — kopiere aus .env.example."
cp "$WORKDIR/.env.example" "$WORKDIR/.env"
fi
mkdir -p "$LOG_DIR" "$HOME/Library/LaunchAgents"
echo "[1/4] Generiere plist mit WORKDIR=$WORKDIR"
sed \
-e "s|__WORKDIR__|$WORKDIR|g" \
-e "s|__HOME__|$HOME|g" \
"$TEMPLATE" > "$PLIST_DST"
echo "[2/4] Falls bereits geladen: vorher entladen"
launchctl unload "$PLIST_DST" 2>/dev/null || true
echo "[3/4] Lade LaunchAgent"
launchctl load -w "$PLIST_DST"
echo "[4/4] Warte 2 s und prüfe Health-Endpoint"
echo "[*] kickstart -k $SERVICE_TARGET"
service_kickstart
sleep 2
if curl -fsS http://127.0.0.1:8080/health >/dev/null; then
echo
echo "[✓] Voice-Agent Mac-Worker läuft."
curl -s http://127.0.0.1:8080/health | python3 -m json.tool 2>/dev/null || true
if curl -fsS http://127.0.0.1:8080/health >/dev/null 2>&1; then
echo "[OK] Worker läuft wieder."
else
echo
echo "[!] Health-Check fehlgeschlagen. Logs:"
echo " $LOG_DIR/voice-agent-worker.err.log"
echo " $LOG_DIR/voice-agent-worker.out.log"
echo "[!] Health-Check schlägt nach Restart fehl — siehe $LOG_DIR/voice-agent-worker.err.log" >&2
tail -n 20 "$LOG_DIR/voice-agent-worker.err.log" 2>/dev/null || true
exit 1
fi
}
echo
echo "── Verwaltung ───────────────────────────────────────────"
echo " Stoppen / Starten: launchctl unload|load -w $PLIST_DST"
echo " Status: $0 status"
echo " Entfernen: $0 uninstall"
echo " Logs (live): tail -f $LOG_DIR/voice-agent-worker.{out,err}.log"
install_agent() {
echo "[1/6] venv + Dependencies"
ensure_venv
echo "[2/6] .env"
ensure_env
echo "[3/6] plist generieren ($PLIST_DST)"
write_plist
echo "[4/6] LaunchAgent (bootout falls vorhanden, dann bootstrap)"
service_unload
service_load
echo "[5/6] Health-Check (max. 8 s warten)"
local ok=0
for _ in 1 2 3 4 5 6 7 8; do
sleep 1
if curl -fsS http://127.0.0.1:8080/health >/dev/null 2>&1; then
ok=1
break
fi
done
if [ "$ok" -ne 1 ]; then
echo
echo "[!] Health-Check fehlgeschlagen. Letzte Log-Zeilen:" >&2
tail -n 30 "$LOG_DIR/voice-agent-worker.err.log" 2>/dev/null || true
echo
echo " Plist: $PLIST_DST"
echo " Logs: $LOG_DIR/voice-agent-worker.{out,err}.log"
exit 1
fi
echo "[6/6] Health-Antwort:"
curl -s http://127.0.0.1:8080/health | python3 -m json.tool 2>/dev/null || true
cat <<EOF
[OK] Voice-Agent Mac-Worker läuft.
── Verwaltung ────────────────────────────────────────────────────
Status + Logs: $0 status
Restart: $0 restart # nur Prozess neu starten
Update: $WORKDIR/deploy/update.sh # git pull + restart
Entfernen: $0 uninstall
Live-Logs: tail -f $LOG_DIR/voice-agent-worker.{out,err}.log
EOF
}
case "$cmd" in
install) install_agent ;;
uninstall) uninstall ;;
status) show_status ;;
restart) restart_only ;;
*)
echo "Nutzung: $0 [install|uninstall|status]" >&2
echo "Nutzung: $0 [install|uninstall|status|restart]" >&2
exit 1
;;
esac
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Voice-Agent Mac-Worker — Ein-Befehl-Update.
#
# Macht: git pull (im Repo-Root) + pip install -r requirements.txt + Worker-Restart.
# Idempotent. Beendet sich mit Health-Check.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKDIR="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_DIR="$(cd "$WORKDIR/.." && pwd)"
LABEL="com.voiceagent.worker"
SERVICE_TARGET="gui/$(id -u)/${LABEL}"
echo "[1/4] git pull ($REPO_DIR)"
git -C "$REPO_DIR" pull --ff-only
echo "[2/4] pip install (venv: $WORKDIR/.venv)"
if [ ! -x "$WORKDIR/.venv/bin/pip" ]; then
echo "FEHLER: $WORKDIR/.venv fehlt. Bitte zuerst $SCRIPT_DIR/install-launchagent.sh ausführen." >&2
exit 1
fi
"$WORKDIR/.venv/bin/pip" install -r "$WORKDIR/requirements.txt"
echo "[3/4] Worker neu starten (kickstart -k $SERVICE_TARGET)"
if ! launchctl print "$SERVICE_TARGET" >/dev/null 2>&1; then
echo "FEHLER: LaunchAgent ist nicht geladen. Bitte $SCRIPT_DIR/install-launchagent.sh ausführen." >&2
exit 1
fi
launchctl kickstart -k "$SERVICE_TARGET"
echo "[4/4] Health-Check (max. 8 s warten)"
for _ in 1 2 3 4 5 6 7 8; do
sleep 1
if curl -fsS http://127.0.0.1:8080/health >/dev/null 2>&1; then
echo "[OK] Update durch, Worker läuft."
curl -s http://127.0.0.1:8080/health | python3 -m json.tool 2>/dev/null || true
exit 0
fi
done
echo "[!] Health-Check schlägt nach Update fehl." >&2
tail -n 30 "$HOME/Library/Logs/voice-agent-worker.err.log" 2>/dev/null || true
exit 1