- 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
178 lines
5.6 KiB
Bash
Executable File
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
|