Add Sankofa consolidated hub operator tooling
This commit is contained in:
54
scripts/verify/check-sankofa-consolidated-nginx-examples.sh
Executable file
54
scripts/verify/check-sankofa-consolidated-nginx-examples.sh
Executable 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."
|
||||
33
scripts/verify/smoke-phoenix-api-hub-lan.sh
Executable file
33
scripts/verify/smoke-phoenix-api-hub-lan.sh
Executable 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."
|
||||
68
scripts/verify/smoke-phoenix-graphql-ws-subscription.mjs
Normal file
68
scripts/verify/smoke-phoenix-graphql-ws-subscription.mjs
Normal 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);
|
||||
}
|
||||
});
|
||||
66
scripts/verify/smoke-phoenix-graphql-wss-public.sh
Executable file
66
scripts/verify/smoke-phoenix-graphql-wss-public.sh
Executable 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"
|
||||
80
scripts/verify/verify-sankofa-consolidated-hub-lan.sh
Executable file
80
scripts/verify/verify-sankofa-consolidated-hub-lan.sh
Executable 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
|
||||
Reference in New Issue
Block a user