#!/usr/bin/env bash # Set up Keycloak client `ai-platform-dashboard` on sso.clossers.com realm `didi-clients`. # Idempotent — safe to re-run; existing client gets updated to match this config. # # Usage: # KC_ADMIN_USER=admin KC_ADMIN_PASS=admin123 ./setup-keycloak.sh # # Optional overrides: # KC_BASE_URL=https://sso.clossers.com # KC_REALM=didi-clients # KC_CLIENT_ID=ai-platform-dashboard # DASHBOARD_HOST=10.11.10.12 # used to build redirect URI set -euo pipefail # Keycloak admin endpoints redirect public sso.clossers.com → internal # sso.clossers.local. Token issuer must match the host you call, so we use the # internal name end-to-end (this host has DNS for it). KC_BASE_URL="${KC_BASE_URL:-https://sso.clossers.local}" KC_REALM="${KC_REALM:-didi-clients}" KC_CLIENT_ID="${KC_CLIENT_ID:-ai-platform-dashboard}" KC_ADMIN_USER="${KC_ADMIN_USER:-admin}" KC_ADMIN_PASS="${KC_ADMIN_PASS:?Set KC_ADMIN_PASS}" DASHBOARD_HOST="${DASHBOARD_HOST:-10.11.10.12}" DASHBOARD_PORT="${DASHBOARD_PORT:-51300}" echo "→ Authenticating to Keycloak master realm…" ADMIN_TOKEN=$( curl -ksS -X POST "$KC_BASE_URL/realms/master/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=admin-cli&grant_type=password&username=$KC_ADMIN_USER&password=$KC_ADMIN_PASS" \ | python3 -c "import json,sys; print(json.load(sys.stdin)['access_token'])" ) [ -n "$ADMIN_TOKEN" ] || { echo "FAIL: could not get admin token"; exit 1; } echo " OK (token length ${#ADMIN_TOKEN})" # ---------- Step 1: ensure realm role 'admin' exists ---------- echo "→ Checking realm role 'admin' exists in realm '$KC_REALM'…" RAW=$(curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \ "$KC_BASE_URL/admin/realms/$KC_REALM/roles/admin") if echo "$RAW" | grep -q '"name"'; then echo " OK — role 'admin' present" else echo " NOT found — creating…" curl -ksSL -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"admin","description":"Platform admin (DIDI + AI platform)"}' \ "$KC_BASE_URL/admin/realms/$KC_REALM/roles" echo " CREATED" fi # ---------- Step 2: ensure client `ai-platform-dashboard` exists ---------- echo "→ Checking client '$KC_CLIENT_ID' exists…" EXISTING=$(curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$KC_CLIENT_ID") CLIENT_UUID=$(echo "$EXISTING" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['id'] if d else '')") REDIRECTS=$(python3 -c " import json print(json.dumps([ 'http://$DASHBOARD_HOST:$DASHBOARD_PORT/admin-ai/*', 'https://$DASHBOARD_HOST:$DASHBOARD_PORT/admin-ai/*', 'http://localhost:$DASHBOARD_PORT/admin-ai/*', 'http://localhost:5173/*', # Reverse-proxy URL (admin-dashboard nginx exposes /admin-ai/ on port 3000) 'https://$DASHBOARD_HOST:3000/admin-ai/*', ])) ") PAYLOAD=$(python3 -c " import json, os print(json.dumps({ 'clientId': '$KC_CLIENT_ID', 'name': 'AI Platform Admin Dashboard', 'description': 'Reskinned admin dashboard for the AI platform (web/llm/embeddings/rerank/audio/video/catalog/brain)', 'rootUrl': 'http://$DASHBOARD_HOST:$DASHBOARD_PORT/admin-ai/', 'baseUrl': '/admin-ai/', 'enabled': True, 'protocol': 'openid-connect', 'publicClient': True, 'standardFlowEnabled': True, 'directAccessGrantsEnabled': False, 'serviceAccountsEnabled': False, 'frontchannelLogout': True, 'redirectUris': $REDIRECTS, 'webOrigins': ['+'], 'attributes': { 'pkce.code.challenge.method': 'S256', 'post.logout.redirect.uris': '+', }, })) ") if [ -z "$CLIENT_UUID" ]; then echo " NOT found — creating…" curl -ksSL -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients" CLIENT_UUID=$( curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$KC_CLIENT_ID" \ | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['id'])" ) echo " CREATED uuid=$CLIENT_UUID" else echo " Found uuid=$CLIENT_UUID — updating to current spec…" curl -ksSL -X PUT -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients/$CLIENT_UUID" echo " UPDATED" fi # ---------- Step 3: smoke test JWKS reachable ---------- echo "→ Smoke test: JWKS endpoint reachable…" JWKS_KEYS=$(curl -ksS \ "$KC_BASE_URL/realms/$KC_REALM/protocol/openid-connect/certs" \ | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('keys',[])))") echo " OK — JWKS returns $JWKS_KEYS keys" # ---------- Step 3b: ensure admin-dashboard client also exists (DIDI side) ---------- # This is a no-op when the client already exists with correct config; included # so a fresh Keycloak install gets both apps wired in one go. ADMIN_DASH_CLIENT="admin-dashboard" echo "→ Checking client '$ADMIN_DASH_CLIENT' (DIDI admin) exists…" EXISTING_AD=$(curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$ADMIN_DASH_CLIENT") ADMIN_DASH_UUID=$(echo "$EXISTING_AD" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['id'] if d else '')") ADMIN_DASH_REDIRECTS=$(python3 -c " import json print(json.dumps([ 'https://$DASHBOARD_HOST:3000/admin/*', 'https://$DASHBOARD_HOST:3000/admin-backend/*', 'http://localhost:3000/admin/*', ])) ") ADMIN_DASH_PAYLOAD=$(python3 -c " import json print(json.dumps({ 'clientId': '$ADMIN_DASH_CLIENT', 'name': 'DIDI Admin Dashboard', 'description': 'Backend admin (framework config, users, moderation queue, history)', 'rootUrl': 'https://$DASHBOARD_HOST:3000/admin/', 'baseUrl': '/admin/', 'enabled': True, 'protocol': 'openid-connect', 'publicClient': True, 'standardFlowEnabled': True, 'directAccessGrantsEnabled': False, 'redirectUris': $ADMIN_DASH_REDIRECTS, 'webOrigins': ['+'], 'attributes': { 'pkce.code.challenge.method': 'S256', 'post.logout.redirect.uris': '+', }, })) ") if [ -z "$ADMIN_DASH_UUID" ]; then echo " NOT found — creating…" curl -ksSL -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "$ADMIN_DASH_PAYLOAD" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients" ADMIN_DASH_UUID=$( curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \ "$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$ADMIN_DASH_CLIENT" \ | python3 -c "import json,sys; print(json.load(sys.stdin)[0]['id'])" ) echo " CREATED uuid=$ADMIN_DASH_UUID" else echo " Found uuid=$ADMIN_DASH_UUID — leaving config as-is (would overwrite custom redirect URIs)" fi # ---------- Step 4: print summary ---------- cat <