#!/usr/bin/env bash set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" BACKEND_DIR="$SCRIPT_DIR/backend" FRONTEND_DIR="$SCRIPT_DIR/frontend" PIDFILE="$SCRIPT_DIR/.dev.pids" KC_CONTAINER="mgmt-keycloak-dev" KC_PORT=8180 KC_URL="http://localhost:$KC_PORT" KC_SECRET_FILE="$SCRIPT_DIR/.keycloak-secret" KC_OIDC_SECRET_FILE="$SCRIPT_DIR/.keycloak-oidc-secret" # ── Helpers ────────────────────────────────────────────────────── wait_for_keycloak() { echo "Waiting for Keycloak to be ready..." local attempts=0 local max_attempts=60 while [ $attempts -lt $max_attempts ]; do if curl -sf "$KC_URL/realms/master" >/dev/null 2>&1; then echo "Keycloak is ready." return 0 fi attempts=$((attempts + 1)) sleep 2 done echo "ERROR: Keycloak failed to start within $((max_attempts * 2)) seconds." exit 1 } ensure_venv() { if [ ! -d "$BACKEND_DIR/.venv" ]; then echo "Creating backend virtual environment..." python3 -m venv "$BACKEND_DIR/.venv" echo "Installing backend dependencies..." "$BACKEND_DIR/.venv/bin/pip" install -q -r "$BACKEND_DIR/requirements.txt" fi } seed_keycloak() { echo "Seeding Keycloak..." local output output=$("$BACKEND_DIR/.venv/bin/python" "$SCRIPT_DIR/scripts/seed-keycloak.py" --url "$KC_URL" 2>&1) echo "$output" # Extract client secret from seed output local secret secret=$(echo "$output" | grep 'KEYCLOAK_CLIENT_SECRET=' | tail -1 | sed 's/.*KEYCLOAK_CLIENT_SECRET=//') if [ -n "$secret" ] && [ "$secret" != "" ]; then echo "$secret" > "$KC_SECRET_FILE" elif [ -f "$KC_SECRET_FILE" ]; then echo "Using previously saved client secret." else echo "WARNING: Could not determine client secret. Check Keycloak admin console." fi # Extract OIDC client secret local oidc_secret oidc_secret=$(echo "$output" | grep 'KEYCLOAK_OIDC_CLIENT_SECRET=' | tail -1 | sed 's/.*KEYCLOAK_OIDC_CLIENT_SECRET=//') if [ -n "$oidc_secret" ] && [ "$oidc_secret" != "" ]; then echo "$oidc_secret" > "$KC_OIDC_SECRET_FILE" elif [ -f "$KC_OIDC_SECRET_FILE" ]; then echo "Using previously saved OIDC client secret." else echo "WARNING: Could not determine OIDC client secret. Check Keycloak admin console." fi } # ── Stop ───────────────────────────────────────────────────────── stop_services() { # Stop backend/frontend if [ -f "$PIDFILE" ]; then while read -r pid; do kill "$pid" 2>/dev/null || true done < "$PIDFILE" rm -f "$PIDFILE" echo "Backend and frontend stopped." else local pids pids=$(lsof -ti :5001,:5173 2>/dev/null || true) if [ -n "$pids" ]; then echo "$pids" | xargs kill 2>/dev/null || true echo "Backend and frontend stopped." fi fi # Stop keycloak container if podman ps -q -f name="^${KC_CONTAINER}$" 2>/dev/null | grep -q .; then podman stop "$KC_CONTAINER" >/dev/null 2>&1 podman rm "$KC_CONTAINER" >/dev/null 2>&1 echo "Keycloak stopped." else podman rm "$KC_CONTAINER" >/dev/null 2>&1 || true echo "Keycloak not running." fi } # ── Start ──────────────────────────────────────────────────────── start_services() { ensure_venv # Frontend setup if [ ! -d "$FRONTEND_DIR/node_modules" ]; then echo "Installing frontend dependencies..." (cd "$FRONTEND_DIR" && npm install) fi # Start Keycloak (if not already running) if ! podman ps -q -f name="^${KC_CONTAINER}$" 2>/dev/null | grep -q .; then echo "Starting Keycloak on $KC_URL..." podman rm "$KC_CONTAINER" >/dev/null 2>&1 || true podman run -d --name "$KC_CONTAINER" \ -p "$KC_PORT:8080" \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \ quay.io/keycloak/keycloak:26.2 start-dev >/dev/null else echo "Keycloak already running on $KC_URL" fi wait_for_keycloak seed_keycloak # Read the client secrets local kc_secret="" if [ -f "$KC_SECRET_FILE" ]; then kc_secret=$(cat "$KC_SECRET_FILE") fi local kc_oidc_secret="" if [ -f "$KC_OIDC_SECRET_FILE" ]; then kc_oidc_secret=$(cat "$KC_OIDC_SECRET_FILE") fi # Kill anything already on our ports local existing existing=$(lsof -ti :5001,:5173 2>/dev/null || true) if [ -n "$existing" ]; then echo "Stopping existing processes on ports 5001/5173..." echo "$existing" | xargs kill 2>/dev/null || true sleep 1 # Force kill if still around existing=$(lsof -ti :5001,:5173 2>/dev/null || true) if [ -n "$existing" ]; then echo "$existing" | xargs kill -9 2>/dev/null || true sleep 1 fi fi # Start backend with Keycloak env vars echo "Starting backend on http://localhost:5001..." ( cd "$BACKEND_DIR" export FLASK_DEBUG=true export KEYCLOAK_URL="$KC_URL" export KEYCLOAK_REALM=osa export KEYCLOAK_CLIENT_ID=osa-admin-client export KEYCLOAK_CLIENT_SECRET="$kc_secret" export KEYCLOAK_OIDC_CLIENT_ID=osa-web export KEYCLOAK_OIDC_CLIENT_SECRET="$kc_oidc_secret" exec .venv/bin/python run.py ) & BACKEND_PID=$! # Start frontend echo "Starting frontend on http://localhost:5173..." (cd "$FRONTEND_DIR" && npm run dev) & FRONTEND_PID=$! # Save PIDs printf '%s\n' "$BACKEND_PID" "$FRONTEND_PID" > "$PIDFILE" echo "" echo "OSA Management Suite running (with Keycloak):" echo " Frontend: http://localhost:5173" echo " Backend: http://localhost:5001" echo " Keycloak: $KC_URL (admin/admin)" echo " Login: admin / admin" echo "" echo "Press Ctrl+C to stop backend/frontend." echo "Use './dev.sh --stop' to stop everything including Keycloak." cleanup() { echo "" echo "Shutting down backend and frontend..." kill $BACKEND_PID $FRONTEND_PID 2>/dev/null wait $BACKEND_PID $FRONTEND_PID 2>/dev/null rm -f "$PIDFILE" echo "Done. Keycloak is still running — use './dev.sh --stop' to stop it." } trap cleanup EXIT INT TERM wait } # ── Entrypoint ─────────────────────────────────────────────────── case "${1:-}" in --stop) stop_services ;; --restart) stop_services sleep 1 start_services ;; *) start_services ;; esac