feat(gitea-phoenix): gov runtime, deploy/template parity, workflow dedupe docs
Some checks failed
Deploy to Phoenix / deploy (push) Has been skipped
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Has been skipped
Deploy to Phoenix / cloudflare (push) Has been skipped
Deploy to Phoenix / validate (push) Failing after 2s

- 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 <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-12 15:55:50 -07:00
parent e00e1f9b54
commit 377369a5be
22 changed files with 414 additions and 59 deletions

View File

@@ -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 **30013004**, 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 targets `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).

View File

@@ -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"
}
]
}

View File

@@ -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 }
}
}
}
}
}

View File

@@ -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/<Portal>/` |
| 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.

View File

@@ -2,9 +2,11 @@
Copy the matching file into **that** Gitea repo as `.gitea/workflows/<name>.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 |

View File

@@ -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).

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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://<dev-vm>: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 repos 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 repos 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)

View File

@@ -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 **30013004**. Public hostnames are documented in [GOV_PORTALS_XOM_DEV_DEPLOYMENT.md](./GOV_PORTALS_XOM_DEV_DEPLOYMENT.md).
## Two operator paths (pick deliberately)

View File

@@ -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 **30013004**, 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

View File

@@ -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**).

View File

@@ -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:

View File

@@ -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) |

View File

@@ -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))

View File

@@ -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"

View File

@@ -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)"

View File

@@ -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

View File

@@ -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"

View File

@@ -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"