149 lines
4.9 KiB
Bash
Executable File
149 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# One entry point for the complete local CASAN lab. It keeps the infrastructure
|
|
# lab and the authenticated Control Panel as separate Compose projects so their
|
|
# lifecycle can be managed without port/network collisions.
|
|
#
|
|
# Usage: local-full.sh start|stop|status|verify|smoke|env
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/casan-paths.sh"
|
|
ROOT="$CASAN_APP_ROOT"
|
|
INFRA="$SCRIPT_DIR/infra-lab.sh"
|
|
CP_COMPOSE="$ROOT/docker-compose.control-panel.local.yml"
|
|
LOCAL_ENV="$ROOT/infra/local-prod/casan.local.env"
|
|
TLS_DIR="$ROOT/tmp/control-panel-local/tls"
|
|
AUTH_BRIDGE_DIR="$ROOT/tmp/control-panel-local/auth-bridge"
|
|
AUTH_BRIDGE_TOKEN_FILE="$AUTH_BRIDGE_DIR/token"
|
|
AUTH_BRIDGE_PID_FILE="$AUTH_BRIDGE_DIR/bridge.pid"
|
|
AUTH_BRIDGE_LOG="$AUTH_BRIDGE_DIR/bridge.log"
|
|
AUTH_BRIDGE_AUDIT="$AUTH_BRIDGE_DIR/model-audit.jsonl"
|
|
AUTH_BRIDGE="$ROOT/packages/casan-control-panel/scripts/provider-auth-bridge.py"
|
|
CMD="${1:-status}"
|
|
|
|
cp_compose() {
|
|
if [[ -f "$AUTH_BRIDGE_TOKEN_FILE" ]]; then
|
|
export CASAN_AUTH_BRIDGE_TOKEN
|
|
CASAN_AUTH_BRIDGE_TOKEN="$(tr -d '\r\n' < "$AUTH_BRIDGE_TOKEN_FILE")"
|
|
fi
|
|
if [[ -f "$LOCAL_ENV" ]]; then
|
|
docker compose --env-file "$LOCAL_ENV" -f "$CP_COMPOSE" "$@"
|
|
else
|
|
docker compose -f "$CP_COMPOSE" "$@"
|
|
fi
|
|
}
|
|
|
|
start_auth_bridge() {
|
|
mkdir -p "$AUTH_BRIDGE_DIR"
|
|
if [[ ! -s "$AUTH_BRIDGE_TOKEN_FILE" ]]; then
|
|
openssl rand -hex 32 > "$AUTH_BRIDGE_TOKEN_FILE"
|
|
chmod 600 "$AUTH_BRIDGE_TOKEN_FILE"
|
|
fi
|
|
if [[ -f "$AUTH_BRIDGE_PID_FILE" ]] && kill -0 "$(cat "$AUTH_BRIDGE_PID_FILE")" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
[[ -f "$AUTH_BRIDGE" ]] || { echo "CASAN_AUTH_BRIDGE_MISSING" >&2; return 1; }
|
|
nohup python3 "$AUTH_BRIDGE" --bind 0.0.0.0 --port 20130 --token-file "$AUTH_BRIDGE_TOKEN_FILE" --audit-log "$AUTH_BRIDGE_AUDIT" > "$AUTH_BRIDGE_LOG" 2>&1 &
|
|
echo "$!" > "$AUTH_BRIDGE_PID_FILE"
|
|
chmod 600 "$AUTH_BRIDGE_PID_FILE" "$AUTH_BRIDGE_LOG" "$AUTH_BRIDGE_AUDIT" 2>/dev/null || true
|
|
wait_url "http://127.0.0.1:20130/healthz"
|
|
}
|
|
|
|
stop_auth_bridge() {
|
|
if [[ -f "$AUTH_BRIDGE_PID_FILE" ]]; then
|
|
local pid
|
|
pid="$(cat "$AUTH_BRIDGE_PID_FILE")"
|
|
kill "$pid" 2>/dev/null || true
|
|
rm -f "$AUTH_BRIDGE_PID_FILE"
|
|
fi
|
|
rm -f "$AUTH_BRIDGE_TOKEN_FILE"
|
|
}
|
|
|
|
need_docker() {
|
|
command -v docker >/dev/null 2>&1 || { echo "CASAN_LOCAL_DOCKER_MISSING" >&2; exit 1; }
|
|
docker compose version >/dev/null 2>&1 || { echo "CASAN_LOCAL_COMPOSE_MISSING" >&2; exit 1; }
|
|
}
|
|
|
|
ensure_tls() {
|
|
mkdir -p "$TLS_DIR"
|
|
if [[ ! -f "$TLS_DIR/tls.crt" || ! -f "$TLS_DIR/tls.key" ]]; then
|
|
openssl req -x509 -newkey rsa:2048 -nodes \
|
|
-keyout "$TLS_DIR/tls.key" -out "$TLS_DIR/tls.crt" \
|
|
-subj "/CN=localhost" -days 30 >/dev/null 2>&1
|
|
fi
|
|
}
|
|
|
|
wait_url() {
|
|
local url="$1"
|
|
for _ in $(seq 1 60); do
|
|
curl -k -fsS -m 3 "$url" >/dev/null 2>&1 && return 0
|
|
sleep 1
|
|
done
|
|
echo "CASAN_LOCAL_WAIT_TIMEOUT url=$url" >&2
|
|
return 1
|
|
}
|
|
|
|
case "$CMD" in
|
|
start)
|
|
need_docker
|
|
bash "$INFRA" start
|
|
ensure_tls
|
|
start_auth_bridge
|
|
cp_compose up -d --build
|
|
wait_url "http://127.0.0.1:18082/healthz"
|
|
# The unauthenticated console intentionally redirects, so test the OIDC IdP
|
|
# here; `smoke` performs the complete logged-in browser flow.
|
|
echo "CASAN_LOCAL_FULL_STARTED"
|
|
echo "dashboard=http://127.0.0.1:18080 (basic auth: casan / casan)"
|
|
echo "minio_console=http://127.0.0.1:19091 (casanadmin / casanadmin123)"
|
|
echo "control_panel=https://localhost:18443 (self-signed TLS; mock OIDC login)"
|
|
;;
|
|
stop)
|
|
need_docker
|
|
cp_compose down --remove-orphans
|
|
stop_auth_bridge
|
|
bash "$INFRA" stop
|
|
echo "CASAN_LOCAL_FULL_STOPPED"
|
|
;;
|
|
status)
|
|
need_docker
|
|
echo "=== infrastructure ==="
|
|
bash "$INFRA" status
|
|
echo "=== control panel ==="
|
|
cp_compose ps
|
|
if [[ -f "$AUTH_BRIDGE_PID_FILE" ]] && kill -0 "$(cat "$AUTH_BRIDGE_PID_FILE")" 2>/dev/null; then
|
|
echo "provider_auth_bridge=running pid=$(cat "$AUTH_BRIDGE_PID_FILE")"
|
|
else
|
|
echo "provider_auth_bridge=stopped"
|
|
fi
|
|
;;
|
|
verify)
|
|
need_docker
|
|
bash "$INFRA" verify
|
|
cp_compose ps
|
|
wait_url "http://127.0.0.1:18082/healthz"
|
|
wait_url "http://127.0.0.1:20130/healthz"
|
|
echo "CASAN_LOCAL_FULL_VERIFY_PASS"
|
|
;;
|
|
smoke)
|
|
need_docker
|
|
bash "$ROOT/packages/casan-control-panel/scripts/local-prod-smoke.sh"
|
|
;;
|
|
env)
|
|
bash "$INFRA" env
|
|
printf '%s\n' \
|
|
'# OmniRoute is opt-in. Replace <model-id> with an ID returned by /v1/models.' \
|
|
'export CASAN_OPENAI_COMPATIBLE_BASE_URL=http://127.0.0.1:20128/v1' \
|
|
'export CASAN_OPENAI_COMPATIBLE_API_KEY="$OPENAI_API_KEY"' \
|
|
'export CASAN_MODEL_PRIMARY=openai-compatible:<model-id>' \
|
|
'export CASAN_CHAT_MODEL_MODE=model' \
|
|
'export CASAN_CHAT_MODEL_PROVIDER=omniroute' \
|
|
'export CASAN_PREFLIGHT=1'
|
|
;;
|
|
*)
|
|
echo "Usage: $0 start|stop|status|verify|smoke|env" >&2
|
|
exit 64
|
|
;;
|
|
esac
|