From 3e7c9b99414c9c398afc5ec81735cf3c749f6fda Mon Sep 17 00:00:00 2001 From: defiQUG Date: Thu, 9 Apr 2026 02:01:50 -0700 Subject: [PATCH] fix(npm): IT API TLS helper + treat certificate_id string 0 as missing - jq select includes certificate_id == "0" for NPM JSON quirks - request-it-api-tls-npm.sh wraps CERT_DOMAINS_FILTER for it-api.sankofa.nexus - Docs: TLS command, Cloudflare redirect-loop note; spec remaining items Made-with: Cursor --- AGENTS.md | 2 +- .../SANKOFA_IT_OPERATIONS_CONTROLLER_SPEC.md | 2 +- .../SANKOFA_IT_OPS_KEYCLOAK_PORTAL_NEXT_STEPS.md | 2 +- scripts/deployment/request-it-api-tls-npm.sh | 9 +++++++++ scripts/request-npmplus-certificates.sh | 12 ++++++------ 5 files changed, 18 insertions(+), 9 deletions(-) create mode 100755 scripts/deployment/request-it-api-tls-npm.sh diff --git a/AGENTS.md b/AGENTS.md index 055e7ac..984c66f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,7 @@ Orchestration for Proxmox VE, Chain 138 (`smom-dbis-138/`), explorers, NPMplus, | The Order portal (`https://the-order.sankofa.nexus`) | OSJ management UI (secure auth); source repo **the_order** at `~/projects/the_order`. NPM upstream defaults to **order-haproxy** CT **10210** (`IP_ORDER_HAPROXY:80`); use `THE_ORDER_UPSTREAM_*` to point at the Sankofa portal if 10210 is down. Provision HAProxy: `scripts/deployment/provision-order-haproxy-10210.sh`. **`www.the-order.sankofa.nexus`** → **301** apex (same as www.sankofa / www.phoenix). | | Portal login + Keycloak systemd + `.env` (prints password once) | `./scripts/deployment/enable-sankofa-portal-login-7801.sh` (`--dry-run` first); preserves `KEYCLOAK_*` from repo `.env` and runs merge script when `KEYCLOAK_CLIENT_SECRET` is set | | Keycloak redirect URIs (portal + admin) | `./scripts/deployment/keycloak-sankofa-ensure-client-redirects-via-proxmox-pct.sh` (or `keycloak-sankofa-ensure-client-redirects.sh` for LAN URL) — needs `KEYCLOAK_ADMIN_PASSWORD` in `.env` | -| NPM TLS for hosts missing certs | `./scripts/request-npmplus-certificates.sh` — optional `CERT_DOMAINS_FILTER='portal\\.sankofa|admin\\.sankofa'` | +| NPM TLS for hosts missing certs | `./scripts/request-npmplus-certificates.sh` — optional `CERT_DOMAINS_FILTER='portal\\.sankofa|admin\\.sankofa'`; IT API: `./scripts/deployment/request-it-api-tls-npm.sh` (same as filter `it-api\\.sankofa\\.nexus`) | | Token-aggregation API (Chain 138) | `pnpm run verify:token-aggregation-api` — tokens, pools, quote (prints `quoteEngine` when `jq` installed), `bridge/routes`, networks. Build + env: `scripts/deploy-token-aggregation-for-publication.sh` (sets `RPC_URL_138`, `TOKEN_AGGREGATION_CHAIN138_RPC_URL`, optional `TOKEN_AGGREGATION_PMM_*`). LAN push + restart: `scripts/deployment/push-token-aggregation-bundle-to-explorer.sh`. Nginx gaps: `scripts/fix-explorer-http-api-v1-proxy.sh` (apex `/api/v1/`), `scripts/fix-explorer-token-aggregation-api-v2-proxy.sh` (planner POST). Runbook: `docs/04-configuration/TOKEN_AGGREGATION_REPORT_API_RUNBOOK.md`. | | **Chain 138 Open Snap** (MetaMask, open Snap permissions only; stable MetaMask requires MetaMask install allowlist for npm Snaps) | Source repo: [Defi-Oracle-Tooling/chain138-snap-minimal](https://github.com/Defi-Oracle-Tooling/chain138-snap-minimal). Vendored in this workspace: `metamask-integration/chain138-snap-minimal/`. Snap ID `npm:chain138-open-snap`; **`npm run verify`** = `npm audit --omit=dev` + build. **Publish:** token in `chain138-snap/.env` or `npm login`, then `./scripts/deployment/publish-chain138-open-snap.sh`. **Full-feature Snap** (API quotes, allowlist): `metamask-integration/chain138-snap/`. Explorer `/wallet` install works on stable MetaMask only after allowlisting; use Flask or local serve for dev. | | Completable (no LAN) | `./scripts/run-completable-tasks-from-anywhere.sh` | diff --git a/docs/02-architecture/SANKOFA_IT_OPERATIONS_CONTROLLER_SPEC.md b/docs/02-architecture/SANKOFA_IT_OPERATIONS_CONTROLLER_SPEC.md index e10e4d5..4820a4f 100644 --- a/docs/02-architecture/SANKOFA_IT_OPERATIONS_CONTROLLER_SPEC.md +++ b/docs/02-architecture/SANKOFA_IT_OPERATIONS_CONTROLLER_SPEC.md @@ -171,7 +171,7 @@ The HTML controller should show a **joined view**: *public hostname → NPM → 1. **Full BFF** with OIDC (Keycloak) and Postgres — **`dbis_core` vs dedicated CT** — decide once. 2. **Keycloak** — assign **`sankofa-it-admin`** to real IT users (role creation is scripted; mapping is manual policy). -3. **TLS for `it-api.sankofa.nexus`** — NPM certificate after DNS propagation; duplicate guest IP remediation (export exit **2**) on the cluster. +3. **TLS for `it-api.sankofa.nexus`** — `scripts/deployment/request-it-api-tls-npm.sh` (or `CERT_DOMAINS_FILTER='it-api\.sankofa\.nexus'` + `request-npmplus-certificates.sh`). If public HTTPS redirect-loops, align Cloudflare proxy/SSL mode with NPM. **Duplicate guest IPs** (export exit **2**) — remediate on cluster. 4. **UniFi / NPM** live collectors — Phase 2 of this spec. This spec does **not** replace change control; it gives you a **single product vision** so IP, VLAN, ports, hosts, licenses, and billing support evolve together instead of in silos. diff --git a/docs/03-deployment/SANKOFA_IT_OPS_KEYCLOAK_PORTAL_NEXT_STEPS.md b/docs/03-deployment/SANKOFA_IT_OPS_KEYCLOAK_PORTAL_NEXT_STEPS.md index 97cd0ab..9306e52 100644 --- a/docs/03-deployment/SANKOFA_IT_OPS_KEYCLOAK_PORTAL_NEXT_STEPS.md +++ b/docs/03-deployment/SANKOFA_IT_OPS_KEYCLOAK_PORTAL_NEXT_STEPS.md @@ -11,7 +11,7 @@ One command after `.env` has `NPM_PASSWORD`, Cloudflare vars (for DNS), and SSH 1. **`bash scripts/deployment/bootstrap-sankofa-it-read-api-lan.sh`** — Refreshes inventory JSON, rsyncs a minimal tree to **`/opt/proxmox`** on **`PROXMOX_HOST`** (default r630-01), installs **`sankofa-it-read-api`** (bind **`0.0.0.0:8787`**, secrets in **`/etc/sankofa-it-read-api.env`**), upserts **`IT_READ_API_URL`** / **`IT_READ_API_KEY`** in repo **`.env`**, enables weekly **`sankofa-it-inventory-export.timer`** on the same host, runs **`sankofa-portal-merge-it-read-api-env-from-repo.sh`** for CT **7801**. Export exit code **2** (duplicate guest IPs) does **not** abort the bootstrap. 2. **NPM:** `bash scripts/nginx-proxy-manager/upsert-it-read-api-proxy-host.sh` — proxy **`it-api.sankofa.nexus`** → **`http://:8787`** (override with **`IT_READ_API_PUBLIC_HOST`**). 3. **DNS:** `bash scripts/cloudflare/add-it-api-sankofa-dns.sh` — **`it-api.sankofa.nexus`** A → **`PUBLIC_IP`** (proxied). -4. **TLS:** In NPM UI, request a certificate for **`it-api.sankofa.nexus`** after DNS propagates (or widen **`CERT_DOMAINS_FILTER`** in `scripts/request-npmplus-certificates.sh`). +4. **TLS:** `CERT_DOMAINS_FILTER='it-api\.sankofa\.nexus' bash scripts/request-npmplus-certificates.sh` (or NPM UI → SSL). If the public URL redirect-loops (e.g. Cloudflare **Proxied** + NPM **SSL forced**), set the record to **DNS only** temporarily or align Cloudflare SSL mode with your origin. **Note:** Operator workstations outside VLAN 11 may be firewalled from **`192.168.11.11:8787`**; portal CT **7801** and NPM on LAN should still reach it. diff --git a/scripts/deployment/request-it-api-tls-npm.sh b/scripts/deployment/request-it-api-tls-npm.sh new file mode 100755 index 0000000..583ce7e --- /dev/null +++ b/scripts/deployment/request-it-api-tls-npm.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Request Let's Encrypt via NPMplus for it-api.sankofa.nexus and assign to proxy host. +# Requires NPM_* in repo .env. Same as: +# CERT_DOMAINS_FILTER='it-api\.sankofa\.nexus' bash scripts/request-npmplus-certificates.sh +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +export CERT_DOMAINS_FILTER='it-api\.sankofa\.nexus' +exec bash "${PROJECT_ROOT}/scripts/request-npmplus-certificates.sh" "$@" diff --git a/scripts/request-npmplus-certificates.sh b/scripts/request-npmplus-certificates.sh index b33dc1e..54412ba 100755 --- a/scripts/request-npmplus-certificates.sh +++ b/scripts/request-npmplus-certificates.sh @@ -67,7 +67,7 @@ echo "" # Authenticate (use jq to build JSON so password is safely escaped) log_info "Authenticating to NPMplus API..." AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}') -TOKEN_RESPONSE=$(curl -s -k -X POST "$NPM_URL/api/tokens" \ +TOKEN_RESPONSE=$(curl -s -k -L --http1.1 --connect-timeout 15 --max-time "${NPM_CURL_MAX_TIME:-180}" -X POST "$NPM_URL/api/tokens" \ -H "Content-Type: application/json" \ -d "$AUTH_JSON") @@ -87,7 +87,7 @@ echo "" # Get all proxy hosts log_info "Fetching proxy hosts..." -PROXY_HOSTS_JSON=$(curl -s -k -X GET "$NPM_URL/api/nginx/proxy-hosts" \ +PROXY_HOSTS_JSON=$(curl -s -k -L --http1.1 --connect-timeout 15 --max-time "${NPM_CURL_MAX_TIME:-300}" -X GET "$NPM_URL/api/nginx/proxy-hosts" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json") @@ -101,7 +101,7 @@ if [ "$PROXY_COUNT" = "0" ]; then fi # Build list of hosts that need a certificate (id|domain, one per line) -NEED_CERT_LIST=$(echo "$PROXY_HOSTS_JSON" | jq -r '.[] | select(.certificate_id == null or .certificate_id == 0) | "\(.id)|\(.domain_names[0] // "")"' 2>/dev/null | while IFS='|' read -r id domain; do +NEED_CERT_LIST=$(echo "$PROXY_HOSTS_JSON" | jq -r '.[] | select(.certificate_id == null or .certificate_id == 0 or .certificate_id == "0") | "\(.id)|\(.domain_names[0] // "")"' 2>/dev/null | while IFS='|' read -r id domain; do [ -z "$domain" ] || [ "$domain" = "null" ] && continue echo "$domain" | grep -q "test.*example.com" && continue echo "${id}|${domain}" @@ -133,7 +133,7 @@ fi # Try to get DNS (Cloudflare) credential_id so we use same method as UI (DNS challenge) CREDENTIAL_ID="" for path in "/api/nginx/letsencrypt-credentials" "/api/letsencrypt-credentials"; do - CRED_JSON=$(curl -s -k -X GET "$NPM_URL$path" -H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]") + CRED_JSON=$(curl -s -k -L --http1.1 --connect-timeout 15 --max-time "${NPM_CURL_MAX_TIME:-180}" -X GET "$NPM_URL$path" -H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]") if echo "$CRED_JSON" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then CREDENTIAL_ID=$(echo "$CRED_JSON" | jq -r '.[0].id // .[0].credential_id // empty' 2>/dev/null) [ -n "$CREDENTIAL_ID" ] && [ "$CREDENTIAL_ID" != "null" ] && break @@ -158,7 +158,7 @@ while IFS='|' read -r host_id domain; do # Request certificate. NPM API accepts only domain_names + provider (extra keys cause "must NOT have additional properties"). # For DNS (Cloudflare) and correct expiry, request certs in NPM UI: Hosts → host → SSL → Request new SSL Certificate → DNS Challenge, Cloudflare. log_info " Requesting SSL certificate..." - CERT_RESPONSE=$(curl -s -k -X POST "$NPM_URL/api/nginx/certificates" \ + CERT_RESPONSE=$(curl -s -k -L --http1.1 --connect-timeout 15 --max-time "${NPM_CURL_MAX_TIME:-180}" -X POST "$NPM_URL/api/nginx/certificates" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg domain "$domain" '{ domain_names: [$domain], provider: "letsencrypt" }')") @@ -175,7 +175,7 @@ while IFS='|' read -r host_id domain; do # Update proxy host to use certificate log_info " Assigning certificate to proxy host..." - UPDATE_RESPONSE=$(curl -s -k -X PUT "$NPM_URL/api/nginx/proxy-hosts/$host_id" \ + UPDATE_RESPONSE=$(curl -s -k -L --http1.1 --connect-timeout 15 --max-time "${NPM_CURL_MAX_TIME:-180}" -X PUT "$NPM_URL/api/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{