Add Sankofa consolidated hub operator tooling

This commit is contained in:
defiQUG
2026-04-13 21:41:14 -07:00
parent 49740f1a59
commit b7eebb87b3
42 changed files with 2635 additions and 14 deletions

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Validate example nginx configs for Sankofa consolidated web/API hub (syntax only).
# Read-only; no mutations. Uses host `nginx -t` when available, else Docker `nginx:1.27-alpine`.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
NGINX_DIR="${PROJECT_ROOT}/config/nginx"
TMPDIR="${TMPDIR:-/tmp}"
WRAP="${TMPDIR}/sankofa-nginx-test-$$.conf"
cleanup() {
rm -f "$WRAP" "${TMPDIR}/sankofa-nginx-wrap-docker-"*.conf 2>/dev/null || true
rm -f "${TMPDIR}/sankofa-nginx-test-$$"/*.conf 2>/dev/null || true
rmdir "${TMPDIR}/sankofa-nginx-test-$$" 2>/dev/null || true
}
trap cleanup EXIT
mkdir -p "${TMPDIR}/sankofa-nginx-test-$$"
cp "${NGINX_DIR}/sankofa-non-chain-frontends.example.conf" "${TMPDIR}/sankofa-nginx-test-$$/01-web.conf"
cp "${NGINX_DIR}/sankofa-phoenix-api-hub.example.conf" "${TMPDIR}/sankofa-nginx-test-$$/02-api.conf"
cat >"$WRAP" <<'EOF'
events { worker_connections 1024; }
http {
include __INCLUDE_DIR__/*.conf;
}
EOF
sed -i "s|__INCLUDE_DIR__|${TMPDIR}/sankofa-nginx-test-$$|g" "$WRAP"
if command -v nginx >/dev/null 2>&1; then
echo "== nginx -t (host binary, wrapped includes) =="
nginx -t -c "$WRAP"
elif command -v docker >/dev/null 2>&1; then
DOCKER_WRAP="${TMPDIR}/sankofa-nginx-wrap-docker-$$.conf"
cat >"$DOCKER_WRAP" <<'INNER'
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /tmp/inc/*.conf;
}
INNER
echo "== nginx -t (docker nginx:1.27-alpine) =="
docker run --rm \
-v "$DOCKER_WRAP:/etc/nginx/nginx.conf:ro" \
-v "${TMPDIR}/sankofa-nginx-test-$$:/tmp/inc:ro" \
nginx:1.27-alpine \
nginx -t
else
echo "SKIP: need host nginx or docker to run syntax check."
exit 0
fi
echo "OK: example configs parse."

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Read-only LAN smoke: Tier-1 Phoenix API hub (:8080) — health + GraphQL + proxied api-docs.
# Usage: bash scripts/verify/smoke-phoenix-api-hub-lan.sh
# Env: IP_SANKOFA_PHOENIX_API, SANKOFA_API_HUB_PORT (default 8080) from load-project-env / ip-addresses.
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"
HUB_IP="${IP_SANKOFA_PHOENIX_API:-192.168.11.50}"
# Tier-1 nginx hub listens on :8080 by default (not SANKOFA_PHOENIX_API_HUB_PORT, which often tracks Apollo :4000).
HUB_PORT="${SANKOFA_API_HUB_LISTEN_PORT:-8080}"
BASE="http://${HUB_IP}:${HUB_PORT}"
echo "=== smoke-phoenix-api-hub-lan ==="
echo "Base: ${BASE}"
echo ""
curl -fsS -m 8 "${BASE}/health" | head -c 200
echo ""
echo "--- GraphQL POST /graphql"
curl -fsS -m 12 "${BASE}/graphql" \
-H 'Content-Type: application/json' \
-d '{"query":"query { __typename }"}' | head -c 300
echo ""
echo "--- GET /api-docs (optional)"
_ad="/tmp/hub-api-docs-$$"
code="$(curl -sS -m 12 -o "$_ad" -w "%{http_code}" "${BASE}/api-docs" || echo 000)"
if [[ "$code" == "200" ]]; then head -c 120 "$_ad"; echo ""; else echo "HTTP ${code} (hub still OK if GraphQL passed)"; fi
rm -f "$_ad"
echo ""
echo "OK: hub smoke passed."

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env node
/**
* Optional: full graphql-ws handshake — connection_init → connection_ack over wss://
* Server must expose a single clean upgrade path (standalone `ws` + graphql-ws; remove unused
* `@fastify/websocket` on CT 7800 if clients see RSV1 — see ensure-sankofa-phoenix-graphql-ws-remove-fastify-websocket-7800.sh).
*
* Usage:
* node scripts/verify/smoke-phoenix-graphql-ws-subscription.mjs
* PHOENIX_GRAPHQL_WSS_URL=wss://host/graphql-ws node scripts/verify/smoke-phoenix-graphql-ws-subscription.mjs
*/
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const require = createRequire(import.meta.url);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
const WebSocket = require(path.join(repoRoot, 'node_modules', 'ws'));
const url = process.env.PHOENIX_GRAPHQL_WSS_URL || 'wss://phoenix.sankofa.nexus/graphql-ws';
const timeoutMs = Number(process.env.PHOENIX_WS_SUB_TIMEOUT_MS || 15000);
const ws = new WebSocket(url, ['graphql-transport-ws'], { perMessageDeflate: false });
const timer = setTimeout(() => {
console.error('TIMEOUT waiting for connection_ack');
ws.terminate();
process.exit(1);
}, timeoutMs);
ws.on('open', () => {
ws.send(JSON.stringify({ type: 'connection_init' }));
});
ws.on('message', (data) => {
const text = String(data);
let msg;
try {
msg = JSON.parse(text);
} catch {
console.error('Non-JSON message:', text.slice(0, 200));
return;
}
if (msg.type === 'connection_ack') {
clearTimeout(timer);
console.log('OK: graphql-ws connection_ack');
ws.close(1000, 'smoke-ok');
process.exit(0);
}
if (msg.type === 'ping') {
ws.send(JSON.stringify({ type: 'pong' }));
return;
}
console.log('msg:', msg.type, JSON.stringify(msg).slice(0, 200));
});
ws.on('error', (err) => {
clearTimeout(timer);
console.error('WebSocket error:', err.message);
process.exit(2);
});
ws.on('close', (code, reason) => {
clearTimeout(timer);
if (code !== 1000) {
console.error('Closed:', code, String(reason));
process.exit(3);
}
});

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Smoke: WebSocket upgrade to Phoenix GraphQL WS (graphql-transport-ws) end-to-end through TLS.
# Uses curl --http1.1 (HTTP/2 cannot complete WS upgrade on many edges). Expects HTTP 101.
# Each successful probe waits up to PHOENIX_WSS_CURL_MAXTIME seconds (default 8): curl has no EOF on WS.
#
# Usage:
# bash scripts/verify/smoke-phoenix-graphql-wss-public.sh
# PHOENIX_GRAPHQL_WSS_URL=wss://phoenix.example/graphql-ws bash scripts/verify/smoke-phoenix-graphql-wss-public.sh
# Optional LAN hub (no TLS):
# PHOENIX_GRAPHQL_WS_LAN=http://192.168.11.50:8080/graphql-ws PHOENIX_WS_HOST_HEADER=phoenix.sankofa.nexus \
# bash scripts/verify/smoke-phoenix-graphql-wss-public.sh
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
PUBLIC_WSS="${PHOENIX_GRAPHQL_WSS_URL:-https://phoenix.sankofa.nexus/graphql-ws}"
LAN_WS="${PHOENIX_GRAPHQL_WS_LAN:-}"
LAN_HOST="${PHOENIX_WS_HOST_HEADER:-phoenix.sankofa.nexus}"
# After HTTP 101, curl waits for the WebSocket stream until --max-time (no clean EOF). Keep this
# modest so LAN+public probes do not sit for minutes (override with PHOENIX_WSS_CURL_MAXTIME if needed).
CURL_MAXTIME="${PHOENIX_WSS_CURL_MAXTIME:-8}"
# Opt-in LAN hub probe (same CT, HTTP): PHOENIX_WSS_INCLUDE_LAN=1 with load-project-env / ip-addresses
if [[ "${PHOENIX_WSS_INCLUDE_LAN:-0}" == "1" && -z "$LAN_WS" && -n "${IP_SANKOFA_PHOENIX_API:-}" ]]; then
LAN_WS="http://${IP_SANKOFA_PHOENIX_API}:8080/graphql-ws"
fi
probe_upgrade() {
local name="$1"
local url="$2"
shift 2
echo "--- ${name}"
echo " URL: ${url}"
local first
first="$(curl --http1.1 -sS --connect-timeout 10 -m "${CURL_MAXTIME}" -D- -o /dev/null \
"$@" \
-H 'Connection: Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
-H 'Sec-WebSocket-Protocol: graphql-transport-ws' \
"$url" 2>&1 | head -1 | tr -d '\r')"
if [[ "$first" == *"101"* ]]; then
echo " OK (${first})"
return 0
fi
echo " FAIL (expected HTTP/1.1 101; first line: ${first})"
return 1
}
echo "=== smoke-phoenix-graphql-wss-public (curl WS upgrade) ==="
fail=0
probe_upgrade "Public WSS (NPM → hub → Apollo)" "$PUBLIC_WSS" || fail=1
if [[ -n "$LAN_WS" ]]; then
probe_upgrade "LAN hub (optional)" "$LAN_WS" -H "Host: ${LAN_HOST}" || fail=1
fi
if [[ "$fail" -ne 0 ]]; then
echo ""
echo "RESULT: one or more upgrade probes failed."
exit 1
fi
echo ""
echo "RESULT: WebSocket upgrade path OK (HTTP 101). Full handshake: pnpm run verify:phoenix-graphql-ws-subscription"

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Read-only LAN checks for Sankofa Phoenix + dbis_core + optional Keycloak / corporate web.
# Exit 0 if all probes that are attempted succeed; exit 1 if any required probe fails.
# Phoenix: prefers Tier-1 hub :8080 /health when hub is up (Apollo may be 127.0.0.1:4000 only).
# Optional: SANKOFA_VERIFY_PHOENIX_DIRECT_PORT=1 to probe direct :4000 (fails when loopback-bound).
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"
fail=0
probe() {
local name="$1" url="$2"
echo "--- ${name}: ${url}"
if curl -fsS -m 8 -o /dev/null -w " HTTP %{http_code}\n" "$url"; then
return 0
fi
echo " FAIL"
fail=1
}
echo "=== Sankofa / Phoenix / DBIS LAN readiness (read-only) ==="
echo ""
# Prefer Tier-1 hub :8080 when present (Apollo may bind loopback :4000 only).
PH_HUB_HEALTH="http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_API_HUB_LISTEN_PORT:-8080}/health"
PH_DIRECT_HEALTH="http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_PHOENIX_API_PORT}/health"
if curl -fsS -m 6 "$PH_HUB_HEALTH" -o /dev/null 2>/dev/null; then
probe "Phoenix API hub /health" "$PH_HUB_HEALTH"
else
probe "Phoenix API /health (direct Apollo)" "$PH_DIRECT_HEALTH"
fi
if [[ "${SANKOFA_VERIFY_PHOENIX_DIRECT_PORT:-0}" == "1" ]]; then
probe "Phoenix API /health (direct :${SANKOFA_PHOENIX_API_PORT}, optional)" "$PH_DIRECT_HEALTH" || true
fi
probe "Portal /" "http://${IP_SANKOFA_PORTAL}:${SANKOFA_PORTAL_PORT}/"
if [[ -n "${IP_SANKOFA_PUBLIC_WEB:-}" ]]; then
probe "Corporate web /" "http://${IP_SANKOFA_PUBLIC_WEB}:${SANKOFA_PUBLIC_WEB_PORT:-3000}/" || true
fi
if [[ -n "${IP_DBIS_API:-}" ]]; then
probe "dbis_core /health" "http://${IP_DBIS_API}:3000/health" || true
fi
# Keycloak (7802 typical) — optional
if [[ -n "${IP_KEYCLOAK:-}" ]] || [[ -n "${KEYCLOAK_URL:-}" ]]; then
_kc="${IP_KEYCLOAK:-}"
if [[ -z "$_kc" && "${KEYCLOAK_URL:-}" =~ http://([^:/]+) ]]; then
_kc="${BASH_REMATCH[1]}"
fi
if [[ -n "$_kc" ]]; then
echo "--- Keycloak (optional): realm metadata"
curl -fsS -m 8 -o /dev/null -w " HTTP %{http_code} http://${_kc}:8080/realms/master\n" "http://${_kc}:8080/realms/master" \
|| echo " SKIP (unreachable or wrong path)"
fi
fi
echo ""
echo "Resolved hub env (for NPM / nginx cutover):"
echo " IP_SANKOFA_WEB_HUB=${IP_SANKOFA_WEB_HUB:-} port ${SANKOFA_WEB_HUB_PORT:-}"
echo " IP_SANKOFA_PHOENIX_API_HUB=${IP_SANKOFA_PHOENIX_API_HUB:-} port ${SANKOFA_PHOENIX_API_HUB_PORT:-}"
echo ""
echo "--- Phoenix Tier-1 API hub (informational, :8080)"
HUB_LAN="http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_API_HUB_LISTEN_PORT:-8080}"
if curl -fsS -m 6 "${HUB_LAN}/health" -o /dev/null 2>/dev/null; then
echo " OK ${HUB_LAN}/health (also covered by required probe above when hub is primary)"
else
echo " SKIP (no hub on ${HUB_LAN} — install: install-sankofa-api-hub-nginx-on-pve.sh)"
fi
echo ""
if [[ "$fail" -ne 0 ]]; then
echo "RESULT: one or more probes failed."
exit 1
fi
echo "RESULT: required probes OK."
exit 0