Files
proxmox/scripts/deployment/keycloak-bootstrap-or-reset-master-admin-db.sh
defiQUG 7ac74f432b chore: sync docs, config schemas, scripts, and meta task alignment
- Institutional / JVMTM / reserve-provenance / GRU transport + standards JSON
- Validation and verify scripts (Blockscout labels, x402, GRU preflight, P1 local path)
- Wormhole wiring in AGENTS, MCP_SETUP, MASTER_INDEX, 04-configuration README
- Meta docs, integration gaps, live verification log, architecture updates
- CI validate-config workflow updates

Operator/LAN items, submodule working trees, and public token-aggregation edge
routes remain follow-up (see TODOS_CONSOLIDATED P1).

Made-with: Cursor
2026-03-31 22:31:39 -07:00

178 lines
5.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Create or reset the Keycloak master-realm "admin" user directly in PostgreSQL (Keycloak 24 Quarkus
# has no bootstrap-admin CLI). Use when user_entity is empty or you must rotate the admin password.
#
# Requirements: SSH to Proxmox, pct to PostgreSQL CT (default 7803), sudo postgres psql on DB "keycloak".
# Does not print the password to stdout; writes it to a file you pass, or merges into repo .env.
#
# Usage:
# KEYCLOAK_ADMIN_PASSWORD='your-secure-value' ./scripts/deployment/keycloak-bootstrap-or-reset-master-admin-db.sh
# ./scripts/deployment/keycloak-bootstrap-or-reset-master-admin-db.sh # generates password → .env
#
# Env:
# PROXMOX_HOST (default 192.168.11.11), POSTGRES_CT_VMID (7803), KEYCLOAK_CT_VMID (7802)
# KEYCLOAK_ADMIN_USERNAME (default admin), KEYCLOAK_DB_NAME (keycloak)
# KEYCLOAK_ADMIN_PASSWORD — if unset, a random alphanumeric password is generated
# WRITE_ENV_FILE — path to .env to upsert KEYCLOAK_ADMIN + KEYCLOAK_ADMIN_PASSWORD (default: repo .env)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# shellcheck source=/dev/null
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
POSTGRES_CT_VMID="${POSTGRES_CT_VMID:-7803}"
KEYCLOAK_CT_VMID="${KEYCLOAK_CT_VMID:-${SANKOFA_KEYCLOAK_VMID:-7802}}"
ADMIN_USER="${KEYCLOAK_ADMIN:-admin}"
DB_NAME="${KEYCLOAK_DB_NAME:-keycloak}"
WRITE_ENV_FILE="${WRITE_ENV_FILE:-${PROJECT_ROOT}/.env}"
SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15)
gen_pass() {
openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32
}
NEW_PASS="${KEYCLOAK_ADMIN_PASSWORD:-}"
if [[ -z "$NEW_PASS" ]]; then
NEW_PASS="$(gen_pass)"
fi
SQL_GEN="$(mktemp)"
trap 'rm -f "$SQL_GEN"' EXIT
python3 - "$NEW_PASS" "$ADMIN_USER" >"$SQL_GEN" <<'PY'
import json, base64, hashlib, os, sys, time, uuid
password, admin_user = sys.argv[1], sys.argv[2]
salt = os.urandom(16)
iters = 27500
dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iters)
secret_data = json.dumps(
{
"value": base64.b64encode(dk).decode(),
"salt": base64.b64encode(salt).decode(),
"additionalParameters": {},
},
separators=(",", ":"),
)
credential_data = json.dumps(
{"hashIterations": iters, "algorithm": "pbkdf2-sha256", "additionalParameters": {}},
separators=(",", ":"),
)
ts = int(time.time() * 1000)
user_id = str(uuid.uuid4())
cred_id = str(uuid.uuid4())
def q(s: str) -> str:
return s.replace("'", "''")
sd, cd = q(secret_data), q(credential_data)
user_esc = q(admin_user)
print("BEGIN;")
print(
f"""
DO $do$
DECLARE
rid TEXT;
r_admin TEXT;
r_default TEXT;
uid TEXT;
n INT;
v_secret TEXT := '{sd}';
v_cred TEXT := '{cd}';
BEGIN
SELECT id INTO rid FROM realm WHERE name = 'master' LIMIT 1;
IF rid IS NULL THEN
RAISE EXCEPTION 'realm master not found';
END IF;
SELECT id INTO r_admin FROM keycloak_role
WHERE realm_id = rid AND name = 'admin' AND client IS NULL LIMIT 1;
SELECT id INTO r_default FROM keycloak_role
WHERE realm_id = rid AND name = 'default-roles-master' AND client IS NULL LIMIT 1;
IF r_admin IS NULL OR r_default IS NULL THEN
RAISE EXCEPTION 'missing admin or default-roles-master role';
END IF;
SELECT COUNT(*) INTO n FROM user_entity WHERE realm_id = rid AND username = '{user_esc}';
IF n = 0 THEN
INSERT INTO user_entity (
id, email, email_constraint, email_verified, enabled, realm_id, username, created_timestamp, not_before
) VALUES (
'{user_id}',
'{user_esc}@sankofa.nexus',
'{user_esc}@sankofa.nexus',
true,
true,
rid,
'{user_esc}',
{ts},
0
);
uid := '{user_id}';
INSERT INTO user_role_mapping (role_id, user_id) VALUES (r_admin, uid);
INSERT INTO user_role_mapping (role_id, user_id) VALUES (r_default, uid);
ELSE
SELECT id INTO uid FROM user_entity WHERE realm_id = rid AND username = '{user_esc}' LIMIT 1;
END IF;
DELETE FROM credential WHERE user_id = uid AND type = 'password';
INSERT INTO credential (id, salt, type, user_id, created_date, user_label, secret_data, credential_data, priority)
VALUES (
'{cred_id}',
NULL,
'password',
uid,
{ts},
NULL,
v_secret,
v_cred,
10
);
END
$do$;
"""
)
print("COMMIT;")
PY
ssh "${SSH_OPTS[@]}" "root@${PROXMOX_HOST}" \
"pct exec ${POSTGRES_CT_VMID} -- sudo -u postgres psql -d ${DB_NAME} -v ON_ERROR_STOP=1 -f -" <"$SQL_GEN"
ssh "${SSH_OPTS[@]}" "root@${PROXMOX_HOST}" \
"pct exec ${KEYCLOAK_CT_VMID} -- systemctl restart keycloak"
echo "[ok] Keycloak master admin user '${ADMIN_USER}' password set in DB; Keycloak restarted on CT ${KEYCLOAK_CT_VMID}."
if [[ -n "${WRITE_ENV_FILE}" ]]; then
python3 - "${WRITE_ENV_FILE}" "${NEW_PASS}" "${ADMIN_USER}" <<'PY'
import re
import sys
from pathlib import Path
path, password, admin_user = Path(sys.argv[1]), sys.argv[2], sys.argv[3]
text = path.read_text() if path.exists() else ""
def upsert_line(body: str, key: str, value: str) -> str:
line = f"{key}={value}"
if re.search(rf"^{re.escape(key)}=", body, flags=re.M):
return re.sub(rf"^{re.escape(key)}=.*$", line, body, flags=re.M, count=1)
if body and not body.endswith("\n"):
body += "\n"
return body + line + "\n"
text = upsert_line(text, "KEYCLOAK_ADMIN", admin_user)
text = upsert_line(text, "KEYCLOAK_ADMIN_PASSWORD", password)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text)
PY
echo "[ok] Updated ${WRITE_ENV_FILE} (KEYCLOAK_ADMIN, KEYCLOAK_ADMIN_PASSWORD)."
fi