bifroest/scripts/dev.sh
2026-10-27 08:34:17 +01:00

185 lines
7.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# Bifröst lokal starten: PostgreSQL, Migrationen, DB-Dienst, Web und Worker in einem Terminal (Logs mit Präfix).
# Strg+C stoppt alles, was das Skript gestartet hat; die Container laufen weiter.
#
# scripts/dev.sh # Web auf http://127.0.0.1:8765, Worker, DB-Dienst PostgreSQL (Port 8791)
# scripts/dev.sh --oracle # dazu Oracle-Container und DB-Dienst für Oracle (Port 8792)
# scripts/dev.sh --port 8000 # anderer Port für Web
# scripts/dev.sh --setup # Ersteinrichtung: .env anlegen, Master-Key, Demo-Nutzer
# scripts/dev.sh --reset # von null: lokale Datenbank und data/ löschen (mit Rückfrage), dann wie
# # --setup und zusätzlich die Demo-Workspaces anlegen (aiw demo)
# scripts/dev.sh --reset --yes # ohne Rückfrage; mit --oracle auch die Oracle-Daten
#
# Die DB-Dienste registriert man einmal in Bifröst als Datenbank-Treiber (Administration → Datenbank-Treiber);
# das Skript zeigt URL und Schlüssel beim Start.
# Einstellungen kommen aus .env. Für lokale Quellen setzt das Skript AIW_DB_ALLOW_LOCAL und AIW_MCP_ALLOW_LOCAL
# auf true, sofern .env nichts anderes sagt.
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT"
PORT=8765
ORACLE=false
SETUP=false
RESET=false
YES=false
# Schlüssel nur für die lokal gestarteten DB-Dienste
DEV_KEY="dev-schluessel-12345678"
# Hilfe: der Kommentarblock am Anfang
usage() { awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "$0"; }
while [ $# -gt 0 ]; do
case "$1" in
--oracle) ORACLE=true ;;
--port) PORT=${2:?Port fehlt}; shift ;;
--setup) SETUP=true ;;
--reset) RESET=true; SETUP=true ;;
--yes) YES=true ;;
-h|--help) usage; exit 0 ;;
*) echo "Unbekannte Option: $1"; usage; exit 1 ;;
esac
shift
done
say() { printf '\033[1m==> %s\033[0m\n' "$*"; }
fail() { printf '\033[31m%s\033[0m\n' "$*" >&2; exit 1; }
# Zeilen eines Prozesses mit Namen versehen (portabel, auch mit der Bash von macOS)
prefix() { while IFS= read -r line; do printf '[%-6s] %s\n' "$1" "$line"; done; }
port_busy() { lsof -nP -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1; }
env_value() { grep -E "^$1=" .env 2>/dev/null | tail -1 | cut -d= -f2-; }
command -v docker >/dev/null || fail "Docker fehlt."
command -v uv >/dev/null || fail "uv fehlt (https://docs.astral.sh/uv/)."
# Von null: lokale Daten löschen. .env bleibt (Master-Key, Demo-Passwort), Docker-Images auch.
if $RESET; then
port_busy "$PORT" && fail "Auf Port $PORT läuft noch etwas (Bifröst?). Erst stoppen, dann --reset."
echo "Gelöscht werden ALLE lokalen Daten von Bifröst: Datenbank (Workspaces, Chats, Nutzer, Modelle, API-Keys,"
echo "Einstellungen) samt Shop-Datenbank der Demo, dazu Uploads, Git-Cache und Demo-Repositories unter data/."
$ORACLE && echo "Mit --oracle zusätzlich die Daten des Oracle-Containers."
if ! $YES; then
# Ohne Terminal (Skript, CI) nicht auf eine Antwort warten: dort ausdrücklich --yes
[ -t 0 ] || fail "Abgebrochen, nichts gelöscht: Die Rückfrage braucht ein Terminal (sonst --yes)."
printf "Fortfahren? Zum Bestätigen 'ja' eingeben: "
read -r answer || answer=""
[ "$answer" = "ja" ] || fail "Abgebrochen, nichts gelöscht."
fi
say "PostgreSQL-Container und -Daten löschen"
docker compose rm -sfv postgres >/dev/null
docker volume rm -f ai-workspace_aiw-postgres >/dev/null
if $ORACLE; then
say "Oracle-Container und -Daten löschen"
docker compose --profile oracle rm -sfv oracle >/dev/null
docker volume rm -f ai-workspace_aiw-oracle >/dev/null
fi
# Ablagen aus .env, sofern sie im Projekt liegen; data/ immer
for dir in data "$(env_value AIW_UPLOAD_DIR)" "$(env_value AIW_GIT_CACHE_DIR)"; do
[ -n "$dir" ] || continue
case "$dir" in
/*|*..*) echo "Übersprungen (außerhalb des Projekts, bitte selbst prüfen): $dir" ;;
*) if [ -e "$dir" ]; then say "$dir/ löschen"; rm -rf -- "$dir"; fi ;;
esac
done
fi
if $SETUP; then
if [ -f .env ]; then
say ".env gibt es schon, bleibt unverändert"
else
say ".env aus .env.example anlegen"
cp .env.example .env
fi
uv sync
if [ -z "$(env_value AIW_SECRET_KEY)" ]; then
say "Master-Key erzeugen (AIW_SECRET_KEY)"
key=$(uv run aiw gen-key | tail -1)
grep -q '^AIW_SECRET_KEY=' .env && sed -i '' '/^AIW_SECRET_KEY=/d' .env
printf 'AIW_SECRET_KEY=%s\n' "$key" >> .env
fi
if [ -z "$(env_value AIW_DEMO_PASSWORD)" ]; then
echo "Hinweis: AIW_DEMO_PASSWORD in .env setzen, sonst ist die Demo-Anmeldung (anna, ben, clara) aus."
fi
fi
[ -f .env ] || fail "Keine .env. Ersteinrichtung mit: scripts/dev.sh --setup"
[ -n "${AIW_SECRET_KEY:-$(env_value AIW_SECRET_KEY)}" ] || fail "AIW_SECRET_KEY fehlt in .env (scripts/dev.sh --setup)."
port_busy "$PORT" && fail "Port $PORT ist belegt (läuft Bifröst schon?). Anderer Port: --port 8000"
say "PostgreSQL starten (127.0.0.1:55433)"
docker compose up -d --wait postgres
say "Migrationen"
uv run aiw migrate
$SETUP && { say "Demo-Nutzer anlegen"; uv run aiw seed-demo; }
: "${AIW_DB_ALLOW_LOCAL:=$(env_value AIW_DB_ALLOW_LOCAL)}"
: "${AIW_MCP_ALLOW_LOCAL:=$(env_value AIW_MCP_ALLOW_LOCAL)}"
export AIW_DB_ALLOW_LOCAL=${AIW_DB_ALLOW_LOCAL:-true}
export AIW_MCP_ALLOW_LOCAL=${AIW_MCP_ALLOW_LOCAL:-true}
export AIW_WORKER_HTTP_HOST=${AIW_WORKER_HTTP_HOST:-127.0.0.1}
export AIW_COOKIE_SECURE=${AIW_COOKIE_SECURE:-false}
export PYTHONUNBUFFERED=1
trap 'trap - INT TERM EXIT; echo; say "Stoppe Web, Worker und DB-Dienste"; kill 0 2>/dev/null; wait 2>/dev/null' INT TERM EXIT
PIDS=()
HINTS=()
# DB-Dienst starten, außer er läuft schon auf dem Port
db_service() {
local engine=$1 port=$2
if curl -sf "http://127.0.0.1:$port/ready" >/dev/null 2>&1; then
say "DB-Dienst $engine läuft schon auf Port $port (Schlüssel muss $DEV_KEY sein)"
else
port_busy "$port" && fail "Port $port ist belegt, aber dort antwortet kein DB-Dienst."
say "DB-Dienst $engine starten (Port $port)"
(cd services/db-mcp && DBMCP_API_KEYS="dev:$DEV_KEY" DBMCP_ALLOW_LOCAL=true DBMCP_PORT=$port \
DBMCP_BIND=127.0.0.1 "$ROOT/.venv/bin/python" -m "dbmcp.$engine" 2>&1 | prefix "$engine") &
PIDS+=($!)
for _ in $(seq 50); do curl -sf "http://127.0.0.1:$port/ready" >/dev/null 2>&1 && break; sleep 0.2; done
fi
HINTS+=("$engine: URL http://localhost:$port/mcp Schlüssel des Dienstes $DEV_KEY")
}
if $ORACLE; then
say "Oracle starten (127.0.0.1:55434, Service FREEPDB1; der erste Start dauert ein paar Minuten)"
docker compose --profile oracle up -d --wait oracle
db_service oracle 8792
fi
db_service postgres 8791
if $RESET; then
say "Demo-Workspaces anlegen (aiw demo)"
uv run aiw demo
fi
say "Web starten: http://127.0.0.1:$PORT"
(uv run aiw web --port "$PORT" 2>&1 | prefix web) &
PIDS+=($!)
say "Worker starten"
(uv run aiw worker 2>&1 | prefix worker) &
PIDS+=($!)
if $RESET; then
say "Fertig von null. Noch offen: als anna ein Modell einrichten (Administration → Modelle und Zugänge)."
say "Alles Weitere (Munin, Hugin, Dokumenten-Auslese, Bitbucket …): docs/einrichtung.md"
say "DB-Dienste (PostgreSQL ist für die Demo schon registriert):"
else
say "DB-Dienste, einmalig registrieren unter Administration → Datenbank-Treiber, dann Tools abrufen und einstufen:"
fi
for hint in "${HINTS[@]}"; do echo " $hint"; done
# Endet ein Prozess (z. B. Fehler beim Start), alles stoppen
while true; do
for pid in "${PIDS[@]}"; do
if ! kill -0 "$pid" 2>/dev/null; then
fail "Ein Prozess hat sich beendet (siehe Log oben)."
fi
done
sleep 1
done