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:
parent
f408a555f8
commit
bc497a1665
174
README.md
174
README.md
@ -1,37 +1,42 @@
|
||||
# Voice-Agent
|
||||
|
||||
Self-hosted Transkriptions- und Protokollsystem für Sitzungsaufzeichnungen — vollständig lokal, DSGVO-konform.
|
||||
Self-hosted Transkriptions- und Protokollsystem für Sprachaufzeichnungen — vollständig lokal, DSGVO-konform.
|
||||
|
||||
> Vollständiges Lastenheft: siehe [`need.md`](need.md)
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Benutzer
|
||||
Benutzer (Browser / Handy-PWA)
|
||||
│
|
||||
▼ HTTP (Upload)
|
||||
┌─────────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ LXC-Container │ HTTP │ Mac (Apple Silicon) │
|
||||
│ - FastAPI + HTML/JS UI │ ──────► │ - FastAPI Worker │
|
||||
│ - SQLite Job-Store │ │ - lightning-whisper-mlx │
|
||||
│ - Result-Verzeichnis │ ◄────── │ - Ollama (llama3.1:8b) │
|
||||
│ - Nginx Reverse Proxy │ │ - python-docx │
|
||||
└─────────────────────────────┘ └──────────────────────────────┘
|
||||
▼ HTTPS (selbst-signiert, Mikrofon-fähig)
|
||||
┌──────────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ LXC-Container (Linux) │ HTTP │ Mac (Apple Silicon) │
|
||||
│ - FastAPI + HTML/JS UI │ ──────► │ - FastAPI Worker │
|
||||
│ - SQLite Job-Store + Auth │ │ - lightning-whisper-mlx │
|
||||
│ - Profile (YAML, ≥17) │ │ - Ollama (Standard 8B) │
|
||||
│ - Result-/Upload-Verz. │ ◄────── │ - python-docx + reportlab │
|
||||
│ - Nginx HTTPS-Reverse-Proxy │ │ - Per-Job Log-Ringbuffer │
|
||||
└──────────────────────────────┘ └──────────────────────────────┘
|
||||
```
|
||||
|
||||
- **LXC**: keine AI-Verarbeitung — nur Uploads, Job-Tracking, Auslieferung.
|
||||
- **Mac**: Stateless API. Erhält Audio/Text, gibt Transkripte / Zusammenfassungen / Protokolle / DOCX zurück.
|
||||
- **LXC**: keine AI-Verarbeitung — nur Uploads, Job-Tracking, Login, Auslieferung.
|
||||
- **Mac**: Stateless API. Erhält Audio/Text, gibt Transkripte / Zusammenfassungen / Protokolle / DOCX / PDF zurück.
|
||||
|
||||
## MVP-Funktionsumfang (v0.1)
|
||||
## Funktionsumfang (aktueller Stand)
|
||||
|
||||
- [x] Audio-Upload (MP3, WAV, M4A, MP4, OGG, FLAC, max. 500 MB)
|
||||
- [x] Whisper-Transkription auf dem Mac
|
||||
- [x] Ollama-Zusammenfassung (Beschlüsse, Aufgaben, Teilnehmer)
|
||||
- [x] Strukturiertes Sitzungsprotokoll
|
||||
- [x] DOCX-Export
|
||||
- [ ] Speaker-Diarization (Prio 2)
|
||||
- [ ] Auth / Benutzerverwaltung (Prio 2)
|
||||
- [ ] PDF-Export (Prio 2)
|
||||
- [x] Audio-Upload (MP3, WAV, M4A, MP4, OGG, FLAC, WEBM — max. 500 MB)
|
||||
- [x] Mobile-First-Layout + PWA mit Mikrofon-Aufnahme (Start / Pause / Stop)
|
||||
- [x] Whisper-Transkription auf dem Mac (MLX, Apple Neural Engine)
|
||||
- [x] Ollama-Zusammenfassung
|
||||
- [x] Strukturiertes Protokoll je Profil
|
||||
- [x] **17 Verarbeitungs-Profile** (Sitzung, Diktat, Pflegedoku, IT-Ticket, Sales-Call, …)
|
||||
- [x] **DOCX- und PDF-Export**
|
||||
- [x] **Benutzer-Verwaltung** (bcrypt, Cookie-Sessions, Per-User-Job-Isolation, Admin-Rolle)
|
||||
- [x] **HTTPS via selbst-signiertem Zertifikat** (Mikrofon-Zugriff am Handy)
|
||||
- [x] **Idempotente Pipeline + Retry** — bei „Server disconnected" automatischer 1× Retry, Transkript wird beim manuellen Erneut-Versuchen weiterverwendet
|
||||
- [x] **Diagnose-Panel pro Job** — Live-Heartbeat während laufender Mac-Calls + Mac-Worker-Log direkt in der UI
|
||||
- [ ] Speaker-Diarization (Prio 2 — siehe `mac-worker/README.md`)
|
||||
|
||||
---
|
||||
|
||||
@ -40,12 +45,12 @@ Benutzer
|
||||
Voraussetzungen:
|
||||
|
||||
- **Mac** (Apple Silicon empfohlen, M1/M2/M3/M4) mit min. 16 GB RAM, besser 32 GB.
|
||||
- **Proxmox-Host** für den LXC-Container.
|
||||
- **Proxmox-Host** (oder beliebiger Linux-Host) für den LXC-Container.
|
||||
- **Netzwerk**: Mac und LXC im selben LAN, der LXC muss den Mac auf Port 8080 erreichen.
|
||||
|
||||
### Schritt 1 — LXC-Container provisionieren (Proxmox)
|
||||
|
||||
Entweder mit dem Toolkit-Skill `/proxmox-lxc` oder manuell ein Container mit:
|
||||
Container mit:
|
||||
|
||||
- Debian 13 (Trixie) oder Ubuntu 24.04
|
||||
- 2 vCPU, 2 GB RAM, 20 GB Disk reichen
|
||||
@ -71,15 +76,17 @@ cd /var/www/voice-agent
|
||||
|
||||
Das Skript installiert idempotent:
|
||||
|
||||
1. apt-Pakete (`git python3-venv nginx ffmpeg ...`)
|
||||
1. apt-Pakete (`git python3-venv nginx ffmpeg openssl …`)
|
||||
2. Deploy-User `deploy`, klont/aktualisiert Repo nach `/var/www/voice-agent`
|
||||
3. Python-venv + Dependencies in `lxc-frontend/.venv`
|
||||
4. `.env` aus Vorlage anlegen (falls nicht vorhanden)
|
||||
5. Verzeichnisse `/var/lib/voice-agent/{uploads,results}`
|
||||
6. systemd-Service `voice-agent.service` (autostart, restart on-failure)
|
||||
7. Nginx-Reverse-Proxy auf Port 80 → `127.0.0.1:8000`
|
||||
4. `.env` aus Vorlage anlegen, `SESSION_SECRET` automatisch erzeugen
|
||||
5. Verzeichnisse `/var/lib/voice-agent/{uploads,results}` + SQLite-DB
|
||||
6. systemd-Service `voice-agent.service` (autostart, restart-on-failure)
|
||||
7. Selbst-signiertes TLS-Zertifikat mit SAN (Hostname + IP)
|
||||
8. Nginx HTTPS-Reverse-Proxy + HTTP→HTTPS Redirect
|
||||
9. CLI-Wrapper `/usr/local/bin/voice-agent-admin`
|
||||
|
||||
Erneutes Ausführen aktualisiert auf den neuesten `main`.
|
||||
Erneutes Ausführen aktualisiert auf den neuesten `main` (idempotent, kein Datenverlust — die SQLite-DB liegt unter `/var/lib/voice-agent/`, nicht im App-Verzeichnis).
|
||||
|
||||
### Schritt 3 — Mac-Worker einrichten
|
||||
|
||||
@ -90,24 +97,13 @@ Auf dem Mac (Details in [`mac-worker/README.md`](mac-worker/README.md)):
|
||||
git clone https://git.cynfo.net/christian/voice-agent.git ~/voice-agent
|
||||
cd ~/voice-agent/mac-worker
|
||||
|
||||
# Python-Umgebung
|
||||
python3.11 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
cp .env.example .env
|
||||
|
||||
# Ollama + Modell
|
||||
# Ollama + Modell (einmalig)
|
||||
brew install ollama
|
||||
launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
|
||||
brew services restart ollama
|
||||
ollama pull llama3.1:8b
|
||||
|
||||
# Worker starten (Vordergrund — für Test)
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8080
|
||||
|
||||
# Für Dauerbetrieb: als LaunchAgent registrieren
|
||||
# (startet bei Login, restart nach Crash, Logs in ~/Library/Logs/)
|
||||
# Worker als LaunchAgent installieren — macht venv + Dependencies + Autostart in einem Rutsch
|
||||
./deploy/install-launchagent.sh
|
||||
```
|
||||
|
||||
@ -128,9 +124,29 @@ sudo nano /var/www/voice-agent/lxc-frontend/.env
|
||||
sudo systemctl restart voice-agent
|
||||
```
|
||||
|
||||
### Schritt 5 — Benutzen
|
||||
### Schritt 5 — Erst-Anmeldung + Benutzen
|
||||
|
||||
Browser auf `http://<LXC-IP>/` → Audio hochladen → fertig.
|
||||
Browser auf `https://<LXC-IP>/` — beim allerersten Aufruf legt die UI den initialen Admin an (Username + Passwort wählen). Bestehende Jobs werden diesem Konto zugeordnet.
|
||||
|
||||
Audio hochladen oder direkt in der UI aufnehmen → fertig.
|
||||
|
||||
---
|
||||
|
||||
## Update / Neudeployment
|
||||
|
||||
**LXC** — einfach nochmal:
|
||||
|
||||
```bash
|
||||
sudo /var/www/voice-agent/deploy/install.sh
|
||||
```
|
||||
|
||||
**Mac** — ein Befehl macht git-pull + pip-install + Worker-Restart + Health-Check:
|
||||
|
||||
```bash
|
||||
~/voice-agent/mac-worker/deploy/update.sh
|
||||
```
|
||||
|
||||
(Falls `update.sh` aus irgendeinem Grund nicht läuft, ist `./deploy/install-launchagent.sh restart` der manuelle Fallback.)
|
||||
|
||||
---
|
||||
|
||||
@ -147,25 +163,61 @@ sudo voice-agent-admin list
|
||||
sudo voice-agent-admin create maria 'StartPasswort123'
|
||||
sudo voice-agent-admin create chefin 'Geheim12345' --admin
|
||||
sudo voice-agent-admin delete maria
|
||||
|
||||
# Passwort prüfen ohne Browser (Debug)
|
||||
sudo voice-agent-admin verify admin 'NeuesPasswort123'
|
||||
```
|
||||
|
||||
Vollständige Befehlsreferenz: [`lxc-frontend/README.md`](lxc-frontend/README.md#benutzer--und-passwort-verwaltung-per-cli).
|
||||
|
||||
## Update / Neudeployment
|
||||
---
|
||||
|
||||
Auf dem LXC einfach nochmal:
|
||||
## Profile
|
||||
|
||||
```bash
|
||||
sudo /var/www/voice-agent/deploy/install.sh
|
||||
```
|
||||
Jeder Job läuft mit einem **Verarbeitungs-Profil**, das die Prompts für Summary, Protokoll und das Layout des Exports steuert. Standard ist `meeting`. Das Standard-Profil je User wird im Web-UI im Header gewählt und bleibt gespeichert.
|
||||
|
||||
Auf dem Mac:
|
||||
Mitgelieferte Profile (alle in `mac-worker/profiles/*.yaml`):
|
||||
|
||||
```bash
|
||||
cd ~/voice-agent && git pull
|
||||
cd mac-worker && source .venv/bin/activate && pip install -r requirements.txt
|
||||
# Worker neu starten
|
||||
```
|
||||
| Profil | Zweck |
|
||||
|---|---|
|
||||
| `meeting` | Klassisches Sitzungsprotokoll |
|
||||
| `phone-call` | Telefonat-Notiz mit Beteiligten/Verabredungen |
|
||||
| `memo` | Kurze Sprachnotiz / Diktat |
|
||||
| `interview` | Interview mit Sprecherspuren |
|
||||
| `one-on-one` | 1:1-Gespräch (Mitarbeiter / Kunde) |
|
||||
| `brainstorm` | Ideen-Sammlung mit Cluster-Vorschlägen |
|
||||
| `service-ticket` | IT-Service-Ticket aus Anruf-Mitschnitt |
|
||||
| `incident-report` | Incident-Doku (was, wann, Auswirkung, Status) |
|
||||
| `postmortem` | Post-Mortem mit Lessons-Learned |
|
||||
| `site-survey` | Vor-Ort-Aufnahme (Leitungsverlauf, Hardware, Zustand) |
|
||||
| `sales-call` | Sales-Gespräch mit Pain-Points / Next-Steps |
|
||||
| `schichtuebergabe` | Schichtübergabe Pflege |
|
||||
| `pflegedoku` | Pflegedokumentations-Eintrag |
|
||||
| `sturzbericht` | Sturzbericht (Pflege) |
|
||||
| `visite` | Arzt-Visite |
|
||||
| `angehoerigengespraech` | Angehörigen-Gespräch (Pflege) |
|
||||
| `biografie` | Biografie-Eintrag (Pflege) |
|
||||
|
||||
Eigene Profile: einfach eine weitere YAML-Datei in `mac-worker/profiles/` ablegen (Beispiel siehe `meeting.yaml`) und Mac-Worker neu starten.
|
||||
|
||||
---
|
||||
|
||||
## Diagnose / Troubleshooting
|
||||
|
||||
Wenn ein Job hängt oder fehlschlägt:
|
||||
|
||||
- **In der UI**: bei FAILED-Jobs und langlaufenden Stages erscheint **„Diagnose ▾"**. Aufklappen zeigt:
|
||||
- aktuelle Stage und wie lange sie schon läuft
|
||||
- letzter Heartbeat (alle 3 s während Mac-Calls)
|
||||
- die letzten 200 Mac-Worker-Log-Zeilen für genau diesen Job
|
||||
- **Auf dem LXC**:
|
||||
- `systemctl status voice-agent` / `journalctl -u voice-agent -f`
|
||||
- `curl -sk https://localhost/api/mac/health`
|
||||
- **Auf dem Mac**:
|
||||
- `~/voice-agent/mac-worker/deploy/install-launchagent.sh status`
|
||||
- `tail -f ~/Library/Logs/voice-agent-worker.{out,err}.log`
|
||||
|
||||
Wenn der Mac mitten in `summarize` die Verbindung abreissen lässt, retried der LXC einmal automatisch (3 s Backoff) — ein vorgelagerter Preload-Aufruf hat zudem das Ollama-Modell schon ins RAM geladen, damit es nicht in der eigentlichen Generierung neu lädt.
|
||||
|
||||
---
|
||||
|
||||
@ -181,7 +233,7 @@ cd mac-worker && source .venv/bin/activate && pip install -r requirements.txt
|
||||
| `faster` | `large-v3` | Intel-Mac / Linux / CUDA |
|
||||
| `mock` | – | Tests ohne Modell-Download |
|
||||
|
||||
Standard ist `mlx` + `large-v3`. Für lange Sitzungen lohnt sich ein Test mit `distil-large-v3`. Alternativen (whisper.cpp + CoreML, WhisperX mit Diarization, Voxtral) sind in [`mac-worker/README.md`](mac-worker/README.md#alternative-transkriptions-engines) dokumentiert.
|
||||
Standard ist `mlx` + `large-v3`. Für lange Sitzungen lohnt sich `distil-large-v3`. Alternativen (whisper.cpp + CoreML, WhisperX mit Diarization, Voxtral) sind in [`mac-worker/README.md`](mac-worker/README.md#alternative-transkriptions-engines) dokumentiert.
|
||||
|
||||
### Zusammenfassung (Ollama)
|
||||
|
||||
@ -198,7 +250,7 @@ Empfohlene Alternativen je nach Hardware (alle via `ollama pull <name>` und `OLL
|
||||
| `gpt-oss:20b` | 20 B | ~14 GB | Sehr gute strukturierte Ausgabe (OpenAI Open-Weight) |
|
||||
| `llama3.3:70b` | 70 B | ~48 GB | Top-Qualität, nur auf Macs mit 64 GB+ |
|
||||
|
||||
Für Sitzungsprotokolle (deutsch, viel JSON-Struktur) ist **`qwen2.5:14b`** oder **`mistral-small:24b`** der beste Sweet-Spot auf einem Mac mit 32 GB.
|
||||
Für deutsche Protokolle mit viel JSON-Struktur ist **`qwen2.5:14b`** oder **`mistral-small:24b`** der beste Sweet-Spot auf einem Mac mit 32 GB.
|
||||
|
||||
Detaillierte Bewertung siehe [`mac-worker/README.md`](mac-worker/README.md#ollama-modell-wählen).
|
||||
|
||||
@ -214,9 +266,11 @@ voice-agent/
|
||||
│ └── .env.example
|
||||
├── mac-worker/ # FastAPI AI-Worker (läuft auf dem Mac)
|
||||
│ ├── app/
|
||||
│ ├── profiles/ # YAML-Profile (Prompts + Layout)
|
||||
│ ├── deploy/ # install-launchagent.sh, update.sh, plist-Template
|
||||
│ ├── requirements.txt
|
||||
│ └── .env.example
|
||||
├── deploy/ # systemd / nginx / install.sh
|
||||
├── deploy/ # systemd / nginx / install.sh (LXC)
|
||||
├── config/ # Toolkit-Konfiguration (project.env)
|
||||
├── .claude/ # KI-Agenten Skills
|
||||
├── need.md # Lastenheft
|
||||
@ -226,6 +280,8 @@ voice-agent/
|
||||
## Sicherheit
|
||||
|
||||
- Vollständig lokaler Betrieb. Keine Cloud-Calls.
|
||||
- HTTPS via Reverse-Proxy ist Aufgabe der Infrastruktur (Let's Encrypt o. Ä.).
|
||||
- HTTPS mit selbst-signiertem Cert (für Mikrofon-Zugriff am Handy zwingend nötig).
|
||||
- Web-UI ist Cookie-Session-basiert (bcrypt cost 12, signiert mit `SESSION_SECRET`).
|
||||
- Mac-Worker hat **keine** Auth — Betrieb nur im internen Netz.
|
||||
- Uploads / Ergebnisse unter `/var/lib/voice-agent/` — bei Bedarf Backup einplanen.
|
||||
- `.env`-Dateien (LXC + Mac) sind chmod 600, enthalten Session-Secret + ggf. Tokens.
|
||||
|
||||
@ -52,9 +52,10 @@ elif ! grep -q "^SESSION_SECRET=" "$APP_DIR/lxc-frontend/.env"; then
|
||||
echo " SESSION_SECRET nachgetragen."
|
||||
fi
|
||||
|
||||
# ALLOWED_EXTS sicherstellen, dass 'webm' für Browser-Aufnahmen drin ist
|
||||
# ALLOWED_EXTS sicherstellen, dass 'webm' für Browser-Aufnahmen drin ist.
|
||||
# Wortgrenzen-Check verhindert dabei wiederholtes Anhängen.
|
||||
if grep -q "^ALLOWED_EXTS=" "$APP_DIR/lxc-frontend/.env"; then
|
||||
if ! grep -E "^ALLOWED_EXTS=.*\bwebm\b" "$APP_DIR/lxc-frontend/.env" >/dev/null; then
|
||||
if ! grep -Eq "^ALLOWED_EXTS=([^,]*,)*webm(,|$)" "$APP_DIR/lxc-frontend/.env"; then
|
||||
sed -i 's|^ALLOWED_EXTS=\(.*\)$|ALLOWED_EXTS=\1,webm|' "$APP_DIR/lxc-frontend/.env"
|
||||
echo " ALLOWED_EXTS um 'webm' ergänzt (für Mikrofon-Aufnahmen)."
|
||||
fi
|
||||
@ -111,23 +112,40 @@ exec sudo -u deploy /var/www/voice-agent/lxc-frontend/.venv/bin/python \
|
||||
WRAPPER
|
||||
chmod 0755 /usr/local/bin/voice-agent-admin
|
||||
|
||||
HOST_IP_NOW="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
|
||||
echo
|
||||
echo "Fertig. Health-Check:"
|
||||
echo " curl -sk https://localhost/health"
|
||||
echo "════════════════════════════════════════════════════════════════════"
|
||||
echo " Voice-Agent LXC-Installation FERTIG"
|
||||
echo "════════════════════════════════════════════════════════════════════"
|
||||
echo
|
||||
echo "Aufruf im Browser: https://<LXC-IP>/ (HTTP wird auf HTTPS umgeleitet)"
|
||||
echo "Beim ersten Aufruf wird der initiale Admin angelegt."
|
||||
echo "Web-UI: https://${HOST_IP_NOW}/ (HTTP wird auf HTTPS umgeleitet)"
|
||||
echo "Health: curl -sk https://localhost/health"
|
||||
echo
|
||||
echo "── Mikrofon-Aufnahme von Handys ──────────────────────────────────────"
|
||||
echo "Damit iOS/Android das Mikrofon freigibt, MUSS die Seite über HTTPS laufen."
|
||||
echo "Das selbst-signierte Zertifikat einmalig auf jedem Gerät installieren:"
|
||||
echo "── Erst-Inbetriebnahme ──────────────────────────────────────────────"
|
||||
echo " 1) MAC_API_URL in $APP_DIR/lxc-frontend/.env auf die IP des Mac-Workers setzen"
|
||||
echo " und 'sudo systemctl restart voice-agent' ausführen."
|
||||
echo " 2) Browser öffnen — beim allerersten Aufruf wird der initiale Admin angelegt."
|
||||
echo
|
||||
echo " iPhone: Safari öffnen, http://<LXC-IP>/ssl/voice-agent.crt aufrufen"
|
||||
echo " → Profil herunterladen → Einstellungen → Profil installieren"
|
||||
echo " → Allgemein → Info → Zertifikatsvertrauen → 'voice-agent' aktivieren"
|
||||
echo "── Benutzer / Passwort ──────────────────────────────────────────────"
|
||||
echo " voice-agent-admin list # alle User"
|
||||
echo " voice-agent-admin create maria 'StartPasswort123' # neuer User"
|
||||
echo " voice-agent-admin reset admin 'NeuesPasswort123' # Passwort zurücksetzen"
|
||||
echo
|
||||
echo " Android: Browser öffnen, http://<LXC-IP>/ssl/voice-agent.crt aufrufen"
|
||||
echo " → Cert installieren als 'VPN/Apps' (Variiert je nach Hersteller)"
|
||||
echo "── Mikrofon-Aufnahme von Handys (PWA) ───────────────────────────────"
|
||||
echo " iOS/Android brauchen HTTPS. Selbst-signiertes Cert einmalig installieren:"
|
||||
echo " iPhone: Safari → http://${HOST_IP_NOW}/ssl/voice-agent.crt"
|
||||
echo " → Profil installieren → Einstellungen → Allgemein → Info"
|
||||
echo " → Zertifikatsvertrauen → 'voice-agent' aktivieren"
|
||||
echo " Android: Browser → http://${HOST_IP_NOW}/ssl/voice-agent.crt installieren"
|
||||
echo
|
||||
echo "Falls noch nicht geschehen: MAC_API_URL in $APP_DIR/lxc-frontend/.env setzen"
|
||||
echo "und Service neu starten: sudo systemctl restart voice-agent"
|
||||
echo "── Update auf neueste Version ───────────────────────────────────────"
|
||||
echo " sudo $APP_DIR/deploy/install.sh # idempotent — pullt + restartet"
|
||||
echo
|
||||
echo "── Diagnose / Troubleshooting ───────────────────────────────────────"
|
||||
echo " Service-Status: systemctl status voice-agent"
|
||||
echo " Live-Log: journalctl -u voice-agent -f"
|
||||
echo " Mac erreichbar? curl -sk https://localhost/api/mac/health"
|
||||
echo " Pro Job in der UI: Button 'Diagnose ▾' öffnet Mac-Worker-Log live"
|
||||
echo
|
||||
|
||||
|
||||
@ -33,9 +33,10 @@ uvicorn app.main:app --reload --port 8000
|
||||
| GET / POST / DELETE | `/api/admin/users[...]` | User-Verwaltung (admin) |
|
||||
| POST | `/api/jobs` | multipart: `audio`, `title`, `profile` → Job anlegen |
|
||||
| GET | `/api/jobs` | Liste aller Jobs (Owner-gefiltert; Admin sieht alle) |
|
||||
| GET | `/api/jobs/{id}` | Job-Detail |
|
||||
| GET | `/api/jobs/{id}` | Job-Detail (inkl. `step_started_at`, `last_heartbeat_at`) |
|
||||
| GET | `/api/jobs/{id}/diag` | Diagnose-Bündel: Stage-Sekunden + Mac-Worker-Log |
|
||||
| POST | `/api/jobs/{id}/retry` | Verarbeitung neu anstoßen (Transkript bleibt) |
|
||||
| GET | `/api/jobs/{id}/download/{kind}` | `kind` ∈ `docx`, `transcript`, `summary`, `protocol` |
|
||||
| GET | `/api/jobs/{id}/download/{kind}` | `kind` ∈ `pdf`, `docx`, `transcript`, `summary`, `protocol` |
|
||||
|
||||
## Benutzer- und Passwort-Verwaltung per CLI
|
||||
|
||||
@ -80,10 +81,18 @@ Danach im Browser direkt einloggen.
|
||||
## Datenfluss
|
||||
|
||||
```
|
||||
Upload → SQLite-Job → BackgroundTask
|
||||
Upload → SQLite-Job → BackgroundTask (idempotent)
|
||||
→ Mac: /api/transcribe (multipart)
|
||||
→ Mac: /api/summarize (JSON)
|
||||
→ Mac: /api/protocol (JSON)
|
||||
→ Mac: /api/preload (Modell ins RAM ziehen, vor Summarize)
|
||||
→ Mac: /api/summarize (JSON)
|
||||
→ Mac: /api/protocol (JSON)
|
||||
→ Mac: /api/export/docx (JSON) → DOCX
|
||||
→ Mac: /api/export/pdf (JSON) → PDF
|
||||
→ Ergebnisse landen in /var/lib/voice-agent/results/{job_id}/
|
||||
```
|
||||
|
||||
Während laufender Mac-Calls pingt ein Heartbeat alle 3 s `last_heartbeat_at`.
|
||||
Bei `RemoteProtocolError` / `ConnectError` retried der MacClient einmal automatisch
|
||||
mit 3 s Backoff. Jeder Mac-Call trägt einen `X-Job-Id`-Header — der Mac-Worker
|
||||
sammelt damit pro Job einen Log-Ringbuffer (200 Zeilen, 1 h TTL), den
|
||||
`/api/jobs/{id}/diag` für die UI bündelt.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
mac-worker/deploy/update.sh
Executable file
43
mac-worker/deploy/update.sh
Executable 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
|
||||
Loading…
x
Reference in New Issue
Block a user