#!/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; } # Lauscht etwas auf dem Port? Mit lsof, sonst (z. B. Linux ohne lsof) per Verbindungsversuch port_busy() { if command -v lsof >/dev/null 2>&1; then lsof -nP -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1 else (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null fi } # Wer belegt den Port? (für die Fehlermeldung; leer, wenn es sich nicht feststellen lässt) port_owner() { command -v lsof >/dev/null 2>&1 || return 0 lsof -nP -iTCP:"$1" -sTCP:LISTEN 2>/dev/null | awk 'NR == 2 { print $1 " (PID " $2 ")" }' } 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-postgres18 >/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 (admin, entwicklung, fachbereich, freigabe) 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 $(port_owner "$PORT") (läuft Bifröst schon?). Anderer Port: --port 8000" WORKER_PORT=${AIW_WORKER_HTTP_PORT:-$(env_value AIW_WORKER_HTTP_PORT)} WORKER_PORT=${WORKER_PORT:-9101} port_busy "$WORKER_PORT" && fail "Port $WORKER_PORT (Health des Workers) ist belegt $(port_owner "$WORKER_PORT") \ – läuft noch ein Worker? Stoppen oder AIW_WORKER_HTTP_PORT in .env ändern." 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 # Gestartete Prozesse: PID, Name und Befehl zum Einzelstart (parallele Listen, Bash 3 von macOS kennt keine Maps) PIDS=() NAMES=() COMMANDS=() started() { PIDS+=("$1"); NAMES+=("$2"); COMMANDS+=("$3"); } 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") & started $! "DB-Dienst $engine (Port $port)" \ "cd services/db-mcp && DBMCP_API_KEYS=dev:$DEV_KEY DBMCP_ALLOW_LOCAL=true DBMCP_PORT=$port ../../.venv/bin/python -m dbmcp.$engine" 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) & started $! "Web (Port $PORT)" "uv run aiw web --port $PORT" say "Worker starten" (uv run aiw worker 2>&1 | prefix worker) & started $! "Worker" "uv run aiw worker" if $RESET; then say "Fertig von null. Noch offen: als admin ein Modell einrichten (Administration → Modelle und Zugänge)." say "Alles Weitere (Munin, Hugin, Dokumenten-Auslese, Bitbucket …): docs/checkliste.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), sagen welcher und alles stoppen while true; do for i in "${!PIDS[@]}"; do if ! kill -0 "${PIDS[$i]}" 2>/dev/null; then code=0 wait "${PIDS[$i]}" 2>/dev/null || code=$? echo printf '\033[31m%s hat sich beendet (Exit-Code %s).\033[0m\n' "${NAMES[$i]}" "$code" >&2 echo "Seine Ausgabe steht oben mit Präfix; fehlt sie, den Prozess einzeln starten und die Meldung ansehen:" >&2 echo " ${COMMANDS[$i]}" >&2 exit 1 fi done sleep 1 done