Files
mgmt/dev.sh
scott fc3bfdd299 make Keycloak the primary login method, local login as break-glass
- Keycloak SSO button is now the prominent primary action on the login page
- Local username/password form hidden behind collapsible "Admin access" toggle
- Falls back to username/password form when Keycloak is not configured
- Rename container to mgmt-keycloak, backup filename to mgmt-backup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:13:39 -07:00

213 lines
7.0 KiB
Bash
Executable File

#!/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" != "<check Keycloak admin console>" ]; 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" != "<check Keycloak admin console>" ]; 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