bifroest/scripts/dev.sh
2026-10-28 10:19:27 +01:00

213 lines
9 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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