#!/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