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
This commit is contained in:
@@ -19,7 +19,7 @@ fi
|
||||
|
||||
LXCS=(
|
||||
"${RTGS_ORCH_VMID:-5805} ${RTGS_ORCH_HOSTNAME:-rtgs-orchestrator-1} ${RTGS_ORCH_IP:-192.168.11.93} 4096 2 24"
|
||||
"${RTGS_FX_VMID:-5806} ${RTGS_FX_HOSTNAME:-rtgs-fx-1} ${RTGS_FX_IP:-192.168.11.94} 4096 2 24"
|
||||
"${RTGS_FX_VMID:-5806} ${RTGS_FX_HOSTNAME:-rtgs-fx-1} ${RTGS_FX_IP:-192.168.11.99} 4096 2 24"
|
||||
"${RTGS_LIQ_VMID:-5807} ${RTGS_LIQ_HOSTNAME:-rtgs-liquidity-1} ${RTGS_LIQ_IP:-192.168.11.95} 4096 2 24"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
REPO_ROOT="$PROJECT_ROOT"
|
||||
SMOM="${PROJECT_ROOT}/smom-dbis-138"
|
||||
|
||||
DRY_RUN=""
|
||||
@@ -37,9 +38,21 @@ if [[ ! -f "$SMOM/.env" ]]; then
|
||||
echo "Missing $SMOM/.env. Abort." >&2
|
||||
exit 1
|
||||
fi
|
||||
set -a
|
||||
source "$SMOM/.env"
|
||||
set +a
|
||||
if [[ -f "$SMOM/scripts/lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SMOM/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$SMOM"
|
||||
else
|
||||
had_nounset=0
|
||||
if [[ $- == *u* ]]; then
|
||||
had_nounset=1
|
||||
set +u
|
||||
fi
|
||||
set -a
|
||||
source "$SMOM/.env"
|
||||
set +a
|
||||
(( had_nounset )) && set -u
|
||||
fi
|
||||
|
||||
# 2) RPC: Core (2101) only — no Public fallback for deployments
|
||||
RPC="${RPC_URL_138:-http://192.168.11.211:8545}"
|
||||
@@ -169,4 +182,4 @@ done
|
||||
|
||||
echo ""
|
||||
echo "Running on-chain verification..."
|
||||
"$PROJECT_ROOT/scripts/verify/check-contracts-on-chain-138.sh" "$RPC"
|
||||
"$REPO_ROOT/scripts/verify/check-contracts-on-chain-138.sh" "$RPC"
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
# - Remove .env.local on CT 7801; install .env with PORTAL_LOCAL_LOGIN_* + NEXTAUTH_SECRET.
|
||||
# - Run sync-sankofa-portal-7801.sh (rebuild portal with updated auth.ts).
|
||||
#
|
||||
# Keycloak SSO: If repo .env defines KEYCLOAK_CLIENT_SECRET (and optional KEYCLOAK_URL / REALM /
|
||||
# CLIENT_ID), those values are written into the pushed .env. After sync, sankofa-portal-merge-keycloak-env-from-repo.sh
|
||||
# runs to mirror OIDC settings into .env.local as well. Without KEYCLOAK_CLIENT_SECRET in .env,
|
||||
# use keycloak-sankofa-ensure-client-redirects*.sh then the merge script.
|
||||
#
|
||||
# Usage: ./scripts/deployment/enable-sankofa-portal-login-7801.sh [--dry-run]
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" 2>/dev/null || true
|
||||
# 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}}"
|
||||
@@ -20,6 +27,9 @@ LOCAL_EMAIL="${PORTAL_LOCAL_LOGIN_EMAIL:-portal@sankofa.nexus}"
|
||||
|
||||
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||
echo "[DRY-RUN] Would patch Keycloak ${VMID_KC}, write .env on ${VMID_PORTAL}, sync portal"
|
||||
if [[ -n "${KEYCLOAK_CLIENT_SECRET:-}" ]]; then
|
||||
echo "[DRY-RUN] Would run sankofa-portal-merge-keycloak-env-from-repo.sh after sync (KEYCLOAK_CLIENT_SECRET is set)"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -34,10 +44,10 @@ NEXT_PUBLIC_GRAPHQL_WS_ENDPOINT=ws://192.168.11.50:4000/graphql-ws
|
||||
|
||||
NEXTAUTH_URL=https://portal.sankofa.nexus
|
||||
NEXTAUTH_SECRET=${NEXTAUTH_SEC}
|
||||
KEYCLOAK_URL=https://keycloak.sankofa.nexus
|
||||
KEYCLOAK_REALM=master
|
||||
KEYCLOAK_CLIENT_ID=sankofa-portal
|
||||
KEYCLOAK_CLIENT_SECRET=
|
||||
KEYCLOAK_URL=${KEYCLOAK_URL:-https://keycloak.sankofa.nexus}
|
||||
KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
|
||||
KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-sankofa-portal}
|
||||
KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-}
|
||||
|
||||
PORTAL_LOCAL_LOGIN_EMAIL=${LOCAL_EMAIL}
|
||||
PORTAL_LOCAL_LOGIN_PASSWORD=${GEN_PASS}
|
||||
@@ -83,12 +93,24 @@ echo ""
|
||||
echo "📤 Syncing portal source + rebuild…"
|
||||
bash "${SCRIPT_DIR}/sync-sankofa-portal-7801.sh"
|
||||
|
||||
if [[ -n "${KEYCLOAK_CLIENT_SECRET:-}" ]]; then
|
||||
echo ""
|
||||
echo "🔐 Mirroring Keycloak OIDC into portal .env + .env.local (merge script)…"
|
||||
bash "${SCRIPT_DIR}/sankofa-portal-merge-keycloak-env-from-repo.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ Sign in at https://portal.sankofa.nexus (NEXTAUTH_URL)"
|
||||
echo " Email: ${LOCAL_EMAIL}"
|
||||
echo " Password: ${GEN_PASS}"
|
||||
echo ""
|
||||
echo "SSO: Add NPM host keycloak.sankofa.nexus → ${IP_KEYCLOAK:-192.168.11.52}:8080, then create Keycloak"
|
||||
echo " confidential client sankofa-portal; set KEYCLOAK_CLIENT_SECRET in .env and re-sync."
|
||||
if [[ -n "${KEYCLOAK_CLIENT_SECRET:-}" ]]; then
|
||||
echo "SSO: Keycloak client secret was taken from repo .env; portal CT updated via merge script."
|
||||
else
|
||||
echo "SSO: No KEYCLOAK_CLIENT_SECRET in repo .env — local login only until you:"
|
||||
echo " 1) NPM: keycloak.sankofa.nexus → ${IP_KEYCLOAK:-192.168.11.52}:8080"
|
||||
echo " 2) ./scripts/deployment/keycloak-sankofa-ensure-client-redirects-via-proxmox-pct.sh"
|
||||
echo " 3) ./scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
177
scripts/deployment/keycloak-bootstrap-or-reset-master-admin-db.sh
Executable file
177
scripts/deployment/keycloak-bootstrap-or-reset-master-admin-db.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/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
|
||||
194
scripts/deployment/keycloak-sankofa-ensure-client-redirects-via-proxmox-pct.sh
Executable file
194
scripts/deployment/keycloak-sankofa-ensure-client-redirects-via-proxmox-pct.sh
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create or update Keycloak OIDC client (default sankofa-portal) with portal/admin redirect URIs.
|
||||
# Runs Admin API against http://127.0.0.1:8080 inside the Keycloak CT (no LAN to NPM required).
|
||||
# After a new client is created, repo .env gets KEYCLOAK_CLIENT_SECRET — push it to CT 7801 with
|
||||
# ./scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh
|
||||
#
|
||||
# If the client is created, prints a JSON footer line for the operator .env:
|
||||
# __SANKOFA_KEYCLOAK_FOOTER__{"created":true,"clientSecret":"..."}
|
||||
#
|
||||
# Loads repo .env. Env: PROXMOX_HOST, KEYCLOAK_CT_VMID (7802), KEYCLOAK_* .
|
||||
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
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$PROJECT_ROOT/.env" 2>/dev/null || true
|
||||
set +a
|
||||
set -u
|
||||
fi
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
|
||||
KEYCLOAK_CT_VMID="${KEYCLOAK_CT_VMID:-${SANKOFA_KEYCLOAK_VMID:-7802}}"
|
||||
REALM="${KEYCLOAK_REALM:-master}"
|
||||
CLIENT_ID="${KEYCLOAK_CLIENT_ID:-sankofa-portal}"
|
||||
ADMIN_USER="${KEYCLOAK_ADMIN:-admin}"
|
||||
ADMIN_PASS="${KEYCLOAK_ADMIN_PASSWORD:-}"
|
||||
SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15)
|
||||
|
||||
if [ -z "$ADMIN_PASS" ]; then
|
||||
echo "KEYCLOAK_ADMIN_PASSWORD is not set in .env" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUT="$(
|
||||
ssh "${SSH_OPTS[@]}" "root@${PROXMOX_HOST}" \
|
||||
"pct exec ${KEYCLOAK_CT_VMID} -- env KC_PASS=\"${ADMIN_PASS}\" ADMUSER=\"${ADMIN_USER}\" REALM=\"${REALM}\" CLIENT_ID=\"${CLIENT_ID}\" python3 -u -" <<'PY'
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
base = "http://127.0.0.1:8080"
|
||||
realm = os.environ["REALM"]
|
||||
client_id = os.environ["CLIENT_ID"]
|
||||
admin_user = os.environ["ADMUSER"]
|
||||
password = os.environ["KC_PASS"]
|
||||
|
||||
desired_redirects = [
|
||||
"https://portal.sankofa.nexus/*",
|
||||
"https://portal.sankofa.nexus",
|
||||
"https://admin.sankofa.nexus/*",
|
||||
"https://admin.sankofa.nexus",
|
||||
]
|
||||
desired_origins = [
|
||||
"https://portal.sankofa.nexus",
|
||||
"https://admin.sankofa.nexus",
|
||||
]
|
||||
created_new = False
|
||||
portal_secret = None
|
||||
|
||||
|
||||
def post_form(url: str, data: dict) -> dict:
|
||||
body = urllib.parse.urlencode(data).encode()
|
||||
req = urllib.request.Request(url, data=body, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
|
||||
|
||||
tok = post_form(
|
||||
f"{base}/realms/master/protocol/openid-connect/token",
|
||||
{
|
||||
"grant_type": "password",
|
||||
"client_id": "admin-cli",
|
||||
"username": admin_user,
|
||||
"password": password,
|
||||
},
|
||||
)
|
||||
access = tok.get("access_token")
|
||||
if not access:
|
||||
raise SystemExit(f"token failed: {tok}")
|
||||
|
||||
list_url = f"{base}/admin/realms/{realm}/clients?clientId={urllib.parse.quote(client_id)}"
|
||||
r = urllib.request.Request(list_url, headers={"Authorization": f"Bearer {access}"})
|
||||
with urllib.request.urlopen(r, timeout=60) as resp:
|
||||
clients = json.loads(resp.read().decode())
|
||||
|
||||
if not clients:
|
||||
portal_secret = secrets.token_urlsafe(48)
|
||||
new_client = {
|
||||
"clientId": client_id,
|
||||
"name": "Sankofa Portal",
|
||||
"enabled": True,
|
||||
"protocol": "openid-connect",
|
||||
"publicClient": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"redirectUris": desired_redirects,
|
||||
"webOrigins": desired_origins,
|
||||
"secret": portal_secret,
|
||||
}
|
||||
cr = urllib.request.Request(
|
||||
f"{base}/admin/realms/{realm}/clients",
|
||||
data=json.dumps(new_client).encode(),
|
||||
headers={"Authorization": f"Bearer {access}", "Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(cr, timeout=120) as resp:
|
||||
if resp.getcode() not in (200, 201):
|
||||
raise SystemExit(f"create client unexpected HTTP {resp.getcode()}")
|
||||
except urllib.error.HTTPError as e:
|
||||
err = e.read().decode() if e.fp else str(e)
|
||||
raise SystemExit(f"POST client failed HTTP {e.code}: {err}") from e
|
||||
created_new = True
|
||||
r = urllib.request.Request(list_url, headers={"Authorization": f"Bearer {access}"})
|
||||
with urllib.request.urlopen(r, timeout=60) as resp:
|
||||
clients = json.loads(resp.read().decode())
|
||||
if not clients:
|
||||
raise SystemExit("client create did not persist")
|
||||
|
||||
internal_id = clients[0]["id"]
|
||||
get_url = f"{base}/admin/realms/{realm}/clients/{internal_id}"
|
||||
r2 = urllib.request.Request(get_url, headers={"Authorization": f"Bearer {access}"})
|
||||
with urllib.request.urlopen(r2, timeout=60) as resp:
|
||||
full = json.loads(resp.read().decode())
|
||||
|
||||
redirs = list(dict.fromkeys((full.get("redirectUris") or []) + desired_redirects))
|
||||
origins = list(dict.fromkeys((full.get("webOrigins") or []) + desired_origins))
|
||||
full["redirectUris"] = redirs
|
||||
full["webOrigins"] = origins
|
||||
if portal_secret and not full.get("secret"):
|
||||
full["secret"] = portal_secret
|
||||
|
||||
put = urllib.request.Request(
|
||||
get_url,
|
||||
data=json.dumps(full).encode(),
|
||||
headers={"Authorization": f"Bearer {access}", "Content-Type": "application/json"},
|
||||
method="PUT",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(put, timeout=120) as resp:
|
||||
code = resp.getcode()
|
||||
except urllib.error.HTTPError as e:
|
||||
err = e.read().decode() if e.fp else str(e)
|
||||
raise SystemExit(f"PUT failed HTTP {e.code}: {err}") from e
|
||||
|
||||
if code not in (200, 204):
|
||||
raise SystemExit(f"PUT unexpected HTTP {code}")
|
||||
action = "Created" if created_new else "Updated"
|
||||
print(f"{action} Keycloak client {client_id!r} (redirect URIs + web origins).", flush=True)
|
||||
footer = {"created": bool(created_new)}
|
||||
if portal_secret:
|
||||
footer["clientSecret"] = portal_secret
|
||||
print("__SANKOFA_KEYCLOAK_FOOTER__" + json.dumps(footer), flush=True)
|
||||
PY
|
||||
)"
|
||||
|
||||
echo "$OUT" | sed '/__SANKOFA_KEYCLOAK_FOOTER__/d'
|
||||
FOOTER=$(echo "$OUT" | grep '^__SANKOFA_KEYCLOAK_FOOTER__' | sed 's/^__SANKOFA_KEYCLOAK_FOOTER__//' || true)
|
||||
if [[ -n "$FOOTER" ]]; then
|
||||
CREATED="$(echo "$FOOTER" | jq -r '.created // false')"
|
||||
SEC="$(echo "$FOOTER" | jq -r '.clientSecret // empty')"
|
||||
if [[ "$CREATED" == "true" ]] && [[ -n "$SEC" ]] && [[ "$SEC" != "null" ]]; then
|
||||
python3 - "${PROJECT_ROOT}/.env" "${SEC}" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path, sec = Path(sys.argv[1]), sys.argv[2]
|
||||
text = path.read_text() if path.exists() else ""
|
||||
|
||||
def upsert(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(text, "KEYCLOAK_CLIENT_SECRET", sec)
|
||||
path.write_text(text)
|
||||
PY
|
||||
echo "[ok] Wrote KEYCLOAK_CLIENT_SECRET to .env (portal Keycloak OIDC path enabled)." >&2
|
||||
fi
|
||||
fi
|
||||
@@ -36,9 +36,22 @@ fi
|
||||
|
||||
# 3) Load env for RPC and nonce checks (no secrets printed)
|
||||
[[ -f "${PROJECT_ROOT}/config/ip-addresses.conf" ]] && source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
|
||||
set -a
|
||||
source "$SMOM/.env"
|
||||
set +a
|
||||
if [[ -f "$SMOM/scripts/lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SMOM/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$SMOM"
|
||||
else
|
||||
local_had_nounset=0
|
||||
if [[ $- == *u* ]]; then
|
||||
local_had_nounset=1
|
||||
set +u
|
||||
fi
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$SMOM/.env"
|
||||
set +a
|
||||
(( local_had_nounset )) && set -u
|
||||
fi
|
||||
|
||||
RPC="${RPC_URL_138:-http://192.168.11.211:8545}"
|
||||
if [[ -z "${PRIVATE_KEY:-}" ]]; then
|
||||
|
||||
136
scripts/deployment/rotate-oracle-publisher-transmitter.sh
Executable file
136
scripts/deployment/rotate-oracle-publisher-transmitter.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rotate the Chain 138 oracle publisher off the shared deployer key by provisioning
|
||||
# a dedicated transmitter key, adding it to the oracle aggregator, updating CT 3500,
|
||||
# and removing the legacy deployer transmitter after the new key confirms an update.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/deployment/rotate-oracle-publisher-transmitter.sh [--dry-run]
|
||||
#
|
||||
# Env overrides:
|
||||
# PROXMOX_NODE_IP default 192.168.11.12
|
||||
# ORACLE_VMID default 3500
|
||||
# ORACLE_SECRET_FILE default ~/.secure-secrets/chain138-oracle-publisher.env
|
||||
# ORACLE_AGGREGATOR_ADDRESS default 0x99b3511a2d315a497c8112c1fdd8d508d4b1e506
|
||||
# ORACLE_FUND_WEI default 100000000000000000 (0.1 native token)
|
||||
# NEW_ORACLE_PRIVATE_KEY optional pre-generated 0x-prefixed key
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
DRY_RUN=0
|
||||
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||
DRY_RUN=1
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
had_nounset=0
|
||||
if [[ $- == *u* ]]; then
|
||||
had_nounset=1
|
||||
set +u
|
||||
fi
|
||||
set -a
|
||||
source scripts/lib/load-project-env.sh >/dev/null 2>&1
|
||||
set +a
|
||||
if [[ "$had_nounset" -eq 1 ]]; then
|
||||
set -u
|
||||
fi
|
||||
|
||||
RPC="${DEPLOY_RPC_URL_138:-${RPC_URL_138:-http://192.168.11.211:8545}}"
|
||||
NODE_IP="${PROXMOX_NODE_IP:-192.168.11.12}"
|
||||
ORACLE_VMID="${ORACLE_VMID:-3500}"
|
||||
AGG="${ORACLE_AGGREGATOR_ADDRESS:-0x99b3511a2d315a497c8112c1fdd8d508d4b1e506}"
|
||||
SECRET_FILE="${ORACLE_SECRET_FILE:-$HOME/.secure-secrets/chain138-oracle-publisher.env}"
|
||||
DEPLOYER_ADDR="$(cast wallet address --private-key "$PRIVATE_KEY")"
|
||||
NEW_KEY="${NEW_ORACLE_PRIVATE_KEY:-0x$(openssl rand -hex 32)}"
|
||||
NEW_ADDR="$(cast wallet address --private-key "$NEW_KEY")"
|
||||
ORACLE_FUND_WEI="${ORACLE_FUND_WEI:-100000000000000000}"
|
||||
|
||||
echo "Oracle transmitter rotation"
|
||||
echo " node: $NODE_IP"
|
||||
echo " vmid: $ORACLE_VMID"
|
||||
echo " aggregator: $AGG"
|
||||
echo " admin: $DEPLOYER_ADDR"
|
||||
echo " new signer: $NEW_ADDR"
|
||||
echo " secret file: $SECRET_FILE"
|
||||
echo " fund wei: $ORACLE_FUND_WEI"
|
||||
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$SECRET_FILE")"
|
||||
umask 077
|
||||
cat >"$SECRET_FILE" <<EOF
|
||||
CHAIN138_ORACLE_PUBLISHER_PRIVATE_KEY=$NEW_KEY
|
||||
CHAIN138_ORACLE_PUBLISHER_ADDRESS=$NEW_ADDR
|
||||
CHAIN138_ORACLE_AGGREGATOR_ADDRESS=$AGG
|
||||
CHAIN138_ORACLE_ROTATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
EOF
|
||||
|
||||
if [[ "$(cast call "$AGG" 'isTransmitter(address)(bool)' "$NEW_ADDR" --rpc-url "$RPC")" != "true" ]]; then
|
||||
cast send "$AGG" 'addTransmitter(address)' "$NEW_ADDR" \
|
||||
--rpc-url "$RPC" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy \
|
||||
--gas-price 1000000000 \
|
||||
>/dev/null
|
||||
fi
|
||||
|
||||
new_balance="$(cast balance "$NEW_ADDR" --rpc-url "$RPC")"
|
||||
if [[ "$new_balance" -lt "$ORACLE_FUND_WEI" ]]; then
|
||||
cast send "$NEW_ADDR" \
|
||||
--value "$ORACLE_FUND_WEI" \
|
||||
--rpc-url "$RPC" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy \
|
||||
--gas-price 1000000000 \
|
||||
>/dev/null
|
||||
fi
|
||||
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no "root@$NODE_IP" "\
|
||||
pct exec $ORACLE_VMID -- bash -lc 'set -euo pipefail; \
|
||||
ENV=/opt/oracle-publisher/.env; \
|
||||
grep -q \"^PRIVATE_KEY=\" \$ENV && sed -i \"s|^PRIVATE_KEY=.*|PRIVATE_KEY=$NEW_KEY|\" \$ENV || echo \"PRIVATE_KEY=$NEW_KEY\" >> \$ENV; \
|
||||
systemctl restart oracle-publisher.service; \
|
||||
systemctl is-active oracle-publisher.service >/dev/null'"
|
||||
|
||||
echo "Waiting for new transmitter to confirm an oracle update..."
|
||||
tx_hash=""
|
||||
for _ in {1..24}; do
|
||||
line="$(ssh -o BatchMode=yes -o StrictHostKeyChecking=no "root@$NODE_IP" "pct exec $ORACLE_VMID -- journalctl -u oracle-publisher.service -n 20 --no-pager | grep 'Transaction confirmed:' | tail -n 1" || true)"
|
||||
if [[ -n "$line" ]]; then
|
||||
tx_hash="$(printf '%s' "$line" | grep -oE '0x[a-fA-F0-9]{64}' | tail -n 1 || true)"
|
||||
fi
|
||||
if [[ -n "$tx_hash" ]]; then
|
||||
tx_from="$(cast receipt "$tx_hash" --rpc-url "$RPC" | awk '/^from /{print $2}')"
|
||||
if [[ "${tx_from,,}" == "${NEW_ADDR,,}" ]]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$tx_hash" ]]; then
|
||||
echo "ERROR: No confirmed oracle update observed from the new transmitter." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tx_from="$(cast receipt "$tx_hash" --rpc-url "$RPC" | awk '/^from /{print $2}')"
|
||||
if [[ "${tx_from,,}" != "${NEW_ADDR,,}" ]]; then
|
||||
echo "ERROR: Latest confirmed oracle update was not sent by the new transmitter: $tx_from" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(cast call "$AGG" 'isTransmitter(address)(bool)' "$DEPLOYER_ADDR" --rpc-url "$RPC")" == "true" ]]; then
|
||||
cast send "$AGG" 'removeTransmitter(address)' "$DEPLOYER_ADDR" \
|
||||
--rpc-url "$RPC" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy \
|
||||
--gas-price 1000000000 \
|
||||
>/dev/null
|
||||
fi
|
||||
|
||||
echo "Rotation complete."
|
||||
echo " new transmitter: $NEW_ADDR"
|
||||
echo " confirmed tx: $tx_hash"
|
||||
echo " deployer signer removed from transmitter set."
|
||||
@@ -17,8 +17,32 @@
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
REPO_ROOT="$PROJECT_ROOT"
|
||||
SMOM="$PROJECT_ROOT/smom-dbis-138"
|
||||
|
||||
load_smom_env() {
|
||||
if [[ -f "$SMOM/scripts/lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SMOM/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$SMOM"
|
||||
return 0
|
||||
fi
|
||||
if [[ -f "$SMOM/.env" ]]; then
|
||||
local had_nounset=0
|
||||
if [[ $- == *u* ]]; then
|
||||
had_nounset=1
|
||||
set +u
|
||||
fi
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$SMOM/.env"
|
||||
set +a
|
||||
(( had_nounset )) && set -u
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
DRY_RUN=""
|
||||
SKIP_MIRROR=""
|
||||
SKIP_MESH=""
|
||||
@@ -82,8 +106,7 @@ if [[ -z "$SKIP_REGISTER_GRU" ]]; then
|
||||
if [[ -n "$DRY_RUN" ]]; then
|
||||
echo "[DRY-RUN] cd $SMOM && forge script script/deploy/RegisterGRUCompliantTokens.s.sol --rpc-url \$RPC_URL_138 --broadcast --private-key \$PRIVATE_KEY --with-gas-price 1000000000"
|
||||
else
|
||||
if [[ -f "$SMOM/.env" ]]; then
|
||||
set -a; source "$SMOM/.env"; set +a
|
||||
if load_smom_env; then
|
||||
# Fallback: Register script expects CUSDT_ADDRESS_138/CUSDC_ADDRESS_138; use COMPLIANT_USDT/COMPLIANT_USDC if set
|
||||
[[ -z "${CUSDT_ADDRESS_138:-}" && -n "${COMPLIANT_USDT:-}" ]] && export CUSDT_ADDRESS_138="$COMPLIANT_USDT"
|
||||
[[ -z "${CUSDC_ADDRESS_138:-}" && -n "${COMPLIANT_USDC:-}" ]] && export CUSDC_ADDRESS_138="$COMPLIANT_USDC"
|
||||
@@ -108,8 +131,8 @@ if [[ -z "$SKIP_VERIFY" ]]; then
|
||||
if [[ -n "$DRY_RUN" ]]; then
|
||||
echo "[DRY-RUN] $PROJECT_ROOT/scripts/verify/check-contracts-on-chain-138.sh"
|
||||
else
|
||||
[[ -f "$SMOM/.env" ]] && set -a && source "$SMOM/.env" && set +a
|
||||
"$PROJECT_ROOT/scripts/verify/check-contracts-on-chain-138.sh" "${RPC_URL_138:-}" || true
|
||||
load_smom_env || true
|
||||
"$REPO_ROOT/scripts/verify/check-contracts-on-chain-138.sh" "${RPC_URL_138:-}" || true
|
||||
fi
|
||||
echo ""
|
||||
else
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
# See docs/07-ccip/CW_DEPLOY_AND_WIRE_RUNBOOK.md and docs/00-meta/CW_BRIDGE_TASK_LIST.md.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/deployment/run-cw-remaining-steps.sh [--dry-run] [--deploy] [--update-mapping] [--verify]
|
||||
# ./scripts/deployment/run-cw-remaining-steps.sh [--dry-run] [--deploy] [--update-mapping] [--verify] [--verify-hard-peg]
|
||||
# --dry-run Run deploy-cw in dry-run mode (print commands only).
|
||||
# --deploy Run deploy-cw on all chains (requires RPC/PRIVATE_KEY in smom-dbis-138/.env).
|
||||
# --update-mapping Update config/token-mapping-multichain.json from CWUSDT_*/CWUSDC_* in .env.
|
||||
# --verify For each chain with CWUSDT_* set, check MINTER_ROLE/BURNER_ROLE on cW* for CW_BRIDGE_*.
|
||||
# --verify-hard-peg Check Avalanche hard-peg bridge controls for cWUSDT/cWUSDC.
|
||||
# With no options, runs --dry-run then --update-mapping (if any CWUSDT_* in .env).
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -18,6 +19,7 @@ DRY_RUN=false
|
||||
DO_DEPLOY=false
|
||||
DO_UPDATE_MAPPING=false
|
||||
DO_VERIFY=false
|
||||
DO_VERIFY_HARD_PEG=false
|
||||
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
@@ -25,9 +27,10 @@ for a in "$@"; do
|
||||
--deploy) DO_DEPLOY=true ;;
|
||||
--update-mapping) DO_UPDATE_MAPPING=true ;;
|
||||
--verify) DO_VERIFY=true ;;
|
||||
--verify-hard-peg) DO_VERIFY_HARD_PEG=true ;;
|
||||
esac
|
||||
done
|
||||
if ! $DRY_RUN && ! $DO_DEPLOY && ! $DO_UPDATE_MAPPING && ! $DO_VERIFY; then
|
||||
if ! $DRY_RUN && ! $DO_DEPLOY && ! $DO_UPDATE_MAPPING && ! $DO_VERIFY && ! $DO_VERIFY_HARD_PEG; then
|
||||
DRY_RUN=true
|
||||
DO_UPDATE_MAPPING=true
|
||||
fi
|
||||
@@ -37,7 +40,9 @@ if [[ ! -f "$SMOM/.env" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
set -a
|
||||
set +u
|
||||
source "$SMOM/.env"
|
||||
set -u
|
||||
set +a
|
||||
|
||||
# Chain name (env suffix) -> chainId for 138 -> chain pairs
|
||||
@@ -135,4 +140,57 @@ if $DO_VERIFY; then
|
||||
done
|
||||
fi
|
||||
|
||||
call_or_unavailable() {
|
||||
local rpc="$1"
|
||||
local address="$2"
|
||||
local signature="$3"
|
||||
shift 3
|
||||
if [[ -z "$rpc" || -z "$address" ]]; then
|
||||
printf 'unavailable\n'
|
||||
return
|
||||
fi
|
||||
cast call "$address" "$signature" "$@" --rpc-url "$rpc" 2>/dev/null || printf 'legacy-or-unavailable\n'
|
||||
}
|
||||
|
||||
if $DO_VERIFY_HARD_PEG; then
|
||||
echo "=== Verify Avalanche hard-peg bridge state ==="
|
||||
|
||||
CHAIN138_L1_BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}"
|
||||
AVAX_CW_BRIDGE="${CW_BRIDGE_AVALANCHE:-}"
|
||||
RESERVE_VERIFIER="${CW_RESERVE_VERIFIER_CHAIN138:-}"
|
||||
AVALANCHE_SELECTOR_VALUE="${AVALANCHE_SELECTOR:-6433500567565415381}"
|
||||
|
||||
CW_CANONICAL_USDT_ADDR="${CW_CANONICAL_USDT:-${COMPLIANT_USDT_ADDRESS:-${CUSDT_ADDRESS_138:-}}}"
|
||||
CW_CANONICAL_USDC_ADDR="${CW_CANONICAL_USDC:-${COMPLIANT_USDC_ADDRESS:-${CUSDC_ADDRESS_138:-}}}"
|
||||
|
||||
echo " Chain 138 L1 bridge: $CHAIN138_L1_BRIDGE"
|
||||
echo " Avalanche cW bridge: $AVAX_CW_BRIDGE"
|
||||
echo " Reserve verifier: ${RESERVE_VERIFIER:-unconfigured}"
|
||||
echo " Avalanche selector: $AVALANCHE_SELECTOR_VALUE"
|
||||
|
||||
if [[ -n "$CHAIN138_L1_BRIDGE" ]]; then
|
||||
echo " L1 bridge reserveVerifier(): $(call_or_unavailable "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "reserveVerifier()(address)")"
|
||||
fi
|
||||
|
||||
for entry in "cUSDT:$CW_CANONICAL_USDT_ADDR:$CW_MAX_OUTSTANDING_USDT_AVALANCHE" "cUSDC:$CW_CANONICAL_USDC_ADDR:$CW_MAX_OUTSTANDING_USDC_AVALANCHE"; do
|
||||
IFS=":" read -r label token desired_cap <<<"$entry"
|
||||
if [[ -z "$token" ]]; then
|
||||
echo " $label: canonical token not set"
|
||||
continue
|
||||
fi
|
||||
echo " $label supportedCanonicalToken(): $(call_or_unavailable "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "supportedCanonicalToken(address)(bool)" "$token")"
|
||||
echo " $label maxOutstanding(): $(call_or_unavailable "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "maxOutstanding(address,uint64)(uint256)" "$token" "$AVALANCHE_SELECTOR_VALUE")"
|
||||
if [[ -n "$desired_cap" ]]; then
|
||||
echo " $label desired maxOutstanding env: $desired_cap"
|
||||
fi
|
||||
echo " $label tokenPairFrozen(): $(call_or_unavailable "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "tokenPairFrozen(address)(bool)" "$token")"
|
||||
if [[ -n "$RESERVE_VERIFIER" ]]; then
|
||||
echo " $label verifier tokenConfigs(): $(call_or_unavailable "$RPC_URL_138" "$RESERVE_VERIFIER" "tokenConfigs(address)(bool,address,bool,bool,bool)" "$token" | tr '\n' ' ' | xargs)"
|
||||
echo " $label verifier getVerificationStatus(): $(call_or_unavailable "$RPC_URL_138" "$RESERVE_VERIFIER" "getVerificationStatus(address,uint64)((bool,bool,bool,bool,bool,bool,bool,bool,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256))" "$token" "$AVALANCHE_SELECTOR_VALUE" | tr '\n' ' ' | xargs)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo " Avalanche destinationFrozen(138): $(call_or_unavailable "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "destinationFrozen(uint64)(bool)" 138)"
|
||||
fi
|
||||
|
||||
echo "Done. See docs/07-ccip/CW_DEPLOY_AND_WIRE_RUNBOOK.md for Phase E (relay and E2E)."
|
||||
|
||||
102
scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh
Executable file
102
scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# Merge Keycloak OIDC settings from the operator repo .env into LXC 7801
|
||||
# (/opt/sankofa-portal/.env and .env.local). Uses base64 over ssh for the client secret
|
||||
# so special characters in KEYCLOAK_CLIENT_SECRET do not break the remote shell.
|
||||
#
|
||||
# Requires KEYCLOAK_CLIENT_SECRET (and loads repo .env via load-project-env when sourced
|
||||
# from repo root, or export vars before calling).
|
||||
#
|
||||
# Run after creating the confidential client (e.g. keycloak-sankofa-ensure-client-redirects*.sh)
|
||||
# or when rotating KEYCLOAK_CLIENT_SECRET.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh
|
||||
# ./scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh --dry-run
|
||||
# ./scripts/deployment/sankofa-portal-merge-keycloak-env-from-repo.sh --no-restart
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" 2>/dev/null || true
|
||||
# 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}}"
|
||||
VMID="${SANKOFA_PORTAL_VMID:-7801}"
|
||||
CT_DIR="${SANKOFA_PORTAL_CT_DIR:-/opt/sankofa-portal}"
|
||||
SERVICE_NAME="${SANKOFA_PORTAL_SERVICE:-sankofa-portal}"
|
||||
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new)
|
||||
|
||||
KC_URL="${KEYCLOAK_URL:-https://keycloak.sankofa.nexus}"
|
||||
KC_REALM="${KEYCLOAK_REALM:-master}"
|
||||
KC_CID="${KEYCLOAK_CLIENT_ID:-sankofa-portal}"
|
||||
|
||||
DRY_RUN=false
|
||||
NO_RESTART=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--no-restart) NO_RESTART=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${KEYCLOAK_CLIENT_SECRET:-}" ]]; then
|
||||
echo "ERROR: KEYCLOAK_CLIENT_SECRET is not set. Add it to repo .env (from Keycloak admin or" >&2
|
||||
echo " keycloak-sankofa-ensure-client-redirects-via-proxmox-pct.sh when the client is created)," >&2
|
||||
echo " then: source scripts/lib/load-project-env.sh && $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if $DRY_RUN; then
|
||||
echo "[DRY-RUN] Would upsert KEYCLOAK_* on CT ${VMID} ${CT_DIR}/.env and .env.local"
|
||||
echo "[DRY-RUN] KEYCLOAK_URL=${KC_URL} KEYCLOAK_REALM=${KC_REALM} KEYCLOAK_CLIENT_ID=${KC_CID}"
|
||||
echo "[DRY-RUN] restart: $([[ "$NO_RESTART" == true ]] && echo no || echo yes)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
B64="$(printf '%s' "$KEYCLOAK_CLIENT_SECRET" | base64 -w0)"
|
||||
|
||||
ssh "${SSH_OPTS[@]}" "root@${PROXMOX_HOST}" \
|
||||
"pct exec ${VMID} -- env KCSEC_B64='${B64}' KC_URL='${KC_URL}' KC_REALM='${KC_REALM}' KC_CID='${KC_CID}' CT_DIR='${CT_DIR}' python3 -" <<'PY'
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
sec = base64.b64decode(os.environ["KCSEC_B64"]).decode("utf-8")
|
||||
ct = Path(os.environ["CT_DIR"])
|
||||
keys = {
|
||||
"KEYCLOAK_URL": os.environ["KC_URL"],
|
||||
"KEYCLOAK_REALM": os.environ["KC_REALM"],
|
||||
"KEYCLOAK_CLIENT_ID": os.environ["KC_CID"],
|
||||
"KEYCLOAK_CLIENT_SECRET": sec,
|
||||
}
|
||||
|
||||
|
||||
def upsert(text: str, k: str, v: str) -> str:
|
||||
line = f"{k}={v}"
|
||||
if re.search(rf"^{re.escape(k)}=", text, flags=re.M):
|
||||
return re.sub(rf"^{re.escape(k)}=.*$", line, text, flags=re.M, count=1)
|
||||
if text and not text.endswith("\n"):
|
||||
text += "\n"
|
||||
return text + line + "\n"
|
||||
|
||||
|
||||
for fname in (".env", ".env.local"):
|
||||
p = ct / fname
|
||||
body = p.read_text() if p.exists() else ""
|
||||
for k, v in keys.items():
|
||||
body = upsert(body, k, v)
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(body)
|
||||
print(f"upserted Keycloak keys in {p}")
|
||||
PY
|
||||
|
||||
if [[ "$NO_RESTART" == true ]]; then
|
||||
echo "[ok] Keycloak OIDC vars merged on CT ${VMID} (no service restart)."
|
||||
else
|
||||
ssh "${SSH_OPTS[@]}" "root@${PROXMOX_HOST}" \
|
||||
"pct exec ${VMID} -- systemctl restart ${SERVICE_NAME} && pct exec ${VMID} -- systemctl is-active ${SERVICE_NAME}"
|
||||
echo "[ok] Keycloak OIDC vars merged on CT ${VMID}; ${SERVICE_NAME} restarted."
|
||||
fi
|
||||
@@ -3,8 +3,9 @@
|
||||
# then run RegisterGRUCompliantTokens to register all c* as GRU in UniversalAssetRegistry.
|
||||
#
|
||||
# Addresses are from docs/11-references/CONTRACT_ADDRESSES_REFERENCE.md and TOKENS_AND_NETWORKS_MINTABLE_TO_DEPLOYER.md.
|
||||
# Usage: ./scripts/deployment/set-dotenv-c-tokens-and-register-gru.sh [--no-register]
|
||||
# Usage: ./scripts/deployment/set-dotenv-c-tokens-and-register-gru.sh [--no-register] [--register-v2]
|
||||
# --no-register Only update .env; do not run RegisterGRUCompliantTokens.
|
||||
# --register-v2 After V1 registration, also register staged V2 cUSDT/cUSDC using RegisterGRUCompliantTokensV2.
|
||||
#
|
||||
# Note: RegisterGRUCompliantTokens requires (1) broadcast account has REGISTRAR_ROLE, and (2) the
|
||||
# UniversalAssetRegistry *implementation* (not just proxy) exposes registerGRUCompliantAsset.
|
||||
@@ -21,7 +22,11 @@ SMOM="$PROJECT_ROOT/smom-dbis-138"
|
||||
ENV_FILE="$SMOM/.env"
|
||||
|
||||
RUN_REGISTER=1
|
||||
for a in "$@"; do [[ "$a" == "--no-register" ]] && RUN_REGISTER=0; done
|
||||
RUN_REGISTER_V2=0
|
||||
for a in "$@"; do
|
||||
[[ "$a" == "--no-register" ]] && RUN_REGISTER=0
|
||||
[[ "$a" == "--register-v2" ]] && RUN_REGISTER_V2=1
|
||||
done
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "Missing $ENV_FILE. Create it first (e.g. copy from .env.example)." >&2
|
||||
@@ -45,6 +50,10 @@ set_env_var "COMPLIANT_USDT" "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22"
|
||||
set_env_var "COMPLIANT_USDC" "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
|
||||
set_env_var "CUSDT_ADDRESS_138" "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22"
|
||||
set_env_var "CUSDC_ADDRESS_138" "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
|
||||
set_env_var "COMPLIANT_USDT_V2" "0x8d342d321DdEe97D0c5011DAF8ca0B59DA617D29"
|
||||
set_env_var "COMPLIANT_USDC_V2" "0x1ac3F4942a71E86A9682D91837E1E71b7BACdF99"
|
||||
set_env_var "CUSDT_V2_ADDRESS_138" "0x8d342d321DdEe97D0c5011DAF8ca0B59DA617D29"
|
||||
set_env_var "CUSDC_V2_ADDRESS_138" "0x1ac3F4942a71E86A9682D91837E1E71b7BACdF99"
|
||||
# cEURC (TOKENS_AND_NETWORKS_MINTABLE_TO_DEPLOYER)
|
||||
set_env_var "CEURC_ADDRESS_138" "0x8085961F9cF02b4d800A3c6d386D31da4B34266a"
|
||||
|
||||
@@ -68,7 +77,7 @@ set_env_var "WETH10" "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
|
||||
set_env_var "LINK_TOKEN" "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
|
||||
set_env_var "CCIP_FEE_TOKEN" "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
|
||||
|
||||
echo "Done. Set: COMPLIANT_USDT, COMPLIANT_USDC, all C*_ADDRESS_138 (cUSDT, cUSDC, cEURC, cEURT, cGBPC, cGBPT, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT), UNIVERSAL_ASSET_REGISTRY, WETH9, WETH10, LINK_TOKEN, CCIP_FEE_TOKEN."
|
||||
echo "Done. Set: COMPLIANT_USDT, COMPLIANT_USDC, COMPLIANT_USDT_V2, COMPLIANT_USDC_V2, all C*_ADDRESS_138 (including staged V2 addresses), UNIVERSAL_ASSET_REGISTRY, WETH9, WETH10, LINK_TOKEN, CCIP_FEE_TOKEN."
|
||||
echo "All c* on explorer.d-bis.org/tokens must be GRU-registered. See docs/04-configuration/EXPLORER_TOKENS_GRU_POLICY.md."
|
||||
|
||||
if [[ "$RUN_REGISTER" -eq 0 ]]; then
|
||||
@@ -85,4 +94,15 @@ RPC="${RPC_URL_138:-http://192.168.11.211:8545}"
|
||||
export RPC_URL_138="$RPC"
|
||||
(cd "$SMOM" && forge script script/deploy/RegisterGRUCompliantTokens.s.sol \
|
||||
--rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000)
|
||||
|
||||
if [[ "$RUN_REGISTER_V2" -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "=== Registering staged c* V2 inventory as GRU (RegisterGRUCompliantTokensV2) ==="
|
||||
(cd "$SMOM" && forge script script/deploy/RegisterGRUCompliantTokensV2.s.sol \
|
||||
--rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price 1000000000)
|
||||
else
|
||||
echo ""
|
||||
echo "V2 note: COMPLIANT_USDT_V2 / COMPLIANT_USDC_V2 are staged in .env but not auto-registered."
|
||||
echo "Use --register-v2 once downstream registry consumers are ready for version-aware symbols."
|
||||
fi
|
||||
echo "=== Done. ==="
|
||||
|
||||
Reference in New Issue
Block a user