feat(sankofa): public web CT 7806, portal NPM/DNS defaults, Keycloak redirect helper

- Provision/sync scripts and systemd for corporate Next on 7806; IP_SANKOFA_PUBLIC_WEB for apex NPM
- Portal stack: NEXTAUTH_URL default portal.sankofa.nexus; NPM fleet + migrate + DNS ordering
- keycloak-sankofa-ensure-client-redirects.sh (KEYCLOAK_ADMIN_PASSWORD); .env.master.example hints
- Docs: task list, inventory, FQDN/E2E/EXPECTED_WEB_CONTENT, AGENTS pointers

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-29 13:41:02 -07:00
parent 50809f75dc
commit 7f3dcf2513
20 changed files with 600 additions and 49 deletions

View File

@@ -52,6 +52,14 @@ NPMPLUS_ALLTRA_HYBX_VMID=
IP_NPMPLUS_ALLTRA_HYBX=
NPM_URL_MIFOS=
# --- Keycloak Admin API (optional) ---
# For scripts/deployment/keycloak-sankofa-ensure-client-redirects.sh — merge portal/admin redirect URIs.
# KEYCLOAK_URL=https://keycloak.sankofa.nexus
# KEYCLOAK_REALM=master
# KEYCLOAK_CLIENT_ID=sankofa-portal
# KEYCLOAK_ADMIN=admin
# KEYCLOAK_ADMIN_PASSWORD=
# --- Fastly ---
FASTLY_API_TOKEN=
@@ -159,6 +167,8 @@ SANKOFA_PHOENIX_API_URL=
SANKOFA_PHOENIX_CLIENT_ID=
SANKOFA_PHOENIX_CLIENT_SECRET=
SANKOFA_PHOENIX_TENANT_ID=
# Corporate apex (sankofa.nexus) → CT 7806 when provisioned (default in ip-addresses stays portal until set)
# IP_SANKOFA_PUBLIC_WEB=192.168.11.63
# --- Frontend / MetaMask / Explorer ---
VITE_WALLETCONNECT_PROJECT_ID=

View File

@@ -21,11 +21,14 @@ Orchestration for Proxmox VE, Chain 138 (`smom-dbis-138/`), explorers, NPMplus,
| Submodule trees clean (CI / post-merge) | `bash scripts/verify/submodules-clean.sh` |
| Submodule + explorer remotes | `docs/00-meta/SUBMODULE_HYGIENE.md` |
| smom-dbis-138 `.env` in bash scripts | Prefer `source smom-dbis-138/scripts/lib/deployment/dotenv.sh` + `load_deployment_env --repo-root "$PROJECT_ROOT"` (trims RPC URL line endings). From an interactive shell: `source smom-dbis-138/scripts/load-env.sh`. Proxmox root scripts: `source scripts/lib/load-project-env.sh` (also trims common RPC vars). |
| Sankofa portal → CT 7801 (build + restart) | `./scripts/deployment/sync-sankofa-portal-7801.sh` (`--dry-run` first); sets `NEXTAUTH_URL` on CT via `sankofa-portal-ensure-nextauth-on-ct.sh` |
| Sankofa portal → CT 7801 (build + restart) | `./scripts/deployment/sync-sankofa-portal-7801.sh` (`--dry-run` first); default `NEXTAUTH_URL=https://portal.sankofa.nexus` via `sankofa-portal-ensure-nextauth-on-ct.sh` |
| Sankofa corporate web → CT 7806 | Provision: `./scripts/deployment/provision-sankofa-public-web-lxc-7806.sh`. Sync: `./scripts/deployment/sync-sankofa-public-web-to-ct.sh`. systemd: `config/systemd/sankofa-public-web.service`. Set `IP_SANKOFA_PUBLIC_WEB` in `.env`, then `scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh` |
| CCIP relay (r630-01 host) | Unit: `config/systemd/ccip-relay.service``/etc/systemd/system/ccip-relay.service`; `systemctl enable --now ccip-relay` |
| TsunamiSwap VM 5010 check | `./scripts/deployment/tsunamiswap-vm-5010-provision.sh` (inventory only until VM exists) |
| 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) |
| Keycloak redirect URIs (portal + admin) | `./scripts/deployment/keycloak-sankofa-ensure-client-redirects.sh` — 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'` |
| Completable (no LAN) | `./scripts/run-completable-tasks-from-anywhere.sh` |
| Operator (LAN + secrets) | `./scripts/run-all-operator-tasks-from-lan.sh` (use `--skip-backup` if `NPM_PASSWORD` unset) |
| Cloudflare bulk DNS → `PUBLIC_IP` | `./scripts/update-all-dns-to-public-ip.sh` — use **`--dry-run`** and **`--zone-only=sankofa.nexus`** (or `d-bis.org` / `mim4u.org` / `defi-oracle.io`) to limit scope; see script header. Prefer scoped **`CLOUDFLARE_API_TOKEN`** (see `.env.master.example`). |

View File

@@ -177,6 +177,17 @@ SANKOFA_PHOENIX_API_PORT="${SANKOFA_PHOENIX_API_PORT:-4000}"
SANKOFA_PORTAL_PORT="${SANKOFA_PORTAL_PORT:-3000}"
IP_SANKOFA_PHOENIX_API="${IP_SANKOFA_PHOENIX_API:-$IP_SERVICE_50}"
IP_SANKOFA_PORTAL="${IP_SANKOFA_PORTAL:-$IP_SERVICE_51}"
# Corporate apex (sankofa.nexus marketing). Default: same as portal until you set IP_SANKOFA_PUBLIC_WEB in .env (e.g. CT 7806).
# CT 7806 (sankofa-public-web) LAN IP when provisioned — see scripts/deployment/provision-sankofa-public-web-lxc-7806.sh
IP_SANKOFA_PUBLIC_WEB_CT="${IP_SANKOFA_PUBLIC_WEB_CT:-192.168.11.63}"
IP_SANKOFA_PUBLIC_WEB="${IP_SANKOFA_PUBLIC_WEB:-$IP_SANKOFA_PORTAL}"
SANKOFA_PUBLIC_WEB_PORT="${SANKOFA_PUBLIC_WEB_PORT:-$SANKOFA_PORTAL_PORT}"
# Client SSO apps (portal.sankofa.nexus, admin.sankofa.nexus) — typical: same LXC as hybrid portal (7801).
IP_SANKOFA_CLIENT_SSO="${IP_SANKOFA_CLIENT_SSO:-$IP_SANKOFA_PORTAL}"
SANKOFA_CLIENT_SSO_PORT="${SANKOFA_CLIENT_SSO_PORT:-$SANKOFA_PORTAL_PORT}"
# Operator dash (dash.sankofa.nexus). Leave unset to skip creating/updating dash in NPM fleet script until provisioned.
# IP_SANKOFA_DASH="192.168.11.xx"
# SANKOFA_DASH_PORT="${SANKOFA_DASH_PORT:-3000}"
# Gov Portals dev (VMID 7804) — DBIS, ICCC, OMNL, XOM at *.xom-dev.phoenix.sankofa.nexus
IP_GOV_PORTALS_DEV="192.168.11.54"

View File

@@ -0,0 +1,17 @@
[Unit]
Description=Sankofa corporate public web (Next.js root app) for sankofa.nexus
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/sankofa-public-web
Environment=NODE_ENV=production
Environment=PORT=3000
# Use login shell so corepack/pnpm PATH matches interactive CT admin.
ExecStart=/bin/bash -lc 'cd /opt/sankofa-public-web && exec pnpm start'
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@@ -1,7 +1,7 @@
# Web Properties — Ground Truth & Validation
**Last Updated:** 2026-03-27
**Document Version:** 1.5
**Last Updated:** 2026-03-29
**Document Version:** 1.6
**Status:** Active Documentation
---
@@ -314,17 +314,17 @@ Backend (typical):
| Service | Domain | VMID | IP | Port | Status | Access model |
|---------|--------|------|-----|------|--------|----------------|
| **Phoenix** (API today; division hostname) | phoenix.sankofa.nexus | 7800 | 192.168.11.50 | 4000 | ✅ Active | Public web **intent**; API paths coexist |
| **Sankofa public web** | sankofa.nexus | 7801 | 192.168.11.51 | 3000 | ✅ Active | Public **intent** (see hostname model) |
| **Sankofa public web** | sankofa.nexus | **7806** @ `192.168.11.63` (or **`IP_SANKOFA_PUBLIC_WEB`** in `.env`) | **`IP_SANKOFA_PUBLIC_WEB`** | **`SANKOFA_PUBLIC_WEB_PORT`** (`3000`) | ✅ Active | Provision: `provision-sankofa-public-web-lxc-7806.sh`; sync: `sync-sankofa-public-web-to-ct.sh`; NPM fleet script |
| **The Order (edge)** | the-order.sankofa.nexus | 10210 → 7801 | 192.168.11.39:80 → .51:3000 | 80 → 3000 | ✅ Active | HAProxy then portal; see §2b |
| **Sankofa Studio** | studio.sankofa.nexus | 7805 | 192.168.11.72 | 8000 | ✅ Active | `/studio/` |
| **Keycloak IdP** | keycloak.sankofa.nexus | 7802 | (see ALL_VMIDS) | 8080 | ✅ Active | IdP + `/admin` |
| **Client admin (SSO)** | admin.sankofa.nexus | — | — | — | 🔶 **Intent** — NPM + app upstream not pinned in VM inventory; may share portal stack (**7801**) until split (see §4, Open Decisions §4) | SSO |
| **Client portal (SSO)** | portal.sankofa.nexus | **7801** (typical) | 192.168.11.51 | 3000 | ✅ **Active** when NPM routes this hostname to the Sankofa portal stack; `NEXTAUTH_URL` / public OIDC URL per `scripts/deployment/sync-sankofa-portal-7801.sh` | SSO |
| **Operator dash** | dash.sankofa.nexus | — | — | — | 🔶 **Intent** — IP allowlist + system auth + MFA; **VMID/IP not fixed** in this matrix until NPM/upstream is wired (see §6) | IP + MFA |
| **Client admin (SSO)** | admin.sankofa.nexus | **7801** (typical) | 192.168.11.51 | 3000 | **NPM row** in fleet script (shares **`IP_SANKOFA_CLIENT_SSO`** with portal until split) | SSO |
| **Client portal (SSO)** | portal.sankofa.nexus | **7801** (typical) | 192.168.11.51 | 3000 | ✅ **NPM row** in fleet script; default **`NEXTAUTH_URL=https://portal.sankofa.nexus`** (`sync-sankofa-portal-7801.sh`) | SSO |
| **Operator dash** | dash.sankofa.nexus | — | **`IP_SANKOFA_DASH`** (when set) | **`SANKOFA_DASH_PORT`** | 🔶 **Fleet script** adds NPM upstream only when **`IP_SANKOFA_DASH`** set; app + IP allowlist + MFA still operator work (see §6) | IP + MFA |
| **SolaceScanScout** | explorer.d-bis.org | 5000 | 192.168.11.140 | 80/4000 | ✅ Active | Public |
| **Blockscout (generic hostname)** | blockscout.defi-oracle.io | **5000** | 192.168.11.140 | **80** (TLS at NPM) | ✅ **Active** when NPM proxies here; **same class** of Blockscout UI as §7 but **not** canonical **SolaceScanScout / Chain 138** branding (see §8) | Public |
**Table notes:** `admin` / `dash` rows stay **non-numeric** on VMID until inventory and NPM proxy rows are authoritative in [ALL_VMIDS_ENDPOINTS.md](../04-configuration/ALL_VMIDS_ENDPOINTS.md) and your NPM export. **`blockscout.defi-oracle.io`** has been documented in routing summaries as terminating on **VMID 5000** (`192.168.11.140:80`); confirm live NPM if behavior differs.
**Table notes:** `dash` **VMID** stays **TBD** until an operator app is pinned; set **`IP_SANKOFA_DASH`** in `config/ip-addresses.conf` to create/update the NPM proxy via `update-npmplus-proxy-hosts-api.sh`. **`admin`** / **`portal`** share **7801** in the default config. Confirm live NPM + DNS + Keycloak redirect URIs after each change. **`blockscout.defi-oracle.io`**: see [ALL_VMIDS_ENDPOINTS.md](../04-configuration/ALL_VMIDS_ENDPOINTS.md).
---

View File

@@ -0,0 +1,161 @@
# Sankofa and Phoenix — public, portal, and system admin endpoint correction tasks
**Created:** 2026-03-29
**Purpose:** Ordered checklist to align **DNS, NPM upstreams, deployed apps, auth URLs, and inventory** with the hostname model in [EXPECTED_WEB_CONTENT.md](../02-architecture/EXPECTED_WEB_CONTENT.md) and [FQDN_EXPECTED_CONTENT.md](../04-configuration/FQDN_EXPECTED_CONTENT.md).
**Verify:** [E2E_ENDPOINTS_LIST.md](../04-configuration/E2E_ENDPOINTS_LIST.md), `scripts/verify/verify-end-to-end-routing.sh --profile=public` (from LAN).
---
## Target state (summary)
| Hostname | Tier | Expected content |
|----------|------|------------------|
| `sankofa.nexus` / `www` | Public web | Corporate / brand (Sankofa — Sovereign Technologies), unauthenticated marketing pages. |
| `phoenix.sankofa.nexus` / `www` | Public web (+ API) | Phoenix Cloud Services division web (intent); API paths may coexist until split. |
| `portal.sankofa.nexus` | Client SSO | Hybrid cloud / client workspace, marketplace, NextAuth + Keycloak with **public URL = this host**. |
| `admin.sankofa.nexus` | Client SSO | Client access administration (IAM-style); not Keycloak operator `/admin`. |
| `keycloak.sankofa.nexus` | IdP | Realm login, tokens, well-known; operator console at `/admin`. |
| `dash.sankofa.nexus` | Operator / systems | IP allowlist + system auth + MFA; not client self-service. |
| `the-order.sankofa.nexus` | Program portal | OSJ / Order app; edge 10210 (HAProxy) → appropriate upstream. |
| `studio.sankofa.nexus` | Tooling | Sankofa Studio (7805), `/studio/`. |
**Remaining operator work:** Add **DNS A/AAAA** records for `portal.sankofa.nexus` and `admin.sankofa.nexus` (and `dash` when used). Run **`scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh`** from LAN with `NPM_PASSWORD`, assign **SSL** in NPM for new rows, and update **Keycloak** client redirect URIs for `https://portal.sankofa.nexus/*` (and `admin` if distinct). Provision **CT 7806** (or set **`IP_SANKOFA_PUBLIC_WEB`**) when the corporate site should no longer share the portal binary on apex.
---
## Implementation status (repo — 2026-03-29)
| Item | Done in repo |
|------|----------------|
| NPM fleet script: `portal` / `admin` / conditional `dash` | `scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh` |
| NPM migrate script + Sankofa ID script | `migrate-to-npmplus.sh`, `update-sankofa-npmplus-proxy-hosts.sh` (apex uses `IP_SANKOFA_PUBLIC_WEB`) |
| Config vars | `config/ip-addresses.conf`: `IP_SANKOFA_PUBLIC_WEB`, `SANKOFA_PUBLIC_WEB_PORT`, `IP_SANKOFA_CLIENT_SSO`, `SANKOFA_CLIENT_SSO_PORT`, optional `IP_SANKOFA_DASH` |
| Default `NEXTAUTH_URL` for portal | `sync-sankofa-portal-7801.sh`, `sankofa-portal-ensure-nextauth-on-ct.sh`, `enable-sankofa-portal-login-7801.sh`**`https://portal.sankofa.nexus`** |
| Corporate site deploy path | `scripts/deployment/sync-sankofa-public-web-to-ct.sh` + `config/systemd/sankofa-public-web.service` |
| Inventory / index | `ALL_VMIDS_ENDPOINTS.md`, `AGENTS.md`, `MASTER_INDEX.md` (task list row) |
**Not automatable here:** Keycloak **realm client** redirect URI edits (operator console), provisioning CT **7806** for corporate apex split, Phoenix marketing page on **7800**, operator **dash** app + **`IP_SANKOFA_DASH`**, NPM **IP allowlists** for dash, stakeholder sign-off.
**Operator run (2026-03-29, LAN):** Loaded `.env` (`NPM_PASSWORD`, Cloudflare). Ran `update-npmplus-proxy-hosts-api.sh` (added NPM rows **portal** / **admin**). Ran `sankofa-portal-ensure-nextauth-on-ct.sh` + full `sync-sankofa-portal-7801.sh`. Ran `update-all-dns-to-public-ip.sh --zone-only=sankofa.nexus` (new A records **portal**, **admin**, **keycloak**; **studio** updated). E2E `verify-end-to-end-routing.sh --profile=public` exit `0`; evidence: `docs/04-configuration/verification-evidence/e2e-verification-20260329_045318/` (and `045210/`). **7806** provisioned **2026-03-29** on r630-01 @ **192.168.11.63**; corporate site synced; NPM **sankofa.nexus** / **www****.63:3000** when `.env` sets **`IP_SANKOFA_PUBLIC_WEB=192.168.11.63`** (operator machine). Ran `request-npmplus-certificates.sh` with `CERT_DOMAINS_FILTER` for **portal.sankofa.nexus** and **admin.sankofa.nexus**; NPM certificate IDs **180** / **179** assigned with **SSL forced**; HTTPS smoke **200**. **Keycloak:** set `KEYCLOAK_ADMIN_PASSWORD` in `.env` and run `scripts/deployment/keycloak-sankofa-ensure-client-redirects.sh`, or add **Valid redirect URIs** / **Web origins** in the console for **portal** and **admin** (client **sankofa-portal**, realm **master** per portal `.env` template).
---
## Phase 0 — Decisions and inventory (blockers)
1. **Confirm split strategy for 7801**
- **Option A:** Second CT/VM (new VMID) for **public marketing** Next app; keep **7801** for **`portal`/`admin`** only.
- **Option B:** Single process with host-based routing (middleware) — higher risk; only if product accepts shared runtime.
- Document chosen VMID(s), ports, and systemd unit names in [ALL_VMIDS_ENDPOINTS.md](../04-configuration/ALL_VMIDS_ENDPOINTS.md).
2. **Resolve Phoenix “public web vs API”** (open decision in EXPECTED_WEB_CONTENT)
- Either add **static/marketing** upstream or **path-based** split on 7800, or dedicate a small VM for Phoenix marketing.
- Record **browser-default** behavior for `GET /` on `phoenix.sankofa.nexus` (not only `/health` / GraphQL).
3. **Pin `admin` and `dash` targets**
- **admin:** same app as portal (path prefix), separate deploy, or separate repo — decide and record IP:port / VMID.
- **dash:** choose app (existing vs greenfield), VMID, IP:port, MFA method, and **NPM IP allowlist** / VPN-only DNS policy.
4. **Keycloak clients**
- For each public hostname that uses OIDC (`portal`, `admin`, and any others), register **valid redirect URIs** and **web origins** for the **final** URLs (not interim apex).
---
## Phase 1 — Sankofa public apex (`sankofa.nexus`)
5. **Build and deploy corporate site** from **`Sankofa` repo root** (not `Sankofa/portal`): `src/app`, `public/`, root `package.json` / `next.config.js`.
6. **Provision runtime** on the chosen host (new CT or existing): Node/pnpm, systemd (or container), production `pnpm build` + `pnpm start`, listen port (e.g. 3000).
7. **Point NPM** `sankofa.nexus` and `www.sankofa.nexus`**new public-site upstream** (update `update-npmplus-proxy-hosts-api.sh`, `update-sankofa-npmplus-proxy-hosts.sh`, `migrate-to-npmplus.sh` as needed).
8. **Remove portal-only behavior from apex** (no forced login shell on `/` for anonymous users unless product explicitly wants a teaser).
9. **Smoke test:** anonymous `GET https://sankofa.nexus/` shows marketing; links to `phoenix`, `portal`, `the-order` work.
---
## Phase 2 — Client portal (`portal.sankofa.nexus`)
10. **Ensure NPM proxy host** `portal.sankofa.nexus` exists and points to **portal** upstream (today typically **192.168.11.51:3000** / 7801 if portal stays there). Add lines to `update-npmplus-proxy-hosts-api.sh` (and sibling updaters).
11. **Set NextAuth / OIDC public URL** to `https://portal.sankofa.nexus`:
- `SANKOFA_PORTAL_NEXTAUTH_URL=https://portal.sankofa.nexus` when running `sync-sankofa-portal-7801.sh` and `sankofa-portal-ensure-nextauth-on-ct.sh`.
- Update defaults in script headers/comments so operators are not steered to apex.
12. **Update `Sankofa/portal`** `.env.example` / README: replace `NEXTAUTH_URL=https://sankofa.nexus` with **`https://portal.sankofa.nexus`**.
13. **Keycloak:** client `redirect_uri` / `Valid Redirect URIs` must include `https://portal.sankofa.nexus/*` (and dev hosts if any).
14. **Cookie / CSRF:** confirm `NEXTAUTH_URL` matches browser bar; fix any `localhost` or apex leakage in callbacks.
---
## Phase 3 — Client admin (`admin.sankofa.nexus`)
15. **Implement or wire** the admin UI (if not already a route in portal app, add app or path and deploy target).
16. **NPM:** add `admin.sankofa.nexus` → upstream (may equal portal VM until split).
17. **OIDC:** separate Keycloak client or fine-grained roles; redirect URIs for `https://admin.sankofa.nexus/*`.
18. **Document** in ALL_VMIDS_ENDPOINTS + EXPECTED_WEB_CONTENT deployment table (replace “intent / TBD” with IP:port).
---
## Phase 4 — Operator systems admin (`dash.sankofa.nexus`)
19. **Deploy** dash application to chosen VMID with **MFA** and **strong session** policy.
20. **NPM:** create `dash.sankofa.nexus` proxy; attach **IP allowlist** (NPM access list / firewall) per EXPECTED_WEB_CONTENT.
21. **DNS:** restrict resolution to operator networks if policy requires (optional; often IP block at edge is enough).
22. **Inventory:** pin VMID, IP, port in ALL_VMIDS_ENDPOINTS and FQDN_EXPECTED_CONTENT.
---
## Phase 5 — Phoenix public (`phoenix.sankofa.nexus`)
23. **Deliver browser-facing division home** (or documented interim: redirect to docs / status page) consistent with FQDN_EXPECTED_CONTENT.
24. **Preserve API** (`/health`, `/graphql`, etc.) for monitors and clients; avoid breaking E2E `/health` checks.
25. **NPM / headers:** ensure WebSocket or long-lived connections if GraphQL subscriptions are exposed on same host.
26. **Copy and nav:** corporate site links “Phoenix” to this hostname, not to portal.
---
## Phase 6 — Program and tooling hostnames (sanity)
27. **the-order.sankofa.nexus:** confirm HAProxy **10210** → correct upstream; if Order app diverges from generic portal, point backend to **the_order** build instead of 7801 generic portal (per product).
28. **studio.sankofa.nexus:** confirm 7805 `/studio/`; certificate and upstream unchanged unless Studio moves.
29. **www.*:** confirm **301** to apex for `sankofa`, `phoenix`, `the-order` in NPM `advanced_config`.
---
## Phase 7 — Automation, docs, and regression
30. ~~**Scripts:** extend fleet NPM updater~~ **Done:** `update-npmplus-proxy-hosts-api.sh`, `migrate-to-npmplus.sh`, `update-sankofa-npmplus-proxy-hosts.sh` + `config/ip-addresses.conf`.
31. ~~**Corporate sync script**~~ **Done:** `scripts/deployment/sync-sankofa-public-web-to-ct.sh` + `config/systemd/sankofa-public-web.service`.
32. **E2E:** after DNS + NPM SSL for `portal` / `admin` (and `dash` when live), move hosts from “optional when fail” to **required** in `verify-end-to-end-routing.sh` / tighten `E2E_OPTIONAL_WHEN_FAIL`.
33. ~~**EXPECTED_WEB_CONTENT.md** table~~ **Partially done:** `admin` / `portal` NPM rows documented; **`dash`** stays 🔶 until app + **`IP_SANKOFA_DASH`**; apex row notes **7806** / `IP_SANKOFA_PUBLIC_WEB`.
34. **Run** `verify-end-to-end-routing.sh --profile=public` from LAN; archive evidence under `docs/04-configuration/verification-evidence/`.
35. **Stakeholder sign-off:** security review of public vs SSO vs operator boundaries and Keycloak client surface area.
---
## Quick dependency graph
```mermaid
flowchart LR
D0[Phase 0 decisions] --> P1[Phase 1 apex public]
D0 --> P2[Phase 2 portal host]
D0 --> P3[Phase 3 admin]
D0 --> P4[Phase 4 dash]
D0 --> P5[Phase 5 Phoenix web]
P2 --> KC[Keycloak client URIs]
P3 --> KC
P1 --> NPM[NPM / DNS updates]
P2 --> NPM
P3 --> NPM
P4 --> NPM
P5 --> NPM
NPM --> E2E[Phase 7 E2E + docs]
```
---
## References (repo)
- Hostname model: [EXPECTED_WEB_CONTENT.md](../02-architecture/EXPECTED_WEB_CONTENT.md)
- FQDN matrix: [FQDN_EXPECTED_CONTENT.md](../04-configuration/FQDN_EXPECTED_CONTENT.md)
- IPs / VMIDs: [ALL_VMIDS_ENDPOINTS.md](../04-configuration/ALL_VMIDS_ENDPOINTS.md)
- NPM API updater: `scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh`
- Portal deploy: `scripts/deployment/sync-sankofa-portal-7801.sh`, `scripts/deployment/sankofa-portal-ensure-nextauth-on-ct.sh`
- Corporate web deploy: `scripts/deployment/sync-sankofa-public-web-to-ct.sh`
- Order edge: `scripts/deployment/provision-order-haproxy-10210.sh`
- Source trees (operator workstation): `../Sankofa` (public site root), `../Sankofa/portal` (client portal), `~/projects/the_order` (Order UI)

View File

@@ -1,6 +1,6 @@
# Complete VMID and Endpoints Reference
**Last Updated:** 2026-03-26
**Last Updated:** 2026-03-29
**Document Version:** 1.2
**Status:** Active Documentation — **Master (source of truth)** for VMID, IP, port, and domain mapping. See [MASTER_DOCUMENTATION_INDEX.md](../00-meta/MASTER_DOCUMENTATION_INDEX.md).
@@ -211,6 +211,22 @@ The following VMIDs have been permanently removed:
---
### Hyperledger Aries / AnonCreds
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 6500 | 192.168.11.88 | aries-1 | ✅ Running | ACA-Py DIDComm: 8030, Admin API: 8031 | Hyperledger Aries / AnonCreds agent runtime |
---
### Hyperledger Caliper
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 6600 | 192.168.11.93 | caliper-1 | ✅ Running | Local CLI workspace, outbound RPC to 192.168.11.211:8545 / 8546 | Hyperledger Caliper benchmark harness |
---
### DBIS Core Services
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
@@ -256,15 +272,17 @@ The following VMIDs have been permanently removed:
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 7800 | 192.168.11.50 | sankofa-api-1 | ✅ Running | GraphQL: 4000, Health: /health | Phoenix API (Cloud Platform Portal) |
| 7801 | 192.168.11.51 | sankofa-portal-1 | ✅ Running | Web: 3000 | Sankofa Portal (Company Website) |
| 7801 | 192.168.11.51 | sankofa-portal-1 | ✅ Running | Web: 3000 | Hybrid cloud **client portal** (`portal.sankofa.nexus` / `admin.sankofa.nexus` when NPM routes); not the long-term corporate apex app — see `IP_SANKOFA_PUBLIC_WEB` / `sync-sankofa-public-web-to-ct.sh` |
| 7802 | 192.168.11.52 | sankofa-keycloak-1 | ✅ Running | Keycloak: 8080, Admin: /admin | Identity and Access Management |
| 7803 | 192.168.11.53 | sankofa-postgres-1 | ✅ Running | PostgreSQL: 5432 | Database Service |
| 7804 | 192.168.11.54 | (Gov Portals dev) | ✅ Running | Web: 80 | Gov Portals — DBIS, ICCC, OMNL, XOM (*.xom-dev.phoenix.sankofa.nexus) |
| 7805 | 192.168.11.72 | sankofa-studio | — | API: 8000 | Sankofa Studio (FusionAI Creator) — studio.sankofa.nexus (IP .72; .55 = VMID 10230 order-vault) |
| 7806 | 192.168.11.63 | sankofa-public-web | ✅ Running | Web: 3000 | Corporate / marketing Next.js (Sankofa **repo root**); provision: `scripts/deployment/provision-sankofa-public-web-lxc-7806.sh`; deploy: `scripts/deployment/sync-sankofa-public-web-to-ct.sh`; NPM apex via **`IP_SANKOFA_PUBLIC_WEB`** (`.env` or override) |
**Public Domains** (NPMplus routing):
- `sankofa.nexus` → Routes to `http://192.168.11.51:3000` (Sankofa Portal/VMID 7801)
- `www.sankofa.nexus` → Same upstream as apex; NPM **`advanced_config`** issues **301** to **`https://sankofa.nexus`** (preserve path/query via `$request_uri`). ✅
- `sankofa.nexus` / `www.sankofa.nexus`**`IP_SANKOFA_PUBLIC_WEB`:`SANKOFA_PUBLIC_WEB_PORT** (typical: **7806** `192.168.11.63:3000` when `.env` sets `IP_SANKOFA_PUBLIC_WEB`; else defaults to portal **7801**); fleet script: `scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh`. **`www`** → **301** → apex `https://sankofa.nexus` (`$request_uri`).
- `portal.sankofa.nexus` / `admin.sankofa.nexus`**`IP_SANKOFA_CLIENT_SSO`:`SANKOFA_CLIENT_SSO_PORT** (typical: 7801 `:3000`). NextAuth / OIDC public URL: **`https://portal.sankofa.nexus`**. ✅ when NPM proxy rows exist (fleet script creates/updates them).
- `dash.sankofa.nexus` → Set **`IP_SANKOFA_DASH`** (+ `SANKOFA_DASH_PORT`) in `config/ip-addresses.conf` to enable upstream in the fleet script; IP allowlist at NPM is operator policy. 🔶 until dash app + env are set.
- `phoenix.sankofa.nexus` → Routes to `http://192.168.11.50:4000` (Phoenix API/VMID 7800) ✅
- `www.phoenix.sankofa.nexus` → Same upstream; **301** to **`https://phoenix.sankofa.nexus`**. ✅
- `the-order.sankofa.nexus` / `www.the-order.sankofa.nexus` → OSJ management portal (secure auth). App source: **the_order** at `~/projects/the_order`. NPMplus default upstream: **order-haproxy** `http://192.168.11.39:80` (VMID **10210**), which proxies to Sankofa portal `http://192.168.11.51:3000` (7801). Fallback: set `THE_ORDER_UPSTREAM_IP` / `THE_ORDER_UPSTREAM_PORT` to `.51` / `3000` if HAProxy is offline. **`www.the-order.sankofa.nexus`** → **301** **`https://the-order.sankofa.nexus`** (same as `www.sankofa` / `www.phoenix`).
@@ -520,8 +538,11 @@ This section lists all endpoints that should be configured in NPMplus, extracted
| `secure.mim4u.org` | `192.168.11.37` | `http` | `80` | ❌ No | MIM4U Secure Portal (VMID 7810) |
| `training.mim4u.org` | `192.168.11.37` | `http` | `80` | ❌ No | MIM4U Training Portal (VMID 7810) |
| **Sankofa Phoenix Services** |
| `sankofa.nexus` | `192.168.11.51` | `http` | `3000` | ❌ No | Sankofa Portal - Company Website (VMID 7801) ✅ **Deployed** |
| `www.sankofa.nexus` | `192.168.11.51` | `http` | `3000` | ❌ No | Sankofa Portal (VMID 7801) ✅ **Deployed** |
| `sankofa.nexus` | **`IP_SANKOFA_PUBLIC_WEB`** (default `.51` until public-web CT) | `http` | **`SANKOFA_PUBLIC_WEB_PORT`** (`3000`) | ❌ No | Corporate apex; fleet script `update-npmplus-proxy-hosts-api.sh` |
| `www.sankofa.nexus` | same as apex | `http` | same | ❌ No | **301**`https://sankofa.nexus` |
| `portal.sankofa.nexus` | **`IP_SANKOFA_CLIENT_SSO`** (typ. `.51` / 7801) | `http` | **`SANKOFA_CLIENT_SSO_PORT`** (`3000`) | ❌ No | Client SSO portal; `NEXTAUTH_URL=https://portal.sankofa.nexus` |
| `admin.sankofa.nexus` | same as portal | `http` | same | ❌ No | Client access admin (same upstream until split) |
| `dash.sankofa.nexus` | **`IP_SANKOFA_DASH`** (set in `ip-addresses.conf`) | `http` | **`SANKOFA_DASH_PORT`** | ❌ No | Operator dash — row omitted from fleet script until `IP_SANKOFA_DASH` set |
| `phoenix.sankofa.nexus` | `192.168.11.50` | `http` | `4000` | ❌ No | Phoenix API - Cloud Platform Portal (VMID 7800) ✅ **Deployed** |
| `www.phoenix.sankofa.nexus` | `192.168.11.50` | `http` | `4000` | ❌ No | Phoenix API (VMID 7800) ✅ **Deployed** |
| `the-order.sankofa.nexus`, `www.the-order.sankofa.nexus` | `192.168.11.39` (10210 HAProxy; default) or `192.168.11.51` (direct portal if env override) | `http` | `80` or `3000` | ❌ No | NPM → **.39:80** by default; HAProxy → **.51:3000** |
@@ -537,9 +558,7 @@ Some domains use path-based routing in NPM configs:
- `/graph``http://192.168.11.155:3000` (DBIS GraphQL)
- `/``http://192.168.11.130:80` (DBIS Frontend)
**`sankofa.nexus`** (per deploy script):
- `/api``http://10.160.0.10:4000` (Sankofa API)
- `/``http://10.160.0.11:3000` (Sankofa Portal)
**`sankofa.nexus`** (intent): corporate marketing at **`IP_SANKOFA_PUBLIC_WEB`**; **`portal.sankofa.nexus`** serves the authenticated portal at **`IP_SANKOFA_CLIENT_SSO`**. Legacy path-based splits (if any) should be reconciled with [EXPECTED_WEB_CONTENT.md](../02-architecture/EXPECTED_WEB_CONTENT.md).
**Note**: NPMplus may need custom location blocks or separate proxy hosts for path-based routing.
@@ -550,7 +569,9 @@ Some domains use path-based routing in NPM configs:
| Domain | Correct target (VMID, IP:port) | Do NOT point to |
|--------|--------------------------------|-----------------|
| `explorer.d-bis.org` | 5000, 192.168.11.140:80 (web), :4000 (API) | — |
| `sankofa.nexus`, `www.sankofa.nexus` | 7801, 192.168.11.51:3000 | 192.168.11.140 (Blockscout) |
| `sankofa.nexus`, `www.sankofa.nexus` | **Public web:** target **7806** (or `IP_SANKOFA_PUBLIC_WEB`) when split; defaults still **7801**, 192.168.11.51:3000 | 192.168.11.140 (Blockscout) |
| `portal.sankofa.nexus`, `admin.sankofa.nexus` | **7801**, 192.168.11.51:3000 (`IP_SANKOFA_CLIENT_SSO`) | 192.168.11.140 (Blockscout) |
| `dash.sankofa.nexus` | Set **`IP_SANKOFA_DASH`** when operator dash exists | 192.168.11.140 (Blockscout) |
| `phoenix.sankofa.nexus`, `www.phoenix.sankofa.nexus` | 7800, 192.168.11.50:4000 | 192.168.11.140 (Blockscout) |
| `the-order.sankofa.nexus`, `www.the-order.sankofa.nexus` | 10210, 192.168.11.39:80 | 192.168.11.140 (Blockscout) |
| `studio.sankofa.nexus` | 7805, 192.168.11.72:8000 | — |
@@ -561,7 +582,7 @@ If NPMplus proxy hosts for sankofa.nexus or phoenix.sankofa.nexus currently poin
---
**Last Updated**: 2026-03-27
**Last Updated**: 2026-03-29
**Maintained By**: Infrastructure Team
---

View File

@@ -9,7 +9,8 @@
**What each hostname should present (operator narrative):** [FQDN_EXPECTED_CONTENT.md](FQDN_EXPECTED_CONTENT.md).
**Latest verified public pass:** `2026-03-27` via `bash scripts/verify/verify-end-to-end-routing.sh --profile=public` with report at [verification_report.md](verification-evidence/e2e-verification-20260327_134032/verification_report.md). Result: exit `0`, `DNS passed: 38`, `Failed: 0`, `HTTPS passed: 19`, `Skipped / optional: 1` (after `run-all-operator-tasks-from-lan.sh` NPM sync; `rpc.defi-oracle.io` may log HTTP 405 on the verifier probe but stays non-failing for the profile).
**Latest verified public pass:** `2026-03-29` via `bash scripts/verify/verify-end-to-end-routing.sh --profile=public` with report at [verification_report.md](verification-evidence/e2e-verification-20260329_045318/verification_report.md). Result: exit `0`, `DNS passed: 42`, `Failed: 0`, `HTTPS passed: 27`, `Skipped / optional: 2` (NPM fleet + sankofa zone DNS for `portal` / `admin` / `keycloak`, portal sync + NextAuth on 7801).
**Earlier same day:** [verification_report.md](verification-evidence/e2e-verification-20260329_045210/verification_report.md). **Previous:** `2026-03-27` — [verification_report.md](verification-evidence/e2e-verification-20260327_134032/verification_report.md).
**Latest verified private/admin pass:** `2026-03-27` via `bash scripts/verify/verify-end-to-end-routing.sh --profile=private` with report at [verification_report.md](verification-evidence/e2e-verification-20260327_134137/verification_report.md). Result: exit `0`, `DNS passed: 4`, `Failed: 0`.
**Evidence folders:** Each run creates `verification-evidence/e2e-verification-YYYYMMDD_HHMMSS/`. Commit the runs you want on record; older dirs can be removed locally to reduce noise (`scripts/maintenance/prune-e2e-verification-evidence.sh --dry-run` lists candidates). Routing truth is **not** inferred from old reports—use [ALL_VMIDS_ENDPOINTS.md](ALL_VMIDS_ENDPOINTS.md).
@@ -41,7 +42,7 @@
| studio.sankofa.nexus | web | https://studio.sankofa.nexus | Sankofa Studio (FusionAI Creator) at VMID 7805. |
| keycloak.sankofa.nexus | web | https://keycloak.sankofa.nexus | Keycloak IdP (VMID 7802); client SSO for admin/portal. |
| admin.sankofa.nexus | web | https://admin.sankofa.nexus | Client SSO: access administration (hostname intent; NPM upstream TBD). |
| portal.sankofa.nexus | web | https://portal.sankofa.nexus | Client SSO: portal / marketplace (typical upstream VMID 7801). |
| portal.sankofa.nexus | web | https://portal.sankofa.nexus | Client SSO: portal / marketplace (typical upstream VMID 7801). Add DNS + NPM row via `update-npmplus-proxy-hosts-api.sh`; NextAuth public URL `https://portal.sankofa.nexus`. |
| dash.sankofa.nexus | web | https://dash.sankofa.nexus | Operator systems dashboard (IP allowlist + MFA intent; upstream TBD). |
| docs.d-bis.org | web | https://docs.d-bis.org | Docs on explorer nginx where configured. |
| blockscout.defi-oracle.io | web | https://blockscout.defi-oracle.io | Generic Blockscout hostname (often VMID 5000); not canonical Chain 138 **explorer.d-bis.org**. |

View File

@@ -1,6 +1,6 @@
# FQDN expected content (what users and clients should see)
**Last Updated:** 2026-03-27 (aligned with EXPECTED_WEB_CONTENT deployment table v1.5)
**Last Updated:** 2026-03-29 (NPM fleet script includes `portal` / `admin` / optional `dash`; apex uses `IP_SANKOFA_PUBLIC_WEB`)
**Purpose:** One-page description of **what should be presented** at each public NPM-routed hostname after HTTPS. Use this before pruning evidence or changing proxies so expectations stay aligned with product intent.
**Canonical routing (IPs, VMIDs, ports):** [ALL_VMIDS_ENDPOINTS.md](ALL_VMIDS_ENDPOINTS.md), [RPC_ENDPOINTS_MASTER.md](RPC_ENDPOINTS_MASTER.md).
@@ -30,7 +30,7 @@
| FQDN | Kind | What should be displayed or returned |
|------|------|--------------------------------------|
| `sankofa.nexus` | Web | **Sankofa — Sovereign Technologies:** public corporate / brand web (mission, narrative, entry points). |
| `sankofa.nexus` | Web | **Sankofa — Sovereign Technologies:** public corporate / brand web (mission, narrative, entry points). NPM upstream: **`IP_SANKOFA_PUBLIC_WEB`:`SANKOFA_PUBLIC_WEB_PORT`** (defaults to portal IP until marketing CT is split). |
| `www.sankofa.nexus` | 301 → apex | Browser ends on `https://sankofa.nexus/...`. |
| `phoenix.sankofa.nexus` | Web / API | **Phoenix Cloud Services** (division of Sankofa): public-facing **division web** (intent). Same deployment may still expose API paths (`/health`, `/graphql`, …). E2E verifier may use `/health`. |
| `www.phoenix.sankofa.nexus` | 301 → apex | Browser ends on `https://phoenix.sankofa.nexus/...`. |
@@ -48,8 +48,8 @@
| FQDN | VMID / target | Notes |
|------|---------------|--------|
| `keycloak.sankofa.nexus` | **7802** (detail in [ALL_VMIDS_ENDPOINTS.md](ALL_VMIDS_ENDPOINTS.md)) | IdP + `/admin` for platform operators |
| `portal.sankofa.nexus` | **7801** · `192.168.11.51:3000` | **Active** when NPM routes here; public OIDC / `NEXTAUTH_URL` via `scripts/deployment/sync-sankofa-portal-7801.sh` |
| `admin.sankofa.nexus` | 🔶 **Not pinned** in VM inventory | Hostname **intent**; NPM + app upstream TBD; may share **7801** until split |
| `portal.sankofa.nexus` | **`IP_SANKOFA_CLIENT_SSO`** (typ. **7801** · `192.168.11.51:3000`) | Fleet script creates/updates NPM row; default **`NEXTAUTH_URL=https://portal.sankofa.nexus`** (`sync-sankofa-portal-7801.sh`) |
| `admin.sankofa.nexus` | same as **`IP_SANKOFA_CLIENT_SSO`** | Shares portal upstream until split; NPM row in fleet script |
### Operator / systems (IP-gated + MFA)
@@ -121,7 +121,7 @@
## Operator checklist
- **Wrong content** (e.g. explorer UI on `sankofa.nexus`, or HTML on RPC hostname) usually means **NPM upstream** or **DNS** is wrong — fix with `update-npmplus-proxy-hosts-api.sh` and [ALL_VMIDS_ENDPOINTS.md](ALL_VMIDS_ENDPOINTS.md).
- **Wrong content** (e.g. explorer UI on `sankofa.nexus`, or HTML on RPC hostname) usually means **NPM upstream** or **DNS** is wrong — fix with `update-npmplus-proxy-hosts-api.sh` and [ALL_VMIDS_ENDPOINTS.md](ALL_VMIDS_ENDPOINTS.md). Ensure **`portal.sankofa.nexus`** / **`admin.sankofa.nexus`** DNS exist; **`dash`** is created in NPM only when **`IP_SANKOFA_DASH`** is set in `config/ip-addresses.conf`.
- **301 on `www.*`** is intentional; content is judged on the **apex** hostname after redirect.
---

View File

@@ -48,6 +48,7 @@
| Contract / address status | [11-references/ADDRESS_MATRIX_AND_STATUS.md](11-references/ADDRESS_MATRIX_AND_STATUS.md), [11-references/CONTRACT_ADDRESSES_REFERENCE.md](11-references/CONTRACT_ADDRESSES_REFERENCE.md), [11-references/CONTRACT_NEXT_STEPS_LIST.md](11-references/CONTRACT_NEXT_STEPS_LIST.md) (59-addr check) | CONTRACT_INVENTORY_AND_VERIFICATION (deleted) |
| **Proxmox VMIDs, LAN IPs, NPM targets** | [04-configuration/ALL_VMIDS_ENDPOINTS.md](04-configuration/ALL_VMIDS_ENDPOINTS.md), [`config/ip-addresses.conf`](../config/ip-addresses.conf), [11-references/NETWORK_CONFIGURATION_MASTER.md](11-references/NETWORK_CONFIGURATION_MASTER.md), [`config/proxmox-operational-template.json`](../config/proxmox-operational-template.json) | Dated inventories under `docs/archive/` (paths on disk only) |
| **FQDN → expected content (web / API / RPC)** | [04-configuration/FQDN_EXPECTED_CONTENT.md](04-configuration/FQDN_EXPECTED_CONTENT.md) | — |
| **Sankofa / Phoenix public vs portal vs admin endpoints (fix list)** | [03-deployment/SANKOFA_PHOENIX_PUBLIC_PORTAL_ADMIN_ENDPOINT_CORRECTION_TASKS.md](03-deployment/SANKOFA_PHOENIX_PUBLIC_PORTAL_ADMIN_ENDPOINT_CORRECTION_TASKS.md) | — |
| **IP conflict resolutions** | [reports/status/IP_CONFLICTS_RESOLUTION_COMPLETE.md](../reports/status/IP_CONFLICTS_RESOLUTION_COMPLETE.md), `scripts/resolve-ip-conflicts.sh` | — |
---
@@ -58,7 +59,7 @@
|------|-----------------|
| **00-meta** (tasks, next steps, phases) | [00-meta/NEXT_STEPS_INDEX.md](00-meta/NEXT_STEPS_INDEX.md), [00-meta/PHASES_AND_TASKS_MASTER.md](00-meta/PHASES_AND_TASKS_MASTER.md) |
| **02-architecture** | [02-architecture/](02-architecture/) — **Public sector + Phoenix catalog baseline:** [02-architecture/PUBLIC_SECTOR_TENANCY_MARKETPLACE_AND_DEPLOYMENT_BASELINE.md](02-architecture/PUBLIC_SECTOR_TENANCY_MARKETPLACE_AND_DEPLOYMENT_BASELINE.md); **non-goals (incl. catalog vs marketing §9):** [02-architecture/NON_GOALS.md](02-architecture/NON_GOALS.md); **DBIS Chain 138:** [dbis_chain_138_technical_master_plan.md](../dbis_chain_138_technical_master_plan.md), [02-architecture/DBIS_NODE_ROLE_MATRIX.md](02-architecture/DBIS_NODE_ROLE_MATRIX.md), [02-architecture/DBIS_PHASE2_PROXMOX_SOVEREIGNIZATION_ROADMAP.md](02-architecture/DBIS_PHASE2_PROXMOX_SOVEREIGNIZATION_ROADMAP.md) |
| **03-deployment** | [03-deployment/OPERATIONAL_RUNBOOKS.md](03-deployment/OPERATIONAL_RUNBOOKS.md), [03-deployment/DEPLOYMENT_ORDER_OF_OPERATIONS.md](03-deployment/DEPLOYMENT_ORDER_OF_OPERATIONS.md), **Public sector live checklist:** [03-deployment/PUBLIC_SECTOR_LIVE_DEPLOYMENT_CHECKLIST.md](03-deployment/PUBLIC_SECTOR_LIVE_DEPLOYMENT_CHECKLIST.md), **Proxmox VE ops template:** [03-deployment/PROXMOX_VE_OPERATIONAL_DEPLOYMENT_TEMPLATE.md](03-deployment/PROXMOX_VE_OPERATIONAL_DEPLOYMENT_TEMPLATE.md) · [`config/proxmox-operational-template.json`](config/proxmox-operational-template.json); **DBIS Phase 13:** [03-deployment/PHASE1_DISCOVERY_RUNBOOK.md](03-deployment/PHASE1_DISCOVERY_RUNBOOK.md), [03-deployment/DBIS_PHASE3_E2E_PRODUCTION_SIMULATION_RUNBOOK.md](03-deployment/DBIS_PHASE3_E2E_PRODUCTION_SIMULATION_RUNBOOK.md), [03-deployment/CALIPER_CHAIN138_PERF_HOOK.md](03-deployment/CALIPER_CHAIN138_PERF_HOOK.md), [03-deployment/DBIS_HYPERLEDGER_RUNTIME_STATUS.md](03-deployment/DBIS_HYPERLEDGER_RUNTIME_STATUS.md), [03-deployment/DBIS_PHASES_1_TO_3_PRODUCTION_GATE.md](03-deployment/DBIS_PHASES_1_TO_3_PRODUCTION_GATE.md), **RTGS canonical production checklist and institutional-finance layers:** [03-deployment/DBIS_RTGS_E2E_REQUIREMENTS_MATRIX.md](03-deployment/DBIS_RTGS_E2E_REQUIREMENTS_MATRIX.md), [03-deployment/DBIS_RTGS_FX_TRANSACTION_CATALOG.md](03-deployment/DBIS_RTGS_FX_TRANSACTION_CATALOG.md), [03-deployment/DBIS_RTGS_DEPOSITORY_AND_CUSTODY_OPERATING_MODEL.md](03-deployment/DBIS_RTGS_DEPOSITORY_AND_CUSTODY_OPERATING_MODEL.md), [03-deployment/DBIS_RTGS_FX_AND_LIQUIDITY_OPERATING_MODEL.md](03-deployment/DBIS_RTGS_FX_AND_LIQUIDITY_OPERATING_MODEL.md), [03-deployment/DBIS_RTGS_CONTROL_PLANE_DEPLOYMENT_CHECKLIST.md](03-deployment/DBIS_RTGS_CONTROL_PLANE_DEPLOYMENT_CHECKLIST.md), [03-deployment/DBIS_RTGS_LATER_PHASE_SIDECARS_DEPLOYMENT_CHECKLIST.md](03-deployment/DBIS_RTGS_LATER_PHASE_SIDECARS_DEPLOYMENT_CHECKLIST.md), [03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_INTEGRATION_BLUEPRINT.md](03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_INTEGRATION_BLUEPRINT.md), [03-deployment/DBIS_RTGS_FIRST_SLICE_ARCHITECTURE.md](03-deployment/DBIS_RTGS_FIRST_SLICE_ARCHITECTURE.md), [03-deployment/DBIS_RTGS_FIRST_SLICE_DEPLOYMENT_CHECKLIST.md](03-deployment/DBIS_RTGS_FIRST_SLICE_DEPLOYMENT_CHECKLIST.md), [03-deployment/DBIS_HYBX_SIDECAR_BOUNDARY_MATRIX.md](03-deployment/DBIS_HYBX_SIDECAR_BOUNDARY_MATRIX.md), [03-deployment/DBIS_MOJALOOP_INTEGRATION_STATUS.md](03-deployment/DBIS_MOJALOOP_INTEGRATION_STATUS.md), [03-deployment/DBIS_HYPERLEDGER_IDENTITY_STACK_DECISION.md](03-deployment/DBIS_HYPERLEDGER_IDENTITY_STACK_DECISION.md) |
| **03-deployment** | [03-deployment/OPERATIONAL_RUNBOOKS.md](03-deployment/OPERATIONAL_RUNBOOKS.md), [03-deployment/DEPLOYMENT_ORDER_OF_OPERATIONS.md](03-deployment/DEPLOYMENT_ORDER_OF_OPERATIONS.md), **Public sector live checklist:** [03-deployment/PUBLIC_SECTOR_LIVE_DEPLOYMENT_CHECKLIST.md](03-deployment/PUBLIC_SECTOR_LIVE_DEPLOYMENT_CHECKLIST.md), **Proxmox VE ops template:** [03-deployment/PROXMOX_VE_OPERATIONAL_DEPLOYMENT_TEMPLATE.md](03-deployment/PROXMOX_VE_OPERATIONAL_DEPLOYMENT_TEMPLATE.md) · [`config/proxmox-operational-template.json`](config/proxmox-operational-template.json); **DBIS Phase 13:** [03-deployment/PHASE1_DISCOVERY_RUNBOOK.md](03-deployment/PHASE1_DISCOVERY_RUNBOOK.md), [03-deployment/DBIS_PHASE3_E2E_PRODUCTION_SIMULATION_RUNBOOK.md](03-deployment/DBIS_PHASE3_E2E_PRODUCTION_SIMULATION_RUNBOOK.md), [03-deployment/CALIPER_CHAIN138_PERF_HOOK.md](03-deployment/CALIPER_CHAIN138_PERF_HOOK.md), [03-deployment/DBIS_HYPERLEDGER_RUNTIME_STATUS.md](03-deployment/DBIS_HYPERLEDGER_RUNTIME_STATUS.md), [03-deployment/DBIS_PHASES_1_TO_3_PRODUCTION_GATE.md](03-deployment/DBIS_PHASES_1_TO_3_PRODUCTION_GATE.md), **RTGS canonical production checklist and institutional-finance layers:** [03-deployment/DBIS_RTGS_E2E_REQUIREMENTS_MATRIX.md](03-deployment/DBIS_RTGS_E2E_REQUIREMENTS_MATRIX.md), [03-deployment/DBIS_RTGS_FX_TRANSACTION_CATALOG.md](03-deployment/DBIS_RTGS_FX_TRANSACTION_CATALOG.md), [03-deployment/DBIS_RTGS_DEPOSITORY_AND_CUSTODY_OPERATING_MODEL.md](03-deployment/DBIS_RTGS_DEPOSITORY_AND_CUSTODY_OPERATING_MODEL.md), [03-deployment/DBIS_RTGS_FX_AND_LIQUIDITY_OPERATING_MODEL.md](03-deployment/DBIS_RTGS_FX_AND_LIQUIDITY_OPERATING_MODEL.md), [03-deployment/DBIS_RTGS_CONTROL_PLANE_DEPLOYMENT_CHECKLIST.md](03-deployment/DBIS_RTGS_CONTROL_PLANE_DEPLOYMENT_CHECKLIST.md), [03-deployment/DBIS_RTGS_LATER_PHASE_SIDECARS_DEPLOYMENT_CHECKLIST.md](03-deployment/DBIS_RTGS_LATER_PHASE_SIDECARS_DEPLOYMENT_CHECKLIST.md), [03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_INTEGRATION_BLUEPRINT.md](03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_INTEGRATION_BLUEPRINT.md), [03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_EXECUTABLE_TASK_LIST.md](03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_EXECUTABLE_TASK_LIST.md), [03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_EXECUTION_STATUS_2026-03-29.md](03-deployment/DBIS_OMNL_INDONESIA_BNI_E2E_EXECUTION_STATUS_2026-03-29.md), [03-deployment/DBIS_RTGS_FIRST_SLICE_ARCHITECTURE.md](03-deployment/DBIS_RTGS_FIRST_SLICE_ARCHITECTURE.md), [03-deployment/DBIS_RTGS_FIRST_SLICE_DEPLOYMENT_CHECKLIST.md](03-deployment/DBIS_RTGS_FIRST_SLICE_DEPLOYMENT_CHECKLIST.md), [03-deployment/DBIS_HYBX_SIDECAR_BOUNDARY_MATRIX.md](03-deployment/DBIS_HYBX_SIDECAR_BOUNDARY_MATRIX.md), [03-deployment/DBIS_MOJALOOP_INTEGRATION_STATUS.md](03-deployment/DBIS_MOJALOOP_INTEGRATION_STATUS.md), [03-deployment/DBIS_HYPERLEDGER_IDENTITY_STACK_DECISION.md](03-deployment/DBIS_HYPERLEDGER_IDENTITY_STACK_DECISION.md), [03-deployment/DBIS_IDENTITY_COMPLETION_PACKAGE_RUNBOOK.md](03-deployment/DBIS_IDENTITY_COMPLETION_PACKAGE_RUNBOOK.md) |
| **04-configuration** | [04-configuration/README.md](04-configuration/README.md), [04-configuration/ADDITIONAL_PATHS_AND_EXTENSIONS.md](04-configuration/ADDITIONAL_PATHS_AND_EXTENSIONS.md) (paths, registry, token-mapping, LiFi/Jumper); **Chain 138 wallets:** [04-configuration/CHAIN138_WALLET_CONFIG_VALIDATION.md](04-configuration/CHAIN138_WALLET_CONFIG_VALIDATION.md); **Chain 2138 testnet wallets:** [04-configuration/CHAIN2138_WALLET_CONFIG_VALIDATION.md](04-configuration/CHAIN2138_WALLET_CONFIG_VALIDATION.md); **OMNL Indonesia / HYBX-BATCH-001:** [04-configuration/mifos-omnl-central-bank/HYBX_BATCH_001_OPERATOR_CHECKLIST.md](04-configuration/mifos-omnl-central-bank/HYBX_BATCH_001_OPERATOR_CHECKLIST.md), [04-configuration/mifos-omnl-central-bank/INDONESIA_PACKAGE_4_995_EVIDENCE_STANDARD.md](04-configuration/mifos-omnl-central-bank/INDONESIA_PACKAGE_4_995_EVIDENCE_STANDARD.md) |
| **06-besu** | [06-besu/MASTER_INDEX.md](06-besu/MASTER_INDEX.md) |
| **Testnet (2138)** | [testnet/DEFI_ORACLE_META_TESTNET_2138_RUNBOOK.md](testnet/DEFI_ORACLE_META_TESTNET_2138_RUNBOOK.md), [testnet/TESTNET_DEPLOYMENT.md](testnet/TESTNET_DEPLOYMENT.md) |

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Enable working login on https://sankofa.nexus:
# Enable working login on the client portal hostname (https://portal.sankofa.nexus; NEXTAUTH_URL):
# - Fix Keycloak systemd (JAVA_HOME line; hostname + proxy headers for NPM).
# - 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).
@@ -32,7 +32,7 @@ cat > "$ENV_TMP" <<EOF
NEXT_PUBLIC_GRAPHQL_ENDPOINT=http://192.168.11.50:4000/graphql
NEXT_PUBLIC_GRAPHQL_WS_ENDPOINT=ws://192.168.11.50:4000/graphql-ws
NEXTAUTH_URL=https://sankofa.nexus
NEXTAUTH_URL=https://portal.sankofa.nexus
NEXTAUTH_SECRET=${NEXTAUTH_SEC}
KEYCLOAK_URL=https://keycloak.sankofa.nexus
KEYCLOAK_REALM=master
@@ -85,7 +85,7 @@ bash "${SCRIPT_DIR}/sync-sankofa-portal-7801.sh"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Sign in at https://sankofa.nexus"
echo "✅ Sign in at https://portal.sankofa.nexus (NEXTAUTH_URL)"
echo " Email: ${LOCAL_EMAIL}"
echo " Password: ${GEN_PASS}"
echo ""

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Ensure Keycloak OIDC client has redirect URIs and web origins for portal/admin hostnames.
# Uses Admin REST API (password grant on master realm). Set secrets in .env (not committed).
#
# Required env: KEYCLOAK_ADMIN_PASSWORD
# Optional: KEYCLOAK_URL (default https://keycloak.sankofa.nexus), KEYCLOAK_ADMIN (default admin),
# KEYCLOAK_REALM (default master), KEYCLOAK_CLIENT_ID (default sankofa-portal)
#
# Usage: from repo root with .env loaded:
# ./scripts/deployment/keycloak-sankofa-ensure-client-redirects.sh
# ./scripts/deployment/keycloak-sankofa-ensure-client-redirects.sh --dry-run
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
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
KEYCLOAK_URL="${KEYCLOAK_URL:-https://keycloak.sankofa.nexus}"
REALM="${KEYCLOAK_REALM:-master}"
CLIENT_ID="${KEYCLOAK_CLIENT_ID:-sankofa-portal}"
ADMIN_USER="${KEYCLOAK_ADMIN:-admin}"
ADMIN_PASS="${KEYCLOAK_ADMIN_PASSWORD:-}"
DRY=0
[[ "${1:-}" == "--dry-run" ]] && DRY=1
if [ -z "$ADMIN_PASS" ]; then
echo "KEYCLOAK_ADMIN_PASSWORD is not set. Add it to .env (or export) and re-run." >&2
echo "Without it, update the client manually: Valid redirect URIs + Web origins for portal/admin." >&2
exit 1
fi
TOKEN_JSON=$(curl -sS -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=${ADMIN_USER}" \
-d "password=${ADMIN_PASS}")
TOKEN=$(echo "$TOKEN_JSON" | jq -r '.access_token // empty')
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "Failed to obtain admin token (check URL, user, password)." >&2
echo "$TOKEN_JSON" | jq -r '.error_description // .error // .' >&2
exit 1
fi
CLIENT_LIST=$(curl -sS -G "${KEYCLOAK_URL}/admin/realms/${REALM}/clients" \
-H "Authorization: Bearer ${TOKEN}" \
--data-urlencode "clientId=${CLIENT_ID}")
INTERNAL_ID=$(echo "$CLIENT_LIST" | jq -r '.[0].id // empty')
if [ -z "$INTERNAL_ID" ] || [ "$INTERNAL_ID" = "null" ]; then
echo "No client with clientId=${CLIENT_ID} in realm ${REALM}." >&2
exit 1
fi
FULL=$(curl -sS "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${INTERNAL_ID}" \
-H "Authorization: Bearer ${TOKEN}")
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"
]'
MERGED=$(echo "$FULL" | jq --argjson dr "$DESIRED_REDIRECTS" --argjson wo "$DESIRED_ORIGINS" '
.redirectUris = ((.redirectUris // []) + $dr | unique) |
.webOrigins = ((.webOrigins // []) + $wo | unique)
')
if [ "$DRY" = 1 ]; then
echo "[dry-run] Would set client ${CLIENT_ID} (${INTERNAL_ID}) to:"
echo "$MERGED" | jq '{clientId, redirectUris, webOrigins}'
exit 0
fi
HTTP_CODE=$(curl -sS -o /tmp/kc_put_body.txt -w "%{http_code}" -X PUT \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${INTERNAL_ID}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$MERGED")
if [[ "$HTTP_CODE" != "204" && "$HTTP_CODE" != "200" ]]; then
echo "PUT client failed HTTP ${HTTP_CODE}" >&2
cat /tmp/kc_put_body.txt >&2 || true
exit 1
fi
rm -f /tmp/kc_put_body.txt
echo "Updated Keycloak client ${CLIENT_ID}: redirect URIs and web origins merged (portal + admin)."

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Create LXC 7806 (sankofa-public-web) for corporate Next.js at repo root → sankofa.nexus via IP_SANKOFA_PUBLIC_WEB.
# Installs Node 20 + pnpm and systemd unit; then run sync-sankofa-public-web-to-ct.sh and NPM fleet update.
#
# Usage (from repo root, SSH to r630-01):
# bash scripts/deployment/provision-sankofa-public-web-lxc-7806.sh [--dry-run]
#
# Env: PROXMOX_HOST, SANKOFA_PUBLIC_WEB_VMID (7806), IP_SANKOFA_PUBLIC_WEB_CT (default 192.168.11.63)
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}}"
VMID="${SANKOFA_PUBLIC_WEB_VMID:-7806}"
IP_CT="${IP_SANKOFA_PUBLIC_WEB_CT:-192.168.11.63}"
HOSTNAME_CT="${SANKOFA_PUBLIC_WEB_HOSTNAME:-sankofa-public-web}"
TEMPLATE="${TEMPLATE:-local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst}"
STORAGE="${STORAGE:-local-lvm}"
NETWORK="${NETWORK:-vmbr0}"
GATEWAY="${NETWORK_GATEWAY:-192.168.11.1}"
SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new"
SERVICE_FILE="${PROJECT_ROOT}/config/systemd/sankofa-public-web.service"
DRY_RUN=false
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
echo "=== Provision Sankofa public web LXC ${VMID} (${IP_CT}) ==="
echo "Proxmox: ${PROXMOX_HOST}"
if [[ ! -f "$SERVICE_FILE" ]]; then
echo "ERROR: Missing $SERVICE_FILE"
exit 1
fi
if $DRY_RUN; then
echo "[DRY-RUN] Would create CT ${VMID} if missing, install Node/pnpm, install systemd unit"
exit 0
fi
if ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct list 2>/dev/null | grep -q '^${VMID} '"; then
echo "CT ${VMID} already exists — skipping pct create"
else
echo "Creating CT ${VMID} (${HOSTNAME_CT}) @ ${IP_CT}/24..."
ssh $SSH_OPTS "root@${PROXMOX_HOST}" bash -s <<EOF
set -euo pipefail
pct create ${VMID} ${TEMPLATE} \
--hostname ${HOSTNAME_CT} \
--memory 6144 \
--cores 2 \
--rootfs ${STORAGE}:32 \
--net0 name=eth0,bridge=${NETWORK},ip=${IP_CT}/24,gw=${GATEWAY} \
--nameserver ${DNS_PRIMARY:-1.1.1.1} \
--description 'Sankofa corporate public web (Next.js root) — sankofa.nexus via NPM IP_SANKOFA_PUBLIC_WEB' \
--start 1 \
--onboot 1 \
--unprivileged 0
EOF
echo "Waiting for CT to boot..."
sleep 20
fi
ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct status ${VMID}" | grep -q running || { echo "ERROR: CT ${VMID} not running"; exit 1; }
echo "Installing Node 20 + pnpm inside CT ${VMID}..."
ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct exec ${VMID} -- bash -lc '
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq ca-certificates curl gnupg
if ! command -v node >/dev/null 2>&1 || ! node -v | grep -q \"^v20\"; then
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y -qq nodejs
fi
command -v pnpm >/dev/null 2>&1 || npm install -g pnpm
mkdir -p /opt/sankofa-public-web
'"
echo "Installing systemd unit..."
scp $SSH_OPTS "$SERVICE_FILE" "root@${PROXMOX_HOST}:/tmp/sankofa-public-web.service"
ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct push ${VMID} /tmp/sankofa-public-web.service /etc/systemd/system/sankofa-public-web.service && rm -f /tmp/sankofa-public-web.service"
ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct exec ${VMID} -- systemctl daemon-reload"
ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct exec ${VMID} -- systemctl enable sankofa-public-web"
echo "✅ CT ${VMID} ready. Next:"
echo " SANKOFA_PUBLIC_WEB_VMID=${VMID} bash scripts/deployment/sync-sankofa-public-web-to-ct.sh"
echo " Then set IP_SANKOFA_PUBLIC_WEB=${IP_CT} and run scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh"

View File

@@ -14,7 +14,7 @@ PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
VMID="${SANKOFA_PORTAL_VMID:-7801}"
CT_APP_DIR="${SANKOFA_PORTAL_CT_DIR:-/opt/sankofa-portal}"
SERVICE_NAME="${SANKOFA_PORTAL_SERVICE:-sankofa-portal}"
NEXTAUTH_PUBLIC_URL="${SANKOFA_PORTAL_NEXTAUTH_URL:-https://sankofa.nexus}"
NEXTAUTH_PUBLIC_URL="${SANKOFA_PORTAL_NEXTAUTH_URL:-https://portal.sankofa.nexus}"
SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new"
ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct exec ${VMID} -- bash -s" <<EOF

View File

@@ -6,7 +6,7 @@
# ./scripts/deployment/sync-sankofa-portal-7801.sh [--dry-run]
# Env:
# PROXMOX_HOST (default 192.168.11.11), SANKOFA_PORTAL_VMID (7801), SANKOFA_PORTAL_SRC, IP_SANKOFA_PORTAL (for post-check only)
# SANKOFA_PORTAL_NEXTAUTH_URL (default https://sankofa.nexus) — applied on CT after build
# SANKOFA_PORTAL_NEXTAUTH_URL (default https://portal.sankofa.nexus) — applied on CT after build
#
# See: docs/03-deployment/PUBLIC_SECTOR_LIVE_DEPLOYMENT_CHECKLIST.md (Phoenix CT 7801)
@@ -97,14 +97,14 @@ REMOTE_EOF
echo ""
echo "🔐 Ensuring NextAuth URL/secret on CT (see sankofa-portal-ensure-nextauth-on-ct.sh)…"
SANKOFA_PORTAL_NEXTAUTH_URL="${SANKOFA_PORTAL_NEXTAUTH_URL:-https://sankofa.nexus}"
SANKOFA_PORTAL_NEXTAUTH_URL="${SANKOFA_PORTAL_NEXTAUTH_URL:-https://portal.sankofa.nexus}"
export SANKOFA_PORTAL_VMID SANKOFA_PORTAL_CT_DIR SANKOFA_PORTAL_SERVICE SANKOFA_PORTAL_NEXTAUTH_URL PROXMOX_HOST
bash "${SCRIPT_DIR}/sankofa-portal-ensure-nextauth-on-ct.sh"
echo ""
echo "✅ Done. Verify:"
echo " curl -sS http://${IP_SANKOFA_PORTAL:-192.168.11.51}:3000/ | head -c 120"
echo " curl -sSI https://sankofa.nexus/api/auth/signin | head -n 15"
echo " https://sankofa.nexus/ (via NPM)"
echo " curl -sSI https://portal.sankofa.nexus/api/auth/signin | head -n 15"
echo " https://portal.sankofa.nexus/ (via NPM; corporate apex is sankofa.nexus → IP_SANKOFA_PUBLIC_WEB)"
echo ""
echo "Override public auth URL: SANKOFA_PORTAL_NEXTAUTH_URL=https://portal.sankofa.nexus $0"
echo "Legacy apex auth URL only if needed: SANKOFA_PORTAL_NEXTAUTH_URL=https://sankofa.nexus $0"

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# Sync Sankofa repo-root Next.js app (corporate / marketing site) to a dedicated LXC for apex sankofa.nexus.
# Does not run NextAuth portal setup — use sync-sankofa-portal-7801.sh for client SSO on portal.sankofa.nexus.
#
# Prerequisites: SSH root@PROXMOX_HOST; sibling repo at ../Sankofa (root package.json + src/app + public/).
# On the CT: install systemd unit config/systemd/sankofa-public-web.service → /etc/systemd/system/ and enable.
#
# Usage:
# ./scripts/deployment/sync-sankofa-public-web-to-ct.sh [--dry-run]
# Env:
# PROXMOX_HOST, SANKOFA_PUBLIC_WEB_VMID (default 7806), SANKOFA_PUBLIC_WEB_SRC, SANKOFA_PUBLIC_WEB_CT_DIR, SANKOFA_PUBLIC_WEB_SERVICE
#
# After first deploy: set IP_SANKOFA_PUBLIC_WEB + SANKOFA_PUBLIC_WEB_PORT in config/ip-addresses.conf (or .env) and run
# scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.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}/config/ip-addresses.conf" 2>/dev/null || true
PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
VMID="${SANKOFA_PUBLIC_WEB_VMID:-7806}"
CT_APP_DIR="${SANKOFA_PUBLIC_WEB_CT_DIR:-/opt/sankofa-public-web}"
SERVICE_NAME="${SANKOFA_PUBLIC_WEB_SERVICE:-sankofa-public-web}"
SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new"
DEFAULT_SRC="${PROJECT_ROOT}/../Sankofa"
if [[ -d "$DEFAULT_SRC" && -f "$DEFAULT_SRC/package.json" ]]; then
SANKOFA_PUBLIC_WEB_SRC="${SANKOFA_PUBLIC_WEB_SRC:-$DEFAULT_SRC}"
else
SANKOFA_PUBLIC_WEB_SRC="${SANKOFA_PUBLIC_WEB_SRC:-}"
fi
DRY_RUN=false
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
echo "=== Sync Sankofa public (repo root) → CT ${VMID} (${CT_APP_DIR}) ==="
echo "Proxmox: ${PROXMOX_HOST}"
echo "Source: ${SANKOFA_PUBLIC_WEB_SRC:-<unset>}"
echo ""
if [[ -z "$SANKOFA_PUBLIC_WEB_SRC" || ! -d "$SANKOFA_PUBLIC_WEB_SRC" ]]; then
echo "ERROR: Set SANKOFA_PUBLIC_WEB_SRC to the Sankofa monorepo root (parent of portal/)."
echo "Example: SANKOFA_PUBLIC_WEB_SRC=/path/to/Sankofa $0"
exit 1
fi
if ! command -v tar >/dev/null; then
echo "ERROR: tar required"
exit 1
fi
TMP_TGZ="${TMPDIR:-/tmp}/sankofa-public-web-sync-$$.tgz"
REMOTE_TGZ="/tmp/sankofa-public-web-sync-$$.tgz"
CT_TGZ="/tmp/sankofa-public-web-sync.tgz"
cleanup() { rm -f "$TMP_TGZ"; }
trap cleanup EXIT
if $DRY_RUN; then
echo "[DRY-RUN] tar (exclude node_modules,.next,.git) → $TMP_TGZ"
echo "[DRY-RUN] scp → root@${PROXMOX_HOST}:${REMOTE_TGZ}"
echo "[DRY-RUN] pct push ${VMID} … && systemctl stop ${SERVICE_NAME}"
echo "[DRY-RUN] pnpm install && pnpm build && systemctl start ${SERVICE_NAME}"
exit 0
fi
echo "📦 Archiving Sankofa repo root (excluding node_modules, .next, .git, .env / .env.local)…"
tar czf "$TMP_TGZ" \
--exclude=node_modules \
--exclude=.next \
--exclude=portal/node_modules \
--exclude=portal/.next \
--exclude=.git \
--exclude=.env.local \
--exclude=.env \
-C "$SANKOFA_PUBLIC_WEB_SRC" .
echo "📤 Copy to Proxmox host…"
scp $SSH_OPTS "$TMP_TGZ" "root@${PROXMOX_HOST}:${REMOTE_TGZ}"
echo "📥 Push into CT ${VMID} and build…"
ssh $SSH_OPTS "root@${PROXMOX_HOST}" bash -s <<REMOTE_EOF
set -euo pipefail
pct push ${VMID} ${REMOTE_TGZ} ${CT_TGZ}
rm -f ${REMOTE_TGZ}
pct exec ${VMID} -- systemctl stop ${SERVICE_NAME} || true
pct exec ${VMID} -- bash -lc 'set -euo pipefail
mkdir -p ${CT_APP_DIR}
cd ${CT_APP_DIR}
tar xzf ${CT_TGZ}
rm -f ${CT_TGZ}
command -v pnpm >/dev/null || { echo "ERROR: pnpm missing in CT"; exit 1; }
pnpm install
pnpm build
'
pct exec ${VMID} -- systemctl start ${SERVICE_NAME}
pct exec ${VMID} -- systemctl is-active ${SERVICE_NAME}
REMOTE_EOF
echo ""
echo "✅ Done. Point NPM apex with IP_SANKOFA_PUBLIC_WEB / SANKOFA_PUBLIC_WEB_PORT, then:"
echo " bash scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh"
echo " curl -sS http://${IP_SANKOFA_PUBLIC_WEB:-<CT_IP>}:${SANKOFA_PUBLIC_WEB_PORT:-3000}/ | head -c 120"

View File

@@ -284,24 +284,34 @@ create_proxy_host() {
return 0
}
# Configure all 19 domains
echo "🚀 Starting domain configuration (19 domains)..."
# Configure core domains (count varies; sankofa zone includes portal/admin/dash when dash IP set)
echo "🚀 Starting domain configuration..."
echo ""
SUCCESS=0
FAILED=0
# sankofa.nexus (5 domains) — portal :3000 / Phoenix API :4000 (not Blockscout; explorer is IP_BLOCKSCOUT:80)
# sankofa.nexus zone — corporate apex (IP_SANKOFA_PUBLIC_WEB), Phoenix API :4000, client SSO (portal/admin), optional dash (IP_SANKOFA_DASH). Not Blockscout.
IP_SANKOFA_PORTAL="${IP_SANKOFA_PORTAL:-${IP_SERVICE_51:-192.168.11.51}}"
IP_SANKOFA_PHOENIX_API="${IP_SANKOFA_PHOENIX_API:-${IP_SERVICE_50:-192.168.11.50}}"
SANKOFA_PORTAL_PORT="${SANKOFA_PORTAL_PORT:-3000}"
SANKOFA_PHOENIX_API_PORT="${SANKOFA_PHOENIX_API_PORT:-4000}"
IP_SANKOFA_PUBLIC_WEB="${IP_SANKOFA_PUBLIC_WEB:-${IP_SANKOFA_PORTAL}}"
SANKOFA_PUBLIC_WEB_PORT="${SANKOFA_PUBLIC_WEB_PORT:-${SANKOFA_PORTAL_PORT}}"
IP_SANKOFA_CLIENT_SSO="${IP_SANKOFA_CLIENT_SSO:-${IP_SANKOFA_PORTAL}}"
SANKOFA_CLIENT_SSO_PORT="${SANKOFA_CLIENT_SSO_PORT:-${SANKOFA_PORTAL_PORT}}"
IP_ORDER_HAPROXY="${IP_ORDER_HAPROXY:-192.168.11.39}"
create_proxy_host "sankofa.nexus" "http" "${IP_SANKOFA_PORTAL}" "${SANKOFA_PORTAL_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "www.sankofa.nexus" "http" "${IP_SANKOFA_PORTAL}" "${SANKOFA_PORTAL_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "sankofa.nexus" "http" "${IP_SANKOFA_PUBLIC_WEB}" "${SANKOFA_PUBLIC_WEB_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "www.sankofa.nexus" "http" "${IP_SANKOFA_PUBLIC_WEB}" "${SANKOFA_PUBLIC_WEB_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "phoenix.sankofa.nexus" "http" "${IP_SANKOFA_PHOENIX_API}" "${SANKOFA_PHOENIX_API_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "www.phoenix.sankofa.nexus" "http" "${IP_SANKOFA_PHOENIX_API}" "${SANKOFA_PHOENIX_API_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "the-order.sankofa.nexus" "http" "${IP_ORDER_HAPROXY}" "80" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "portal.sankofa.nexus" "http" "${IP_SANKOFA_CLIENT_SSO}" "${SANKOFA_CLIENT_SSO_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "admin.sankofa.nexus" "http" "${IP_SANKOFA_CLIENT_SSO}" "${SANKOFA_CLIENT_SSO_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
if [[ -n "${IP_SANKOFA_DASH:-}" ]]; then
SANKOFA_DASH_PORT="${SANKOFA_DASH_PORT:-3000}"
create_proxy_host "dash.sankofa.nexus" "http" "${IP_SANKOFA_DASH}" "${SANKOFA_DASH_PORT}" "false" && ((SUCCESS++)) || ((FAILED++))
fi
# d-bis.org (9 domains)
create_proxy_host "explorer.d-bis.org" "http" "${IP_BLOCKSCOUT:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-192.168.11.14}}}}}0}" "80" "false" && ((SUCCESS++)) || ((FAILED++))

View File

@@ -373,15 +373,19 @@ update_proxy_host "dbis.xom-dev.phoenix.sankofa.nexus" "http://${IP_GOV_PORTALS_
update_proxy_host "iccc.xom-dev.phoenix.sankofa.nexus" "http://${IP_GOV_PORTALS_DEV}:3002" false && updated_count=$((updated_count + 1)) || { add_proxy_host "iccc.xom-dev.phoenix.sankofa.nexus" "${IP_GOV_PORTALS_DEV}" 3002 false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "omnl.xom-dev.phoenix.sankofa.nexus" "http://${IP_GOV_PORTALS_DEV}:3003" false && updated_count=$((updated_count + 1)) || { add_proxy_host "omnl.xom-dev.phoenix.sankofa.nexus" "${IP_GOV_PORTALS_DEV}" 3003 false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "xom.xom-dev.phoenix.sankofa.nexus" "http://${IP_GOV_PORTALS_DEV}:3004" false && updated_count=$((updated_count + 1)) || { add_proxy_host "xom.xom-dev.phoenix.sankofa.nexus" "${IP_GOV_PORTALS_DEV}" 3004 false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# Sankofa portal (Next.js CT 7801) and Phoenix API (Fastify CT 7800) — not Blockscout / SolaceScanScout (that is explorer.d-bis.org / IP_BLOCKSCOUT:80)
# Public web intent: sankofa.nexus = Sankofa Sovereign Technologies; phoenix.sankofa.nexus = Phoenix Cloud Services (division). Client SSO: admin / portal + keycloak IdP. Operator: dash (IP+MFA). See docs/02-architecture/EXPECTED_WEB_CONTENT.md.
# Sankofa / Phoenix — not Blockscout (explorer.d-bis.org / IP_BLOCKSCOUT:80).
# Intent: sankofa.nexus = corporate marketing (IP_SANKOFA_PUBLIC_WEB); portal.sankofa.nexus + admin = client SSO (IP_SANKOFA_CLIENT_SSO); phoenix = division + API. dash = operator (set IP_SANKOFA_DASH to manage). See docs/02-architecture/EXPECTED_WEB_CONTENT.md.
# www.sankofa.nexus → 301 https://sankofa.nexus$request_uri; www.phoenix → phoenix; www.the-order → the-order (NPM advanced_config).
IP_SANKOFA_PORTAL="${IP_SANKOFA_PORTAL:-${IP_SERVICE_51:-192.168.11.51}}"
IP_SANKOFA_PHOENIX_API="${IP_SANKOFA_PHOENIX_API:-${IP_SERVICE_50:-192.168.11.50}}"
SANKOFA_PORTAL_PORT="${SANKOFA_PORTAL_PORT:-3000}"
SANKOFA_PHOENIX_API_PORT="${SANKOFA_PHOENIX_API_PORT:-4000}"
update_proxy_host "sankofa.nexus" "http://${IP_SANKOFA_PORTAL}:${SANKOFA_PORTAL_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "sankofa.nexus" "${IP_SANKOFA_PORTAL}" "${SANKOFA_PORTAL_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "www.sankofa.nexus" "http://${IP_SANKOFA_PORTAL}:${SANKOFA_PORTAL_PORT}" false false "https://sankofa.nexus" && updated_count=$((updated_count + 1)) || { add_proxy_host "www.sankofa.nexus" "${IP_SANKOFA_PORTAL}" "${SANKOFA_PORTAL_PORT}" false false "https://sankofa.nexus" && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
IP_SANKOFA_PUBLIC_WEB="${IP_SANKOFA_PUBLIC_WEB:-${IP_SANKOFA_PORTAL}}"
SANKOFA_PUBLIC_WEB_PORT="${SANKOFA_PUBLIC_WEB_PORT:-${SANKOFA_PORTAL_PORT}}"
IP_SANKOFA_CLIENT_SSO="${IP_SANKOFA_CLIENT_SSO:-${IP_SANKOFA_PORTAL}}"
SANKOFA_CLIENT_SSO_PORT="${SANKOFA_CLIENT_SSO_PORT:-${SANKOFA_PORTAL_PORT}}"
update_proxy_host "sankofa.nexus" "http://${IP_SANKOFA_PUBLIC_WEB}:${SANKOFA_PUBLIC_WEB_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "sankofa.nexus" "${IP_SANKOFA_PUBLIC_WEB}" "${SANKOFA_PUBLIC_WEB_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "www.sankofa.nexus" "http://${IP_SANKOFA_PUBLIC_WEB}:${SANKOFA_PUBLIC_WEB_PORT}" false false "https://sankofa.nexus" && updated_count=$((updated_count + 1)) || { add_proxy_host "www.sankofa.nexus" "${IP_SANKOFA_PUBLIC_WEB}" "${SANKOFA_PUBLIC_WEB_PORT}" false false "https://sankofa.nexus" && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "phoenix.sankofa.nexus" "http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_PHOENIX_API_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "phoenix.sankofa.nexus" "${IP_SANKOFA_PHOENIX_API}" "${SANKOFA_PHOENIX_API_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "www.phoenix.sankofa.nexus" "http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_PHOENIX_API_PORT}" false false "https://phoenix.sankofa.nexus" && updated_count=$((updated_count + 1)) || { add_proxy_host "www.phoenix.sankofa.nexus" "${IP_SANKOFA_PHOENIX_API}" "${SANKOFA_PHOENIX_API_PORT}" false false "https://phoenix.sankofa.nexus" && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# Keycloak (CT 7802) — portal SSO; NPM must forward X-Forwarded-* (Keycloak KC_PROXY_HEADERS=xforwarded on upstream)
@@ -403,6 +407,16 @@ IP_SANKOFA_STUDIO="${IP_SANKOFA_STUDIO:-192.168.11.72}"
SANKOFA_STUDIO_PORT="${SANKOFA_STUDIO_PORT:-8000}"
# block_exploits false — studio UI/API may POST; align with portal policy (avoid spurious 405 from NPM WAF)
update_proxy_host "studio.sankofa.nexus" "http://${IP_SANKOFA_STUDIO}:${SANKOFA_STUDIO_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "studio.sankofa.nexus" "${IP_SANKOFA_STUDIO}" "${SANKOFA_STUDIO_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# Client SSO hostnames (Next.js portal stack on 7801 typical). NEXTAUTH_URL / Keycloak redirects: https://portal.sankofa.nexus (and https://admin.sankofa.nexus).
update_proxy_host "portal.sankofa.nexus" "http://${IP_SANKOFA_CLIENT_SSO}:${SANKOFA_CLIENT_SSO_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "portal.sankofa.nexus" "${IP_SANKOFA_CLIENT_SSO}" "${SANKOFA_CLIENT_SSO_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "admin.sankofa.nexus" "http://${IP_SANKOFA_CLIENT_SSO}:${SANKOFA_CLIENT_SSO_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "admin.sankofa.nexus" "${IP_SANKOFA_CLIENT_SSO}" "${SANKOFA_CLIENT_SSO_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# Operator systems dashboard — only when IP_SANKOFA_DASH is set (see config/ip-addresses.conf).
if [[ -n "${IP_SANKOFA_DASH:-}" ]]; then
SANKOFA_DASH_PORT="${SANKOFA_DASH_PORT:-3000}"
update_proxy_host "dash.sankofa.nexus" "http://${IP_SANKOFA_DASH}:${SANKOFA_DASH_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "dash.sankofa.nexus" "${IP_SANKOFA_DASH}" "${SANKOFA_DASH_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
else
echo " Skipping dash.sankofa.nexus (set IP_SANKOFA_DASH and SANKOFA_DASH_PORT to provision upstream)."
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -330,6 +330,10 @@ main() {
SANKOFA_RECORDS=(
"@" # sankofa.nexus
"www" # www.sankofa.nexus
"portal" # portal.sankofa.nexus (client SSO)
"admin" # admin.sankofa.nexus (client access admin)
"keycloak" # keycloak.sankofa.nexus (IdP)
"studio" # studio.sankofa.nexus (FusionAI Creator)
"phoenix" # phoenix.sankofa.nexus
"www.phoenix" # www.phoenix.sankofa.nexus
"the-order" # the-order.sankofa.nexus

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
# Update Sankofa NPMplus proxy hosts (portal + Phoenix API + the-order) via API by numeric host ID.
# Prefer for production: scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh (domain-based, full fleet).
# Update Sankofa NPMplus proxy hosts (apex + Phoenix API + the-order) via API by numeric host ID.
# Prefer for production: scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh (domain-based; includes portal.sankofa.nexus, admin.sankofa.nexus, dash when IP_SANKOFA_DASH set).
# NPM proxy host IDs below match backup backup-20260325_183932 (36, 7, 59); override with SANKOFA_NPM_ID_* if your DB differs.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -27,6 +27,8 @@ IP_SANKOFA_PORTAL="${IP_SANKOFA_PORTAL:-${IP_SERVICE_51:-192.168.11.51}}"
IP_SANKOFA_PHOENIX_API="${IP_SANKOFA_PHOENIX_API:-${IP_SERVICE_50:-192.168.11.50}}"
SANKOFA_PORTAL_PORT="${SANKOFA_PORTAL_PORT:-3000}"
SANKOFA_PHOENIX_API_PORT="${SANKOFA_PHOENIX_API_PORT:-4000}"
IP_SANKOFA_PUBLIC_WEB="${IP_SANKOFA_PUBLIC_WEB:-$IP_SANKOFA_PORTAL}"
SANKOFA_PUBLIC_WEB_PORT="${SANKOFA_PUBLIC_WEB_PORT:-$SANKOFA_PORTAL_PORT}"
IP_ORDER_HAPROXY="${IP_ORDER_HAPROXY:-192.168.11.39}"
THE_ORDER_UPSTREAM_IP="${THE_ORDER_UPSTREAM_IP:-${IP_ORDER_HAPROXY}}"
THE_ORDER_UPSTREAM_PORT="${THE_ORDER_UPSTREAM_PORT:-80}"
@@ -41,8 +43,8 @@ SANKOFA_NPM_ID_WWW_THE_ORDER="${SANKOFA_NPM_ID_WWW_THE_ORDER:-59}"
# Optional 4th field: canonical HTTPS apex — NPM advanced_config 301 (www → apex). Matches update-npmplus-proxy-hosts-api.sh.
declare -A PROXY_HOSTS=(
["$SANKOFA_NPM_ID_ROOT"]="sankofa.nexus|${IP_SANKOFA_PORTAL}|${SANKOFA_PORTAL_PORT}|"
["$SANKOFA_NPM_ID_WWW"]="www.sankofa.nexus|${IP_SANKOFA_PORTAL}|${SANKOFA_PORTAL_PORT}|https://sankofa.nexus"
["$SANKOFA_NPM_ID_ROOT"]="sankofa.nexus|${IP_SANKOFA_PUBLIC_WEB}|${SANKOFA_PUBLIC_WEB_PORT}|"
["$SANKOFA_NPM_ID_WWW"]="www.sankofa.nexus|${IP_SANKOFA_PUBLIC_WEB}|${SANKOFA_PUBLIC_WEB_PORT}|https://sankofa.nexus"
["$SANKOFA_NPM_ID_PHOENIX"]="phoenix.sankofa.nexus|${IP_SANKOFA_PHOENIX_API}|${SANKOFA_PHOENIX_API_PORT}|"
["$SANKOFA_NPM_ID_WWW_PHOENIX"]="www.phoenix.sankofa.nexus|${IP_SANKOFA_PHOENIX_API}|${SANKOFA_PHOENIX_API_PORT}|https://phoenix.sankofa.nexus"
["$SANKOFA_NPM_ID_THE_ORDER"]="the-order.sankofa.nexus|${THE_ORDER_UPSTREAM_IP}|${THE_ORDER_UPSTREAM_PORT}|"