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:
@@ -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
|
||||
|
||||
Executable
+43
@@ -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
|
||||
Reference in New Issue
Block a user