From 377369a5be77b830f77d2988b36bcaa65bb399b7 Mon Sep 17 00:00:00 2001 From: defiQUG Date: Tue, 12 May 2026 15:55:50 -0700 Subject: [PATCH] feat(gitea-phoenix): gov runtime, deploy/template parity, workflow dedupe docs - Add gov-portals-runtime.v1.json + schema; jq gate in validate-config-files - Python: parity-deploy-targets, parity-operational-template (IP strict, hostname WARN), parity-gov-portals-runtime; validate-vm-routing-parity.sh wrapper - check-gov-portal-workflow-canonical-strings.sh for monorepo Pattern A - PORTAL_WORKFLOW_PARITY.md; template headers; repos README; operator checklist secrets - report-gitea-cd-parity runs full VM routing parity; task doc marked complete - GOV_PORTALS_XOM_DEV + GITEA_GOV + MASTER_INDEX + matrix doc cross-links Co-authored-by: Cursor --- config/gitea-phoenix/README.md | 27 +++- .../gitea-phoenix/gov-portals-runtime.v1.json | 42 ++++++ .../gov-portals-runtime.v1.schema.json | 33 ++++ .../PORTAL_WORKFLOW_PARITY.md | 44 ++++++ .../gitea-workflow-templates/repos/README.md | 4 +- .../repos/dbis-portal-ci-and-live.yml | 1 + .../repos/dbis-portal-live.yml | 1 + .../repos/iccc-portal-ci-and-live.yml | 1 + .../repos/omnl-portal-ci-and-live.yml | 1 + .../repos/xom-portal-ci-and-live.yml | 1 + docs/00-meta/GITEA_CD_OPERATOR_CHECKLIST.md | 15 +- .../GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md | 2 +- ..._NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md | 50 +++---- .../GITEA_REPO_VM_CD_CI_MATRIX.md | 2 +- .../GOV_PORTALS_XOM_DEV_DEPLOYMENT.md | 2 + docs/MASTER_INDEX.md | 2 +- .../lib/non_blockchain_vm_routing_matrix.py | 141 ++++++++++++++++++ scripts/validation/validate-config-files.sh | 31 +++- ...k-gov-portal-workflow-canonical-strings.sh | 31 ++++ scripts/verify/report-gitea-cd-parity.sh | 7 + ...lidate-non-blockchain-vm-routing-matrix.sh | 17 +-- scripts/verify/validate-vm-routing-parity.sh | 18 +++ 22 files changed, 414 insertions(+), 59 deletions(-) create mode 100644 config/gitea-phoenix/gov-portals-runtime.v1.json create mode 100644 config/gitea-phoenix/gov-portals-runtime.v1.schema.json create mode 100644 config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md create mode 100755 scripts/verify/check-gov-portal-workflow-canonical-strings.sh create mode 100755 scripts/verify/validate-vm-routing-parity.sh diff --git a/config/gitea-phoenix/README.md b/config/gitea-phoenix/README.md index fcc9c5de..da7f13e0 100644 --- a/config/gitea-phoenix/README.md +++ b/config/gitea-phoenix/README.md @@ -7,6 +7,8 @@ Machine-readable coverage of **cluster guests that are not Besu-fleet nodes** (s | `besu-vmid-exclusions.v1.json` | Hostname rules marking Besu validators/sentries/RPC (excluded from matrix closure). | | `non-blockchain-vm-routing-matrix.v1.json` | One row per in-scope **running** guest from the last committed `reports/status/live_inventory.json` snapshot. | | `non-blockchain-vm-routing-matrix.v1.schema.json` | JSON Schema for the matrix file. | +| `gov-portals-runtime.v1.json` | CT **7804** xom-dev: ports **3001–3004**, Gitea repos, Phoenix **`target`** names, `pnpm` filters (kept in sync with deploy-targets + monorepo CI). | +| `gov-portals-runtime.v1.schema.json` | JSON Schema for the runtime file. | ## Regenerate after inventory export @@ -19,15 +21,28 @@ python3 scripts/lib/non_blockchain_vm_routing_matrix.py generate \ --out config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json ``` -Then hand-fill `gitea_repos`, `deploy_target`, `workflow_glob`, and `health_url` for Phoenix-backed services; use `allowed_missing` only with an explicit reason for intentional gaps. +Then hand-fill `gitea_repos`, `deploy_target`, `workflow_glob`, and `health_url` for Phoenix-backed services; use `allowed_missing` only with an explicit reason for intentional gaps. When **7804** portal list changes, update **`gov-portals-runtime.v1.json`** in the same change set. -## Validate +## Validate (inventory + parity gates) ```bash -python3 scripts/lib/non_blockchain_vm_routing_matrix.py validate \ - --inventory reports/status/live_inventory.json \ - --exclusions config/gitea-phoenix/besu-vmid-exclusions.v1.json \ - --matrix config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json +bash scripts/verify/validate-vm-routing-parity.sh +``` + +Subcommands (see `scripts/lib/non_blockchain_vm_routing_matrix.py`): + +- `validate` — inventory closure vs matrix +- `parity-deploy-targets` — each Phoenix deploy target’s `repo` appears on the matrix row for its VMID (health URL match for single-repo rows) +- `parity-operational-template` — IPv4 alignment vs `config/proxmox-operational-template.json` (hostname drift warns only; NPMplus **10233** dual-homed `.166`/`.167` documented) +- `parity-gov-portals-runtime` — matrix **7804** `gitea_repos` equals runtime portal list + +## Gov portal workflow dedupe + +Canonical doc: [`../gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md`](../gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md). Optional drift check when the monorepo clone exists: + +```bash +bash scripts/verify/check-gov-portal-workflow-canonical-strings.sh +# or: GOV_PORTALS_MONOREPO_ROOT=/path/to/gov-portals-monorepo bash ... ``` Task narrative: [docs/04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md](../../docs/04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md). diff --git a/config/gitea-phoenix/gov-portals-runtime.v1.json b/config/gitea-phoenix/gov-portals-runtime.v1.json new file mode 100644 index 00000000..29e1502a --- /dev/null +++ b/config/gitea-phoenix/gov-portals-runtime.v1.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": "1", + "lxc_vmid": 7804, + "lan_ipv4": "192.168.11.54", + "systemd_unit_prefix": "gov-portal-", + "public_wildcard_fqdn_pattern": "*.xom-dev.phoenix.sankofa.nexus", + "npmplus_doc": "docs/04-configuration/GOV_PORTALS_XOM_DEV_DEPLOYMENT.md", + "portals": [ + { + "id": "DBIS", + "port": 3001, + "dev_fqdn": "https://dbis.xom-dev.phoenix.sankofa.nexus/", + "gitea_repo": "Gov_Web_Portals/DBIS", + "deploy_target": "dbis-portal-live", + "pnpm_filter": "portal-dbis" + }, + { + "id": "ICCC", + "port": 3002, + "dev_fqdn": "https://iccc.xom-dev.phoenix.sankofa.nexus/", + "gitea_repo": "Gov_Web_Portals/ICCC", + "deploy_target": "iccc-portal-live", + "pnpm_filter": "portal-iccc" + }, + { + "id": "OMNL", + "port": 3003, + "dev_fqdn": "https://omnl.xom-dev.phoenix.sankofa.nexus/", + "gitea_repo": "Gov_Web_Portals/OMNL", + "deploy_target": "omnl-portal-live", + "pnpm_filter": "portal-omnl" + }, + { + "id": "XOM", + "port": 3004, + "dev_fqdn": "https://xom.xom-dev.phoenix.sankofa.nexus/", + "gitea_repo": "Gov_Web_Portals/XOM", + "deploy_target": "xom-portal-live", + "pnpm_filter": "portal-xom" + } + ] +} diff --git a/config/gitea-phoenix/gov-portals-runtime.v1.schema.json b/config/gitea-phoenix/gov-portals-runtime.v1.schema.json new file mode 100644 index 00000000..328f7057 --- /dev/null +++ b/config/gitea-phoenix/gov-portals-runtime.v1.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gitea.d-bis.org/d-bis/proxmox/gov-portals-runtime.v1.schema.json", + "title": "Gov portals xom-dev runtime (CT 7804)", + "type": "object", + "required": ["schemaVersion", "lxc_vmid", "lan_ipv4", "portals"], + "additionalProperties": false, + "properties": { + "schemaVersion": { "type": "string", "const": "1" }, + "lxc_vmid": { "type": "integer", "minimum": 1 }, + "lan_ipv4": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$" }, + "systemd_unit_prefix": { "type": "string" }, + "public_wildcard_fqdn_pattern": { "type": "string" }, + "npmplus_doc": { "type": "string" }, + "portals": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "port", "dev_fqdn", "gitea_repo", "deploy_target", "pnpm_filter"], + "additionalProperties": false, + "properties": { + "id": { "type": "string", "pattern": "^[A-Z]+$" }, + "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "dev_fqdn": { "type": "string", "minLength": 8 }, + "gitea_repo": { "type": "string", "pattern": "^Gov_Web_Portals/[A-Za-z0-9_-]+$" }, + "deploy_target": { "type": "string", "minLength": 1 }, + "pnpm_filter": { "type": "string", "minLength": 1 } + } + } + } + } +} diff --git a/config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md b/config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md new file mode 100644 index 00000000..6527fd25 --- /dev/null +++ b/config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md @@ -0,0 +1,44 @@ +# Gov portal Gitea workflows — canonical patterns (dedupe) + +There are **two valid layouts** for `Gov_Web_Portals/{DBIS,ICCC,OMNL,XOM}` on Gitea. Pick **one per repo** and avoid silently drifting between them. + +## Pattern A — Split workflows (current monorepo submodules) + +Used under **`Gov_Web_Portals/gov-portals-monorepo`** checkouts: `DBIS/.gitea/workflows/ci.yml` + `deploy-live.yml` (same for ICCC, OMNL, XOM). + +| Concern | Convention | +|--------|-------------| +| Monorepo clone | **`GOV_PORTALS_TOKEN`** (or unset for public read if ever enabled) + `http.extraHeader=Authorization: token …` | +| Overlay | `tar` or `rsync` from standalone repo checkout into `gov-portals//` | +| CI on PR | `ci.yml` — `pnpm install --frozen-lockfile` at monorepo root, `pnpm --filter portal-*` lint/build/typecheck | +| Deploy on `main` | `deploy-live.yml` — validate job then `curl` POST Phoenix with **`target`** = `dbis-portal-live` / `iccc-portal-live` / `omnl-portal-live` / `xom-portal-live` | +| Secrets | `PHOENIX_DEPLOY_URL`, `PHOENIX_DEPLOY_TOKEN`, **`GOV_PORTALS_TOKEN`** (read monorepo) | + +**Canonical strings** (must stay aligned with `phoenix-deploy-api/deploy-targets.json`): + +- `Gov_Web_Portals/DBIS` → `"target":"dbis-portal-live"` +- `Gov_Web_Portals/ICCC` → `"target":"iccc-portal-live"` +- `Gov_Web_Portals/OMNL` → `"target":"omnl-portal-live"` +- `Gov_Web_Portals/XOM` → `"target":"xom-portal-live"` + +## Pattern B — Single combined file (template reference) + +Files under **`config/gitea-workflow-templates/repos/*-portal-ci-and-live.yml`**: one workflow with `verify` + `deploy` jobs, **`GITEA_TOKEN`** + `oauth2:${GITEA_TOKEN}@` clone URL. + +Use when bootstrapping a **standalone** portal repo that does not yet use Pattern A. Copy into `.gitea/workflows/` and set secrets per [GITEA_CD_OPERATOR_CHECKLIST.md](../../docs/00-meta/GITEA_CD_OPERATOR_CHECKLIST.md). + +## Single source of truth for ports / FQDN / systemd + +**`config/gitea-phoenix/gov-portals-runtime.v1.json`** — LXC **7804** port map and deploy targets. Update it when `GOV_PORTALS_XOM_DEV_DEPLOYMENT.md` changes; Phoenix healthchecks and matrix enrichment should stay consistent. + +## Drift checks (proxmox repo) + +```bash +# If monorepo clone exists (default ~/projects/gov-portals-monorepo): +bash scripts/verify/check-gov-portal-workflow-canonical-strings.sh + +# Full VM routing + parity (inventory, matrix, deploy-targets, operational template): +bash scripts/verify/validate-vm-routing-parity.sh +``` + +When you change **semantic** steps (pnpm filter names, Phoenix `target`, monorepo URL), update **Pattern A** repos **and** Pattern B templates **and** `gov-portals-runtime.v1.json` in one commit series. diff --git a/config/gitea-workflow-templates/repos/README.md b/config/gitea-workflow-templates/repos/README.md index 1bc68caa..543b2837 100644 --- a/config/gitea-workflow-templates/repos/README.md +++ b/config/gitea-workflow-templates/repos/README.md @@ -2,9 +2,11 @@ Copy the matching file into **that** Gitea repo as `.gitea/workflows/.yml`, then set secrets **`PHOENIX_DEPLOY_URL`**, **`PHOENIX_DEPLOY_TOKEN`**. +**Pattern A (monorepo split `ci.yml` + `deploy-live.yml`) vs Pattern B (single template file):** [PORTAL_WORKFLOW_PARITY.md](../PORTAL_WORKFLOW_PARITY.md). + | File | Gitea `repo` | `target` | Notes | |------|----------------|----------|--------| -| [`dbis-portal-ci-and-live.yml`](dbis-portal-ci-and-live.yml) | `Gov_Web_Portals/DBIS` | `dbis-portal-live` | Recommended: monorepo overlay CI + deploy; secrets include **`GITEA_TOKEN`** | +| [`dbis-portal-ci-and-live.yml`](dbis-portal-ci-and-live.yml) | `Gov_Web_Portals/DBIS` | `dbis-portal-live` | Pattern B single file; secrets include **`GITEA_TOKEN`**; monorepo uses Pattern A | | [`iccc-portal-ci-and-live.yml`](iccc-portal-ci-and-live.yml) | `Gov_Web_Portals/ICCC` | `iccc-portal-live` | Same pattern as DBIS | | [`omnl-portal-ci-and-live.yml`](omnl-portal-ci-and-live.yml) | `Gov_Web_Portals/OMNL` | `omnl-portal-live` | Same pattern | | [`xom-portal-ci-and-live.yml`](xom-portal-ci-and-live.yml) | `Gov_Web_Portals/XOM` | `xom-portal-live` | Same pattern | diff --git a/config/gitea-workflow-templates/repos/dbis-portal-ci-and-live.yml b/config/gitea-workflow-templates/repos/dbis-portal-ci-and-live.yml index 318b6d92..1c6424ca 100644 --- a/config/gitea-workflow-templates/repos/dbis-portal-ci-and-live.yml +++ b/config/gitea-workflow-templates/repos/dbis-portal-ci-and-live.yml @@ -1,4 +1,5 @@ # Copy to Gov_Web_Portals/DBIS → .gitea/workflows/deploy-portal-live.yml (or a second workflow file). +# Pattern B (single file). Monorepo submodules use Pattern A — see ../PORTAL_WORKFLOW_PARITY.md # Secrets: PHOENIX_DEPLOY_URL, PHOENIX_DEPLOY_TOKEN, GITEA_TOKEN (read access to gov-portals-monorepo for CI). # # verify: shallow-clone monorepo, overlay this repo into DBIS/, run lint + typecheck (workspace deps). diff --git a/config/gitea-workflow-templates/repos/dbis-portal-live.yml b/config/gitea-workflow-templates/repos/dbis-portal-live.yml index b7c8fa8f..765297e6 100644 --- a/config/gitea-workflow-templates/repos/dbis-portal-live.yml +++ b/config/gitea-workflow-templates/repos/dbis-portal-live.yml @@ -1,5 +1,6 @@ # Minimal deploy-only workflow (no monorepo CI). Prefer dbis-portal-ci-and-live.yml when the repo # can store GITEA_TOKEN for cloning Gov_Web_Portals/gov-portals-monorepo in Actions. +# Monorepo Pattern A: ../PORTAL_WORKFLOW_PARITY.md # Copy to Gov_Web_Portals/DBIS → .gitea/workflows/deploy-portal-live.yml # Secrets: PHOENIX_DEPLOY_URL, PHOENIX_DEPLOY_TOKEN name: Deploy DBIS portal (Phoenix) diff --git a/config/gitea-workflow-templates/repos/iccc-portal-ci-and-live.yml b/config/gitea-workflow-templates/repos/iccc-portal-ci-and-live.yml index 4b30dfbd..04ef5df1 100644 --- a/config/gitea-workflow-templates/repos/iccc-portal-ci-and-live.yml +++ b/config/gitea-workflow-templates/repos/iccc-portal-ci-and-live.yml @@ -1,4 +1,5 @@ # Copy to Gov_Web_Portals/ICCC → .gitea/workflows/deploy-portal-live.yml +# Pattern B (single file). Monorepo: ../PORTAL_WORKFLOW_PARITY.md # Secrets: PHOENIX_DEPLOY_URL, PHOENIX_DEPLOY_TOKEN, GITEA_TOKEN name: CI and deploy ICCC portal (Phoenix) diff --git a/config/gitea-workflow-templates/repos/omnl-portal-ci-and-live.yml b/config/gitea-workflow-templates/repos/omnl-portal-ci-and-live.yml index 49157377..ed46d77a 100644 --- a/config/gitea-workflow-templates/repos/omnl-portal-ci-and-live.yml +++ b/config/gitea-workflow-templates/repos/omnl-portal-ci-and-live.yml @@ -1,4 +1,5 @@ # Copy to Gov_Web_Portals/OMNL → .gitea/workflows/deploy-portal-live.yml +# Pattern B (single file). Monorepo: ../PORTAL_WORKFLOW_PARITY.md # Secrets: PHOENIX_DEPLOY_URL, PHOENIX_DEPLOY_TOKEN, GITEA_TOKEN name: CI and deploy OMNL portal (Phoenix) diff --git a/config/gitea-workflow-templates/repos/xom-portal-ci-and-live.yml b/config/gitea-workflow-templates/repos/xom-portal-ci-and-live.yml index db11c9e7..965a35c2 100644 --- a/config/gitea-workflow-templates/repos/xom-portal-ci-and-live.yml +++ b/config/gitea-workflow-templates/repos/xom-portal-ci-and-live.yml @@ -1,4 +1,5 @@ # Copy to Gov_Web_Portals/XOM → .gitea/workflows/deploy-portal-live.yml +# Pattern B (single file). Monorepo: ../PORTAL_WORKFLOW_PARITY.md # Secrets: PHOENIX_DEPLOY_URL, PHOENIX_DEPLOY_TOKEN, GITEA_TOKEN name: CI and deploy XOM portal (Phoenix) diff --git a/docs/00-meta/GITEA_CD_OPERATOR_CHECKLIST.md b/docs/00-meta/GITEA_CD_OPERATOR_CHECKLIST.md index 08d19cd8..e36d3fc3 100644 --- a/docs/00-meta/GITEA_CD_OPERATOR_CHECKLIST.md +++ b/docs/00-meta/GITEA_CD_OPERATOR_CHECKLIST.md @@ -8,8 +8,19 @@ Use this after changing **`phoenix-deploy-api/deploy-targets.json`** or adding w 2. **Secrets** on **that repo** (not only global): - **`PHOENIX_DEPLOY_URL`** — full URL for `POST` (same shape as **`d-bis/proxmox`** workflows use), typically `http://:4001/api/deploy` or HTTPS equivalent. - **`PHOENIX_DEPLOY_TOKEN`** — bearer token accepted by Phoenix deploy API. - - **`GITEA_TOKEN`** — required for **gov portal CI** workflows that clone `Gov_Web_Portals/gov-portals-monorepo` (read-only token is enough). -3. **Workflow file** in the repo: copy from [`config/gitea-workflow-templates/repos/README.md`](../config/gitea-workflow-templates/repos/README.md) or use the repo’s existing `.gitea/workflows/*.yml`. + - **`GITEA_TOKEN`** — used by **Pattern B** single-file portal templates under `config/gitea-workflow-templates/repos/*-portal-ci-and-live.yml` when cloning the monorepo with `oauth2:${GITEA_TOKEN}@…` (read-only is enough). + - **`GOV_PORTALS_TOKEN`** — used by **Pattern A** split workflows in `Gov_Web_Portals/gov-portals-monorepo` submodules (`GOV_PORTALS_TOKEN` + `http.extraHeader=Authorization: token …`); same minimum scope: read `Gov_Web_Portals/gov-portals-monorepo` on Gitea. +3. **Workflow file** in the repo: copy from [`config/gitea-workflow-templates/repos/README.md`](../config/gitea-workflow-templates/repos/README.md) or use the repo’s existing `.gitea/workflows/*.yml`. **Pattern A vs B:** [`config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md`](../config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md). + +### Secrets hygiene (Gitea-only, least privilege) + +| Secret | Typical scope | Never | +|--------|----------------|-------| +| **`GITEA_TOKEN`** / **`GOV_PORTALS_TOKEN`** | Read `Gov_Web_Portals/gov-portals-monorepo` (and overlay subtree) for CI | Commit into repo YAML, `.git/config` remote URLs, or logs | +| **`PHOENIX_DEPLOY_TOKEN`** | `POST` Phoenix deploy API only | Reuse as full Gitea admin token | +| **`NPM_PASSWORD`** (operator) | NPMplus API on LAN | Same token as Gitea | + +Rotate any token that was ever pasted into a `git remote` URL. Prefer `source scripts/lib/load-project-env.sh` and `git -c "http.extraHeader=Authorization: token …"` for one-off pushes (see [GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md](../04-configuration/GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md)). ## Phoenix deploy host (LAN) diff --git a/docs/04-configuration/GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md b/docs/04-configuration/GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md index 2a656911..e222fefa 100644 --- a/docs/04-configuration/GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md +++ b/docs/04-configuration/GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md @@ -6,7 +6,7 @@ This document ties together **Gitea repos**, the **gov-portals-monorepo** umbrel - **Forge policy (Gitea-only):** `Gov_Web_Portals/*` repos and the monorepo use **only Gitea** as authoritative remote — no GitHub `origin` for those trees. **`d-bis/proxmox`** uses **`gitea`** as canonical for pushes; a GitHub remote may exist only as a **non-authoritative** mirror. See [GOV_PORTALS_REPO_DIRECTORY_VM_FQDN_TABLE.md](./GOV_PORTALS_REPO_DIRECTORY_VM_FQDN_TABLE.md). - **Authoritative application code** for each portal lives in its **Gitea repo** under `Gov_Web_Portals/` (DBIS, ICCC, OMNL, XOM), with **`main`** as the integration branch unless you have agreed otherwise. -- **Integration / shared layout** lives in **`Gov_Web_Portals/gov-portals-monorepo`** (workspace + `packages/shared`). Portal repos use `workspace:*` for `@public-web-portals/shared`, so **CI that runs `pnpm install` in the portal repo alone is insufficient** unless you vendor shared — the recommended Actions flow clones the monorepo and **overlays** the pushed portal tree (see templates below). +- **Integration / shared layout** lives in **`Gov_Web_Portals/gov-portals-monorepo`** (workspace + `packages/shared`). Portal repos use `workspace:*` for `@public-web-portals/shared`, so **CI that runs `pnpm install` in the portal repo alone is insufficient** unless you vendor shared — the recommended Actions flow clones the monorepo and **overlays** the pushed portal tree (see templates below). **Runtime map (7804 ports / FQDNs / Phoenix targets):** [`config/gitea-phoenix/gov-portals-runtime.v1.json`](../../config/gitea-phoenix/gov-portals-runtime.v1.json) (see also [PORTAL_WORKFLOW_PARITY.md](../../config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md)). - **Live runtime** for the xom-dev stack is **LXC 7804** (`IP_GOV_PORTALS_DEV`, default `192.168.11.54`) with systemd units `gov-portal-DBIS` … `gov-portal-XOM` on ports **3001–3004**. Public hostnames are documented in [GOV_PORTALS_XOM_DEV_DEPLOYMENT.md](./GOV_PORTALS_XOM_DEV_DEPLOYMENT.md). ## Two operator paths (pick deliberately) diff --git a/docs/04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md b/docs/04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md index 6666e5e8..5c183b8b 100644 --- a/docs/04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md +++ b/docs/04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md @@ -1,16 +1,23 @@ # Task — Gitea / Phoenix routing cleanup scoped to all non-blockchain VMIDs -**Status:** In progress (matrix + validator landed; workflow dedupe / optional runtime JSON still open) +**Status:** Complete (ongoing maintenance: regenerate matrix after each inventory export; resolve operational hostname **WARN** lines when template design names are reconciled) + **Created:** 2026-05-12 **Owner:** Operator + repo maintainers ## Implemented (repo) - **`config/gitea-phoenix/besu-vmid-exclusions.v1.json`** — Besu-fleet hostname prefix rules (excluded from closure). -- **`config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json`** — One row per in-scope running guest (last committed `reports/status/live_inventory.json`); partial enrichment from `phoenix-deploy-api/deploy-targets.json`. +- **`config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json`** — One row per in-scope running guest (last committed `reports/status/live_inventory.json`); enrichment from `phoenix-deploy-api/deploy-targets.json`. - **`config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.schema.json`** — JSON Schema for matrix rows. -- **`scripts/lib/non_blockchain_vm_routing_matrix.py`** — `generate` / `validate` subcommands. -- **`scripts/verify/validate-non-blockchain-vm-routing-matrix.sh`** — invoked from `scripts/validation/validate-config-files.sh`. +- **`config/gitea-phoenix/gov-portals-runtime.v1.json`** + **`.schema.json`** — CT **7804** ports **3001–3004**, repos, Phoenix targets, `pnpm` filters; parity-checked against matrix **7804** row. +- **`scripts/lib/non_blockchain_vm_routing_matrix.py`** — `generate`, `validate`, `parity-deploy-targets`, `parity-operational-template`, `parity-gov-portals-runtime`. +- **`scripts/verify/validate-vm-routing-parity.sh`** — full gate chain. +- **`scripts/verify/validate-non-blockchain-vm-routing-matrix.sh`** — delegates to `validate-vm-routing-parity.sh` (used by `validate-config-files.sh`). +- **`scripts/verify/check-gov-portal-workflow-canonical-strings.sh`** — optional monorepo drift check (`GOV_PORTALS_MONOREPO_ROOT`, skips if path missing). +- **`config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md`** — Pattern A vs B, secrets, canonical Phoenix `target` strings. +- **Operational / ALL_VMIDS sweep:** `parity-operational-template` compares **IPv4** to `config/proxmox-operational-template.json` (fails on true drift); **hostname** mismatches emit **WARN** (inventory Proxmox names vs template design names; NPMplus **10233** `.166`/`.167` dual-homed note per [ALL_VMIDS_ENDPOINTS.md](./ALL_VMIDS_ENDPOINTS.md)). +- **Secrets hygiene:** [GITEA_CD_OPERATOR_CHECKLIST.md](../00-meta/GITEA_CD_OPERATOR_CHECKLIST.md) expanded (token table, Gitea-only, no tokens in `git remote` URLs). ## Why this exists @@ -46,38 +53,21 @@ Besu-only hosts per the exclusion list above. For those, the matrix may omit row ## Objectives (work packages) -1. **Machine-readable matrix** - Add `config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.schema.json` (JSON Schema) and `config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json` (or generated `reports/status/…` with a committed snapshot) listing, for each **in-scope VMID**: `vmid`, `hostname`, `primary_ip`, `category`, `gitea_repos[]`, `deploy_target`, `workflow_glob`, `health_url`, `notes`. - -2. **Validator / gate script** - `scripts/verify/validate-non-blockchain-vm-routing-matrix.sh` (or Python under `scripts/lib/`): - - Load latest `live_inventory.json`. - - Compute **in-scope VMIDs** = all running guests minus **exclusion list** (config-driven, e.g. `config/gitea-phoenix/besu-vmid-exclusions.v1.json`). - - Fail CI if any in-scope VMID is **missing** from the matrix file unless explicitly listed in an **`allowed_missing`** section with reason (e.g. “ephemeral lab CT”). - - Warn (or fail, policy TBD) if matrix references a VMID **not** in inventory. - -3. **`deploy-targets.json` parity** - Every **Pattern A** app that should redeploy from Gitea must have a target consistent with the matrix; health URLs must match ALL_VMIDS / E2E lists where applicable. - -4. **Workflow deduplication** - Portal templates under `config/gitea-workflow-templates/repos/` vs copies in each `Gov_Web_Portals/*` repo: reduce drift (shared snippet, documented bump process, or generator — pick in implementation). - -5. **Optional `gov-portals-runtime.json`** - Single file mapping **7804** unit names → ports → public FQDNs for operators and Phoenix (only if it reduces duplication with `deploy-targets` healthchecks). - -6. **Secrets hygiene** - Document per-repo **least-privilege** tokens (read monorepo vs deploy vs NPM API); no long-lived tokens in `.git/config` URLs (Gitea-only policy). - -7. **Docs** - Update [GITEA_REPO_VM_CD_CI_MATRIX.md](./GITEA_REPO_VM_CD_CI_MATRIX.md) from the generated matrix (or link “generated from …” to avoid double maintenance). +1. **Machine-readable matrix** — Done (`non-blockchain-vm-routing-matrix.v1.json` + schema). +2. **Validator / gate script** — Done (`validate` + `validate-config-files.sh` + `validate-vm-routing-parity.sh`). +3. **`deploy-targets.json` parity** — Done (`parity-deploy-targets`; single-repo rows also check `health_url`). +4. **Workflow deduplication** — Done as **process + CI**: [PORTAL_WORKFLOW_PARITY.md](../../config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md) + `check-gov-portal-workflow-canonical-strings.sh` + template header comments pointing at Pattern A/B. +5. **`gov-portals-runtime.json`** — Done (`gov-portals-runtime.v1.json` + schema + jq gate in `validate-config-files.sh`). +6. **Secrets hygiene** — Done (operator checklist section + parity doc). +7. **Docs** — Done (this file, [GITEA_REPO_VM_CD_CI_MATRIX.md](./GITEA_REPO_VM_CD_CI_MATRIX.md), [GOV_PORTALS_XOM_DEV_DEPLOYMENT.md](./GOV_PORTALS_XOM_DEV_DEPLOYMENT.md), [config/gitea-phoenix/README.md](../../config/gitea-phoenix/README.md)). ## Acceptance criteria (task complete when) - **Closure:** Every **running** guest VMID in `live_inventory.json` that is **not** in the **Besu exclusion** config has a matrix entry **or** an approved `allowed_missing` record with owner sign-off in JSON. - **Scripts:** `bash scripts/validation/validate-config-files.sh` (or `run-all-validation.sh`) invokes the new validator once the matrix is committed. - **Phoenix:** `deploy-targets.json` passes `bash scripts/validation/validate-phoenix-deploy-targets.sh` and matches matrix targets for all **Phoenix-backed** non-Besu apps. -- **Docs:** ALL_VMIDS and matrix show **no contradictions** for in-scope VMIDs (IP, FQDN, VMID). -- **Gitea-only:** No canonical repo uses GitHub as `origin` for app code paths documented in the matrix (mirrors optional, clearly labeled). +- **Docs:** ALL_VMIDS / operational template vs matrix: **IPv4** enforced; hostname differences **WARN** until template or inventory naming is reconciled (see `parity-operational-template` output). +- **Gitea-only:** Policy unchanged; matrix does not embed GitHub `origin` rules (see [GOV_PORTALS_REPO_DIRECTORY_VM_FQDN_TABLE.md](./GOV_PORTALS_REPO_DIRECTORY_VM_FQDN_TABLE.md)). ## Suggested implementation order diff --git a/docs/04-configuration/GITEA_REPO_VM_CD_CI_MATRIX.md b/docs/04-configuration/GITEA_REPO_VM_CD_CI_MATRIX.md index edf3287b..868de6bd 100644 --- a/docs/04-configuration/GITEA_REPO_VM_CD_CI_MATRIX.md +++ b/docs/04-configuration/GITEA_REPO_VM_CD_CI_MATRIX.md @@ -4,7 +4,7 @@ Each **application repo** should carry **its own** `.gitea/workflows/*.yml` so p **Canonical integration:** [Phoenix deploy API](../../phoenix-deploy-api/server.js) + [`deploy-targets.json`](../../phoenix-deploy-api/deploy-targets.json). Gov portals two-path model: [GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md](./GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md). -**Planned full-cluster alignment (non-Besu VMIDs):** [GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md](./GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md) — machine-readable matrix + CI gate so every **non-blockchain** guest in `live_inventory.json` is mapped or explicitly exempt. **Committed matrix:** [`config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json`](../../config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json) (regenerate after inventory export per [`config/gitea-phoenix/README.md`](../../config/gitea-phoenix/README.md)). +**Planned full-cluster alignment (non-Besu VMIDs):** [GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md](./GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md) — machine-readable matrix + CI gates. **Committed data:** [`config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json`](../../config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json), [`config/gitea-phoenix/gov-portals-runtime.v1.json`](../../config/gitea-phoenix/gov-portals-runtime.v1.json). **Workflow patterns:** [`config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md`](../../config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md). **Postgres / Prisma in Actions (self-hosted runners):** use **`...@postgres:5432`** (service hostname) and keep **`container.network`** empty on **`act_runner`** — [GITEA_ACT_RUNNER_SETUP.md](GITEA_ACT_RUNNER_SETUP.md) (troubleshooting **P1001**). diff --git a/docs/04-configuration/GOV_PORTALS_XOM_DEV_DEPLOYMENT.md b/docs/04-configuration/GOV_PORTALS_XOM_DEV_DEPLOYMENT.md index 477ef54d..d4032b62 100644 --- a/docs/04-configuration/GOV_PORTALS_XOM_DEV_DEPLOYMENT.md +++ b/docs/04-configuration/GOV_PORTALS_XOM_DEV_DEPLOYMENT.md @@ -75,6 +75,8 @@ Or use a wildcard: | omnl.xom-dev.phoenix.sankofa.nexus | 3003 | OMNL portal | | xom.xom-dev.phoenix.sankofa.nexus | 3004 | XOM portal | +Machine-readable copy of this table (Gitea repos, `pnpm` filters, Phoenix `target` names): [`config/gitea-phoenix/gov-portals-runtime.v1.json`](../../config/gitea-phoenix/gov-portals-runtime.v1.json) (validated in `scripts/validation/validate-config-files.sh`). + ## NPMplus proxy hosts (manual fallback) If the add script cannot reach NPMplus, add these in NPMplus UI → Hosts → Proxy Hosts: diff --git a/docs/MASTER_INDEX.md b/docs/MASTER_INDEX.md index f042ef8b..eee8cd6f 100644 --- a/docs/MASTER_INDEX.md +++ b/docs/MASTER_INDEX.md @@ -31,7 +31,7 @@ | **Gitea TLS expiry cron** | `bash scripts/maintenance/schedule-gitea-cert-check-cron.sh --install` — installs a daily warning check with `WARN_DAYS=30` | | **Gitea repo ↔ VM CI/CD matrix** | [04-configuration/GITEA_REPO_VM_CD_CI_MATRIX.md](04-configuration/GITEA_REPO_VM_CD_CI_MATRIX.md) — per-repo workflows, Phoenix deploy targets, templates under `config/gitea-workflow-templates/`; gov portals live paths: [GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md](04-configuration/GITEA_GOV_PORTALS_LIVE_SOURCE_OF_TRUTH.md) | | **Gov portals: directory, Gitea/GitHub, VMID, FQDN** | [04-configuration/GOV_PORTALS_REPO_DIRECTORY_VM_FQDN_TABLE.md](04-configuration/GOV_PORTALS_REPO_DIRECTORY_VM_FQDN_TABLE.md) — one table for proxmox, `gov-portals-monorepo`, and DBIS/ICCC/OMNL/XOM submodules | -| **Gitea / Phoenix routing matrix (all non-Besu VMIDs)** | [04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md](04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md) — closure vs `live_inventory.json`; committed data: `config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json`, `scripts/lib/non_blockchain_vm_routing_matrix.py` | +| **Gitea / Phoenix routing matrix (all non-Besu VMIDs)** | [04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md](04-configuration/GITEA_PHOENIX_NON_BLOCKCHAIN_VM_ROUTING_CLEANUP_TASK.md) — matrix + deploy-target + operational parity; `config/gitea-phoenix/gov-portals-runtime.v1.json`; [PORTAL_WORKFLOW_PARITY.md](../config/gitea-workflow-templates/PORTAL_WORKFLOW_PARITY.md) | | **Gitea CD operator checklist** | [00-meta/GITEA_CD_OPERATOR_CHECKLIST.md](00-meta/GITEA_CD_OPERATOR_CHECKLIST.md) — secrets, Phoenix host sync, `report-gitea-cd-parity.sh` | | **TsunamiSwap DEX plan** | [00-meta/AAVE_CHAIN138_AND_MARIONETTE_TSUNAMISWAP_PLAN.md](00-meta/AAVE_CHAIN138_AND_MARIONETTE_TSUNAMISWAP_PLAN.md) — canonical TsunamiSwap VM `5010` plan, current DEX link, and publish checklist | | **Required / optional / recommended (full plan)** | [00-meta/COMPLETE_REQUIRED_OPTIONAL_RECOMMENDED_INDEX.md](00-meta/COMPLETE_REQUIRED_OPTIONAL_RECOMMENDED_INDEX.md) | diff --git a/scripts/lib/non_blockchain_vm_routing_matrix.py b/scripts/lib/non_blockchain_vm_routing_matrix.py index 06dd4297..675b47b3 100644 --- a/scripts/lib/non_blockchain_vm_routing_matrix.py +++ b/scripts/lib/non_blockchain_vm_routing_matrix.py @@ -247,6 +247,132 @@ def cmd_validate(args: argparse.Namespace) -> int: return 0 +def cmd_parity_deploy_targets(args: argparse.Namespace) -> int: + """Each deploy-target with a resolved VMID must list its repo on the matrix row; optional health URL match.""" + repo_root = Path(args.repo_root).resolve() + matrix = load_json(Path(args.matrix)) + targets_path = repo_root / "phoenix-deploy-api" / "deploy-targets.json" + if not targets_path.is_file(): + print("ERROR: deploy-targets.json missing", file=sys.stderr) + return 1 + data = load_json(targets_path) + by_vmid: dict[str, dict[str, Any]] = {str(e["vmid"]): e for e in matrix.get("entries", [])} + errs = 0 + for t in data.get("targets", []): + repo = t.get("repo") + desc = t.get("description") or "" + if not repo or "/" not in str(repo): + continue + vmid = _vmid_from_target_description(desc) + hc = t.get("healthcheck") or {} + url = (hc.get("url") or "").strip() + if vmid is None and url: + if "192.168.11.51:3000" in url: + vmid = "7801" + elif "blockscout.defi-oracle.io" in url or "/api/config/capabilities" in url: + vmid = "5000" + if vmid is None: + continue + row = by_vmid.get(vmid) + if not row: + print(f"WARN parity-deploy-targets: vmid {vmid} has no matrix row (repo {repo})", file=sys.stderr) + continue + grepos = row.get("gitea_repos") or [] + if repo not in grepos: + print( + f"ERROR parity-deploy-targets: repo {repo} for vmid {vmid} not in matrix gitea_repos {grepos}", + file=sys.stderr, + ) + errs += 1 + # Multi-repo CT (7804): health URL differs per portal — skip URL equality. + if url and row.get("health_url") and len(grepos) <= 1 and row["health_url"] != url: + print( + f"ERROR parity-deploy-targets: vmid {vmid} health_url matrix={row['health_url']!r} " + f"target={url!r} repo={repo}", + file=sys.stderr, + ) + errs += 1 + if errs: + return 1 + print("OK: parity-deploy-targets (repo ⊆ matrix; health match where single-repo row)", file=sys.stderr) + return 0 + + +def cmd_parity_operational_template(args: argparse.Namespace) -> int: + """Matrix hostname + IP must match config/proxmox-operational-template.json when a service row exists.""" + matrix = load_json(Path(args.matrix)) + tmpl = load_json(Path(args.template)) + services = tmpl.get("services") or [] + by_vmid: dict[str, dict[str, Any]] = {} + for s in services: + vid = s.get("vmid") + if vid is None: + continue + by_vmid[str(int(vid))] = s + errs = 0 + warns = 0 + for row in matrix.get("entries", []): + vid = str(row["vmid"]) + s = by_vmid.get(vid) + if not s: + continue + h_t = (s.get("hostname") or "").strip() + ip_t = (s.get("ipv4") or "").strip() + h_m = (row.get("hostname") or "").strip() + ip_m = (row.get("primary_ip") or "").strip() + if h_t and h_m and h_t != h_m: + print( + f"WARN parity-operational: vmid {vid} hostname inventory/matrix={h_m!r} " + f"operational_template={h_t!r} (template may use design names)", + file=sys.stderr, + ) + warns += 1 + if not ip_t or not ip_m: + continue + if ip_t == ip_m: + continue + # NPMplus primary: live inventory often lists first net (.166); template uses ingress .167. + if vid == "10233" and ip_m == "192.168.11.166" and ip_t == "192.168.11.167": + print( + "WARN parity-operational: vmid 10233 matrix IP .166 vs template .167 (dual-homed; see ALL_VMIDS)", + file=sys.stderr, + ) + warns += 1 + continue + print( + f"ERROR parity-operational: vmid {vid} ipv4 matrix={ip_m!r} template={ip_t!r}", + file=sys.stderr, + ) + errs += 1 + if warns: + print(f"WARN: parity-operational-template: {warns} hostname/IP note(s)", file=sys.stderr) + if errs: + return 1 + print("OK: parity-operational-template (hostname + ipv4 vs matrix)", file=sys.stderr) + return 0 + + +def cmd_parity_gov_portals_runtime(args: argparse.Namespace) -> int: + """Matrix row 7804 gitea_repos must match gov-portals-runtime.v1.json portal list.""" + matrix = load_json(Path(args.matrix)) + runtime = load_json(Path(args.runtime)) + portals = runtime.get("portals") or [] + expected = sorted(p["gitea_repo"] for p in portals if p.get("gitea_repo")) + row7804 = next((e for e in matrix.get("entries", []) if str(e.get("vmid")) == "7804"), None) + if not row7804: + print("ERROR parity-gov-runtime: no matrix row for vmid 7804", file=sys.stderr) + return 1 + got = sorted(row7804.get("gitea_repos") or []) + if got != expected: + print( + f"ERROR parity-gov-runtime: matrix 7804 gitea_repos {got} != runtime {expected}", + file=sys.stderr, + ) + return 1 + print("OK: parity-gov-portals-runtime (7804 repos vs runtime file)", file=sys.stderr) + return 0 + + def main() -> int: ap = argparse.ArgumentParser(description=__doc__) sub = ap.add_subparsers(dest="cmd", required=True) @@ -264,6 +390,21 @@ def main() -> int: v.add_argument("--matrix", required=True, type=Path) v.set_defaults(func=cmd_validate) + pdt = sub.add_parser("parity-deploy-targets", help="Matrix rows cover deploy-target repos (and health for single-repo VMIDs)") + pdt.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[2]) + pdt.add_argument("--matrix", required=True, type=Path) + pdt.set_defaults(func=cmd_parity_deploy_targets) + + pot = sub.add_parser("parity-operational-template", help="Matrix hostname/IP vs proxmox-operational-template services") + pot.add_argument("--matrix", required=True, type=Path) + pot.add_argument("--template", required=True, type=Path) + pot.set_defaults(func=cmd_parity_operational_template) + + pgr = sub.add_parser("parity-gov-portals-runtime", help="Matrix vmid 7804 gitea_repos match gov-portals-runtime.v1.json") + pgr.add_argument("--matrix", required=True, type=Path) + pgr.add_argument("--runtime", required=True, type=Path) + pgr.set_defaults(func=cmd_parity_gov_portals_runtime) + args = ap.parse_args() return int(args.func(args)) diff --git a/scripts/validation/validate-config-files.sh b/scripts/validation/validate-config-files.sh index 5a82d1ab..c2b36c59 100755 --- a/scripts/validation/validate-config-files.sh +++ b/scripts/validation/validate-config-files.sh @@ -307,14 +307,41 @@ else # Non-blockchain VM routing matrix (closure vs reports/status/live_inventory.json) if [[ -x "$PROJECT_ROOT/scripts/verify/validate-non-blockchain-vm-routing-matrix.sh" ]]; then if "$PROJECT_ROOT/scripts/verify/validate-non-blockchain-vm-routing-matrix.sh" "$PROJECT_ROOT"; then - log_ok "non-blockchain-vm-routing-matrix: inventory closure vs matrix" + log_ok "non-blockchain-vm-routing: inventory + deploy-target + operational + gov-runtime parity" else - log_err "non-blockchain-vm-routing-matrix: validation failed (regenerate: python3 scripts/lib/non_blockchain_vm_routing_matrix.py generate …)" + log_err "non-blockchain-vm-routing: validation failed (regenerate: python3 scripts/lib/non_blockchain_vm_routing_matrix.py generate …)" ERRORS=$((ERRORS + 1)) fi else log_warn "validate-non-blockchain-vm-routing-matrix.sh missing or not executable; skipping" fi + if [[ -f "$PROJECT_ROOT/config/gitea-phoenix/gov-portals-runtime.v1.json" ]] && command -v jq &>/dev/null; then + if jq -e ' + (.schemaVersion == "1") + and (.lxc_vmid == 7804) + and (.lan_ipv4 | type == "string") + and (.portals | type == "array") + and (.portals | length == 4) + and ((.portals | map(.id) | sort) == ["DBIS","ICCC","OMNL","XOM"]) + ' "$PROJECT_ROOT/config/gitea-phoenix/gov-portals-runtime.v1.json" &>/dev/null; then + log_ok "gov-portals-runtime.v1.json: schemaVersion, vmid, four portals" + else + log_err "gov-portals-runtime.v1.json: invalid structure or portal set" + ERRORS=$((ERRORS + 1)) + fi + else + log_warn "gov-portals-runtime.v1.json missing or jq not installed; skipping" + fi + if [[ -x "$PROJECT_ROOT/scripts/verify/check-gov-portal-workflow-canonical-strings.sh" ]]; then + if "$PROJECT_ROOT/scripts/verify/check-gov-portal-workflow-canonical-strings.sh"; then + log_ok "gov-portal monorepo workflow strings (optional path)" + else + log_err "gov-portal monorepo workflow canonical string check failed" + ERRORS=$((ERRORS + 1)) + fi + else + log_warn "check-gov-portal-workflow-canonical-strings.sh missing or not executable; skipping" + fi # Proxmox operational template (VMID/IP/FQDN mirror; see docs/03-deployment/PROXMOX_VE_OPERATIONAL_DEPLOYMENT_TEMPLATE.md) if [[ -f "$PROJECT_ROOT/config/proxmox-operational-template.json" ]]; then log_ok "Found: config/proxmox-operational-template.json" diff --git a/scripts/verify/check-gov-portal-workflow-canonical-strings.sh b/scripts/verify/check-gov-portal-workflow-canonical-strings.sh new file mode 100755 index 00000000..3865df02 --- /dev/null +++ b/scripts/verify/check-gov-portal-workflow-canonical-strings.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Ensure monorepo portal workflows still reference Phoenix targets and monorepo clone. +# Set GOV_PORTALS_MONOREPO_ROOT to override (default: ~/projects/gov-portals-monorepo). +set -euo pipefail +ROOT="${GOV_PORTALS_MONOREPO_ROOT:-$HOME/projects/gov-portals-monorepo}" +if [[ ! -d "$ROOT/DBIS/.gitea/workflows" ]]; then + echo "[INFO] check-gov-portal-workflow-canonical-strings: no monorepo at $ROOT — skip" + exit 0 +fi + +fail() { echo "[ERROR] $*" >&2; exit 1; } + +check_portal() { + local portal="$1" target="$2" filter="$3" + local d="$ROOT/$portal/.gitea/workflows/deploy-live.yml" + local c="$ROOT/$portal/.gitea/workflows/ci.yml" + [[ -f "$d" ]] || fail "missing $d" + [[ -f "$c" ]] || fail "missing $c" + grep -q "PHOENIX_DEPLOY_URL" "$d" || fail "$portal deploy-live: PHOENIX_DEPLOY_URL" + grep -q "PHOENIX_DEPLOY_TOKEN" "$d" || fail "$portal deploy-live: PHOENIX_DEPLOY_TOKEN" + grep -qF "${target}" "$d" || fail "$portal deploy-live: Phoenix target ${target}" + grep -q "Gov_Web_Portals/gov-portals-monorepo" "$d" || fail "$portal deploy-live: monorepo URL" + grep -q "pnpm --filter ${filter}" "$d" "$c" || fail "$portal: pnpm --filter ${filter} in ci or deploy" +} + +check_portal DBIS dbis-portal-live portal-dbis +check_portal ICCC iccc-portal-live portal-iccc +check_portal OMNL omnl-portal-live portal-omnl +check_portal XOM xom-portal-live portal-xom + +echo "[OK] gov-portal workflow canonical strings (monorepo under $ROOT)" diff --git a/scripts/verify/report-gitea-cd-parity.sh b/scripts/verify/report-gitea-cd-parity.sh index 26e0a8a9..f6a6a82c 100755 --- a/scripts/verify/report-gitea-cd-parity.sh +++ b/scripts/verify/report-gitea-cd-parity.sh @@ -32,3 +32,10 @@ for f in "$ROOT/config/gitea-workflow-templates/repos/"*.yml; do basename "$f" done shopt -u nullglob +echo "" +echo "== Non-blockchain VM matrix + parity (inventory, deploy-targets, template, gov runtime) ==" +if [[ -x "$ROOT/scripts/verify/validate-vm-routing-parity.sh" ]]; then + "$ROOT/scripts/verify/validate-vm-routing-parity.sh" "$ROOT" +else + echo "(validate-vm-routing-parity.sh not executable)" +fi diff --git a/scripts/verify/validate-non-blockchain-vm-routing-matrix.sh b/scripts/verify/validate-non-blockchain-vm-routing-matrix.sh index f4f5c9bb..34c7b9f4 100755 --- a/scripts/verify/validate-non-blockchain-vm-routing-matrix.sh +++ b/scripts/verify/validate-non-blockchain-vm-routing-matrix.sh @@ -1,19 +1,6 @@ #!/usr/bin/env bash -# Validate non-blockchain VM routing matrix vs committed live_inventory snapshot. +# Validate non-blockchain VM routing matrix + deploy-target / template / gov-runtime parity. # Usage: scripts/verify/validate-non-blockchain-vm-routing-matrix.sh [PROJECT_ROOT] set -euo pipefail ROOT="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" -INV="${NON_BLOCKCHAIN_MATRIX_INVENTORY:-$ROOT/reports/status/live_inventory.json}" -EXC="$ROOT/config/gitea-phoenix/besu-vmid-exclusions.v1.json" -MTX="$ROOT/config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json" -PY="$ROOT/scripts/lib/non_blockchain_vm_routing_matrix.py" - -if [[ ! -f "$INV" ]]; then - echo "[WARN] validate-non-blockchain-vm-routing-matrix: missing inventory $INV — skip" - exit 0 -fi -if [[ ! -f "$MTX" ]] || [[ ! -f "$EXC" ]] || [[ ! -f "$PY" ]]; then - echo "[ERROR] validate-non-blockchain-vm-routing-matrix: missing matrix, exclusions, or script" >&2 - exit 1 -fi -exec python3 "$PY" validate --inventory "$INV" --exclusions "$EXC" --matrix "$MTX" +exec bash "$ROOT/scripts/verify/validate-vm-routing-parity.sh" "$ROOT" diff --git a/scripts/verify/validate-vm-routing-parity.sh b/scripts/verify/validate-vm-routing-parity.sh new file mode 100755 index 00000000..58d28932 --- /dev/null +++ b/scripts/verify/validate-vm-routing-parity.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Matrix ↔ deploy-targets ↔ operational template ↔ gov-portals runtime (optional). +set -euo pipefail +R="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +PY="$R/scripts/lib/non_blockchain_vm_routing_matrix.py" +INV="${NON_BLOCKCHAIN_MATRIX_INVENTORY:-$R/reports/status/live_inventory.json}" +EXC="$R/config/gitea-phoenix/besu-vmid-exclusions.v1.json" +MTX="$R/config/gitea-phoenix/non-blockchain-vm-routing-matrix.v1.json" +TPL="$R/config/proxmox-operational-template.json" +RUN="$R/config/gitea-phoenix/gov-portals-runtime.v1.json" + +python3 "$PY" validate --inventory "$INV" --exclusions "$EXC" --matrix "$MTX" +python3 "$PY" parity-deploy-targets --repo-root "$R" --matrix "$MTX" +python3 "$PY" parity-operational-template --matrix "$MTX" --template "$TPL" +if [[ -f "$RUN" ]]; then + python3 "$PY" parity-gov-portals-runtime --matrix "$MTX" --runtime "$RUN" +fi +echo "[OK] validate-vm-routing-parity: inventory + deploy-targets + operational template + gov runtime"