feat(it-ops): cluster live inventory + QEMU ipconfig LAN IPs

Add scripts/it-ops export pipeline (collect_inventory_remote, compute_ipam_drift)
and proxmox_guest_lan_ips parser for ipconfig* and all net* interfaces.

Reconcile ALL_VMIDS, ip-addresses.conf, and operational template with live
VMID/IP data; Order portal env vars; DBIS node matrix; inventory helpers.

Track latest reports/status/live_inventory.json and drift.json (137 guests,
no duplicate LAN IPs). Document export in AGENTS.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-11 10:22:59 -07:00
parent 1c5eebc6b6
commit 1f44a50a25
14 changed files with 2284 additions and 74 deletions

View File

@@ -11,6 +11,10 @@ Orchestration for Proxmox VE, Chain 138 (`smom-dbis-138/`), explorers, NPMplus,
| Need | Location |
|------|-----------|
| Doc index | `docs/MASTER_INDEX.md` |
| Master reference — token / stablecoin launch (“Bible from Nathan”) | `docs/00-meta/BIBLE_FROM_NATHAN_TOKEN_LAUNCH_RESOURCE_COMPENDIUM.md` — cross-cutting institutional compendium (regulation, custody, banking, tooling, audits, listings, checklists); use with Chain 138 canonicals below |
| Master reference — MetaMask Money/mUSD ↔ GRU, provider cross-links, DefiLlama DODO `dfio_meta_main` TVL | `docs/00-meta/METAMASK_GRU_DEFILLAMA_CHAIN138_MASTER_REFERENCE.md` — replay steps, doc cross-links, DefiLlama-Adapters fork/PR **#19198**, touchpoints JSON maintenance |
| DefiLlama ↔ Chain 138 (TVL + optional metrics) | `docs/04-configuration/defillama/CHAIN138_DEFILLAMA_ECOSYSTEM_MAP.md`, `config/defillama-chain138-touchpoints.json`; methodology hub [docs.llama.fi](https://docs.llama.fi/) |
| Optional Cosmos / IBC / Noble / Osmosis / CosmWasm → Chain 138 | `docs/11-references/COSMOS_ECOSYSTEM_CHAIN138_OPTIONAL_INTEGRATIONS_RUNBOOK.md` (streams AE); templates `config/cosmos-chain138-optional/`; **gaps audit** `docs/11-references/COSMOS_CHAIN138_GAPS_AND_INCONSISTENCIES.md` |
| Canonical ecosystem master plan | `docs/02-architecture/DBIS_ECOSYSTEM_TECHNICAL_MASTER_PLAN.md` — umbrella root; subordinate roots: `dbis_chain_138_technical_master_plan.md`, `docs/03-deployment/DBIS_RTGS_MASTER_PLAN_IMPLEMENTATION_TRACKER.md`, `docs/04-configuration/universal-resource-activation/URA_MANIFEST_AUTOMATION_IMPLEMENTATION_TRACKER.md` |
| Treasury / EMI / wallet / VA master plan | `docs/02-architecture/GOVERNMENT_TREASURY_EMI_WALLET_MASTER_PLAN.md` — government treasury, EMIs, digital wallets, virtual accounts (incl. Tatum-style), Rail vs RTGS gates |
| Universal resource activation (manifest, CI, Phoenix) | `UNIVERSAL_RESOURCE_WIRING.md`, `URA_MANIFEST_AUTOMATION_IMPLEMENTATION_TRACKER.md`, `URA_OPERATIONAL_READINESS_CHECKLIST.md` (under `docs/04-configuration/universal-resource-activation/`); `config/universal-resource-activation/{manifest.json,policy-profiles.json,integration/}`; `pnpm ura:ops-readiness` / `ura:ops-readiness:full`, `ura:production-ready` / `ura:production-ready:connectivity`, `ura:validate`, `ura:validate-profiles`, `ura:merge-manifest`, `ura:validate-ledger-mapping`, `ura:writer:ledger`, `ura:writer:settlement`, `ura:profile-hash`, `ura:validate-closure`, `ura:keccak`, `ura:smoke`; `URA_STRICT_CLOSURE` / Gitea `vars.URA_STRICT_CLOSURE`; `smom-dbis-138/contracts/universal-resource/PolicyProfileRegistry.sol` (scoped forge test); Phoenix `PUBLIC_V1_NO_PARTNER_KEY_PATHS` |
@@ -18,6 +22,7 @@ Orchestration for Proxmox VE, Chain 138 (`smom-dbis-138/`), explorers, NPMplus,
| cXAUC/cXAUT unit | 1 full token = 1 troy oz Au — `docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md` (section 5.1) |
| PMM mesh 6s tick | `smom-dbis-138/scripts/reserve/pmm-mesh-6s-automation.sh``docs/integration/ORACLE_AND_KEEPER_CHAIN138.md` (PMM mesh automation) |
| VMID / IP / FQDN | `docs/04-configuration/ALL_VMIDS_ENDPOINTS.md` |
| Live guest inventory + IPAM drift (LAN, seed **r630-01**) | `bash scripts/it-ops/export-live-inventory-and-drift.sh``reports/status/live_inventory.json`, `reports/status/drift.json` (exit **2** only on duplicate guest IPs). Collector parses QEMU **`ipconfig*`** and LXC **`net*`** via `scripts/lib/proxmox_guest_lan_ips.py`. |
| Ops template + JSON | `docs/03-deployment/PROXMOX_VE_OPERATIONAL_DEPLOYMENT_TEMPLATE.md`, `config/proxmox-operational-template.json` |
| Live vs template (read-only SSH) | `bash scripts/verify/audit-proxmox-operational-template.sh` |
| Config validation | `bash scripts/validation/validate-config-files.sh` |
@@ -28,18 +33,25 @@ Orchestration for Proxmox VE, Chain 138 (`smom-dbis-138/`), explorers, NPMplus,
| Submodule + explorer remotes | `docs/00-meta/SUBMODULE_HYGIENE.md``mcp-proxmox` uses **Gitea** `https://gitea.d-bis.org/d-bis/mcp-proxmox.git` (not the old GitHub-only URL). `cross-chain-pmm-lps-publish` is a **worktree** of `cross-chain-pmm-lps`, not a submodule. |
| 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` |
| Gov Portals (CT **7804** @ `IP_GOV_PORTALS_DEV`, r630-04): git pull + build + sync | `./scripts/deployment/sync-gov-portals-ct-7804-from-git.sh` — requires `GITEA_TOKEN` in `.env` (or `export GITEA_TOKEN=…`); optional `--skip-fetch`. See `docs/04-configuration/GOV_PORTALS_XOM_DEV_DEPLOYMENT.md`. |
| 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) |
| Solana native SOL (robust JSON-RPC submit) | `scripts/lib/solana_jsonrpc.py` (stdlib `sendTransaction`), `./scripts/deployment/solana-transfer-native.py` (sign with `solders`). Install: `pip install -r scripts/lib/requirements-solana-ops.txt`. Avoids solana-py `SendTransactionResp` parse failures on RPCs that return only a signature string. Env: `SOLANA_RPC_URL`, `SOLANA_KEYPAIR_PATH` via `source scripts/lib/load-project-env.sh`. |
| 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) |
| Completable (no LAN) | `./scripts/run-completable-tasks-from-anywhere.sh` |
| **EI matrix → mainnet cWUSDC transfer** | `./scripts/deployment/send-cwusdc-ei-matrix-wallets.sh``transfer` from signer to grid slice (`--send-raw` / `--total-send-raw`); resume: `continue-cwusdc-ei-matrix-wallets.sh`. |
| **EI matrix top-up TSV from audit** | `scripts/verify/build-ei-matrix-cwusdc-topup-tsv-from-audit-json.sh` — rebuilds `ei-matrix-cwusdc-topup-*.tsv` from `ei-matrix-readiness-audit-latest.json`. |
| **EI matrix → Multicall3 cWUSDC (preferred)** | `./scripts/deployment/send-cwusdc-ei-matrix-multicall-batches.sh` — Multicall3 `aggregate3` + `transferFrom`; `EI_MATRIX_MC_CHUNK` (default 200). Core: `scripts/lib/ei_matrix_multicall3_cwusdc_batch.py`. Fallback: `send-cwusdc-ei-matrix-targeted.sh` (1 tx/wallet). Pipeline: `pipeline-ei-matrix-remediate-cwusdc-from-audit.sh --multicall`. |
| **EI matrix → mainnet cWUSDC mint** | `./scripts/deployment/pipeline-ei-matrix-mint-cwusdc.sh` — mints `CWUSDC_MAINNET` to each wallet in `config/pmm-soak-wallet-grid.json` (see `docs/03-deployment/EI_MATRIX_CWUSDC_MINT_PIPELINE.md`). Core: `mint-cwusdc-ei-matrix-wallets.sh`; resume: `continue-mint-cwusdc-ei-matrix-wallets.sh`. Not a 138 bridge. |
| **EI matrix on-chain readiness (cWUSDC / 138 cUSDC)** | `./scripts/verify/run-ei-matrix-full-readiness-audit.sh` — full grid, sharded (`--shard-size`, env `EI_MATRIX_AUDIT_*`), writes JSON + gap index files. Ad hoc: `./scripts/verify/audit-ei-matrix-onchain-readiness.sh`. Optional CI: `EI_MATRIX_ONCHAIN_AUDIT_CI=1` in `scripts/verify/run-all-validation.sh`. Core: `scripts/lib/ei_matrix_onchain_readiness_audit.py`. |
| smom-dbis-138 root `forge test` | Uses `foundry.toml` `[profile.default] skip` for legacy Uniswap V2 vendor trees (0.5/0.6 solc); scoped work still uses `bash scripts/forge/scope.sh …` |
| cWUSDT Mainnet USD pricing (on-chain + runbook) | `./scripts/deployment/price-cw-token-mainnet.sh``docs/03-deployment/CW_TOKEN_USD_PRICING_RUNBOOK.md` |
| Deployer LP balances (mesh inventory) | `python3 scripts/deployment/check-deployer-lp-balances.py` — scans `deployment-status.json` + `reports/extraction/promod-uniswap-v2-live-pair-discovery-latest.json`; **UniV2** `lpToken` = pair; **DODO DVM** LP shares = `balanceOf(pool)`; on failure, probes `_BASE_TOKEN_` / `_BASE_CAPITAL_TOKEN_` / `_QUOTE_CAPITAL_TOKEN_` + extra public RPCs (`--no-resolve-dodo` skips; `--chain-id N` for one chain). JSON: `lpTokenAddress`, `lpResolution`, `lpBalances[]`. Use `--deployer` / `DEPLOYER_ADDRESS` if no `PRIVATE_KEY` |
| Etherscan Value $0 for Mainnet `cW*` | Listing path (CoinGecko/CMC), not a contract toggle — `docs/04-configuration/coingecko/ETHERSCAN_USD_VALUE_MAINNET_TOKENS.md` |
| Verify contracts on explorers (all networks) | `cd smom-dbis-138 && ./scripts/deployment/verify-all-networks-explorers.sh` — Blockscout 138, Etherscan + multichain `cW*`, Avax/Arb bridges, optional Cronos/Wemix/CCIPLogger |
| Operator (LAN + secrets) | `./scripts/run-all-operator-tasks-from-lan.sh` (use `--skip-backup` if `NPM_PASSWORD` unset; backup also needs `NPM_EMAIL` in `.env`) |
| Remote SSH to dev VM (5700 / `192.168.11.59`) for runner & deploy API | [docs/04-configuration/DEV_VM_SSH_REMOTE_ACCESS.md](docs/04-configuration/DEV_VM_SSH_REMOTE_ACCESS.md) (Cloudflare Access + tunnel, or UDM allowlist) |
| Remote SSH to dev VM (5700 / `192.168.11.59`) for runner & deploy API | [DEV_VM_SSH_REMOTE_ACCESS.md](docs/04-configuration/DEV_VM_SSH_REMOTE_ACCESS.md); **move workstation `~/projects` → Dev VM:** [DEV_VM_WORKSTATION_MIGRATION_RUNBOOK.md](docs/04-configuration/DEV_VM_WORKSTATION_MIGRATION_RUNBOOK.md), `scripts/deployment/sync-local-projects-to-dev-vm.sh` (rsync code 23 on `--delete-remote`: `scripts/deployment/fix-dev-vm-srv-projects-ownership.sh`) |
| 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`). |
## Git submodules

View File

@@ -91,6 +91,10 @@ ORDER_POSTGRES_PRIMARY="192.168.11.44"
ORDER_POSTGRES_REPLICA="192.168.11.45"
# Dedicated order-redis LXC (e.g. VMID 10020) not present on cluster as of 2026-03; reserve for scripts / future CT
ORDER_REDIS_IP="192.168.11.38"
# Order portal CTs (VMID 1009010092) — reconciled with live inventory 2026-05-11 (r630-04)
ORDER_PORTAL_PUBLIC_IP="${ORDER_PORTAL_PUBLIC_IP:-192.168.11.180}"
ORDER_PORTAL_INTERNAL_IP="${ORDER_PORTAL_INTERNAL_IP:-192.168.11.181}"
ORDER_MCP_LEGAL_IP="${ORDER_MCP_LEGAL_IP:-192.168.11.182}"
# DBIS Service IPs
DBIS_POSTGRES_PRIMARY="192.168.11.105"

View File

@@ -1,7 +1,7 @@
{
"schemaVersion": "1.0.0",
"updated": "2026-03-23",
"description": "Operational template: Proxmox VE nodes, LAN/WAN, NPMplus ingress, workloads (VMID/IP/hostname/FQDN), Besu peering summary, and deployment prerequisites. Authoritative detail remains in docs/04-configuration/ALL_VMIDS_ENDPOINTS.md and config/ip-addresses.conf \u2014 update those first, then sync this file. Live inventory reconciled 2026-03-23 vs cluster SSH audit; order-legal (10070) ARP fix 2026-03-25 (IP_ORDER_LEGAL).",
"updated": "2026-05-09",
"description": "Operational template: Proxmox VE nodes, LAN/WAN, NPMplus ingress, workloads (VMID/IP/hostname/FQDN), Besu peering summary, and deployment prerequisites. Authoritative detail remains in docs/04-configuration/ALL_VMIDS_ENDPOINTS.md and config/ip-addresses.conf update those first, then sync this file. **Cluster inventory 2026-05-09:** PVE 9.1.7; 136 running LXC/QEMU; ml110 0 guests; r630-01 57, r630-02 41, r630-03 19, r630-04 19 (pvesh /cluster/resources).",
"canonicalSources": [
"config/ip-addresses.conf",
"docs/04-configuration/ALL_VMIDS_ENDPOINTS.md",
@@ -108,7 +108,8 @@
],
"primary_ingress_ip": "192.168.11.167",
"public_ipv4": "76.53.10.36",
"purpose": "Main d-bis.org, explorer, Option B RPC hostnames, MIM4U, primary ingress"
"purpose": "Main d-bis.org, explorer, Option B RPC hostnames, MIM4U, primary ingress",
"mission_critical_notes": "preferred_node r630-01; resize to >=2 vCPU / 2048 MiB for production headroom; pair with 10234 via VIP per NPMPLUS_MISSION_CRITICAL_DISTRIBUTION_AND_HA_PLAN.md"
},
{
"vmid": 10234,
@@ -117,7 +118,8 @@
],
"public_ipv4": "76.53.10.37",
"purpose": "Secondary / HA NPMplus (verify running; doc may show stopped)",
"status_note": "Confirm on cluster before relying on HA"
"status_note": "Confirm on cluster before relying on HA",
"mission_critical_notes": "preferred_node r630-02; verify memory/swap in UI matches primary; implement Keepalived + shared state per NPMPLUS_HA_SETUP_GUIDE.md"
},
{
"vmid": 10235,
@@ -126,7 +128,8 @@
],
"public_ipv4": "76.53.10.38",
"designated_public_ip_alt": "76.53.10.42",
"purpose": "rpc-core-2, Alltra, HYBX \u2014 see NPMPLUS_ALLTRA_HYBX_MASTER_PLAN.md"
"purpose": "rpc-core-2, Alltra, HYBX \u2014 see NPMPLUS_ALLTRA_HYBX_MASTER_PLAN.md",
"mission_critical_notes": "relocate off r630-01 when possible (e.g. r630-03) to isolate blast radius \u2014 see NPMPLUS_MISSION_CRITICAL_DISTRIBUTION_AND_HA_PLAN.md"
},
{
"vmid": 10236,
@@ -134,7 +137,8 @@
"192.168.11.170"
],
"public_ipv4": "76.53.10.40",
"purpose": "Dev/Codespaces tunnel, Gitea, Proxmox admin UI"
"purpose": "Dev/Codespaces tunnel, Gitea, Proxmox admin UI",
"mission_critical_notes": "relocate off r630-01 when possible (e.g. r630-04); restrict admin :81 \u2014 see NPMPLUS_MISSION_CRITICAL_DISTRIBUTION_AND_HA_PLAN.md"
},
{
"vmid": 10237,
@@ -811,11 +815,11 @@
{
"vmid": 6000,
"hostname": "fabric-1",
"ipv4": "192.168.11.65",
"ipv4": "192.168.11.113",
"preferred_node": "r630-02",
"category": "dlt",
"runtime_state": "reserved_placeholder_stopped",
"notes": "As of 2026-03-28 this CT has been reclassified as a reserved placeholder and stopped. Earlier app-native checks found no active Fabric peer/orderer/couchdb processes, no expected listeners, and no meaningful Fabric payload under /opt, /etc, or /var.",
"runtime_state": "active_fabric_network",
"notes": "Live inventory 2026-05-11: running @ 192.168.11.113 on r630-02 (was placeholder when at .65).",
"ports": [
{
"port": 7051
@@ -1317,7 +1321,7 @@
"vmid": 3000,
"hostname": "ml-node-1",
"ipv4": "192.168.11.60",
"preferred_node": "ml110",
"preferred_node": "r630-01",
"category": "ml",
"ports": [],
"fqdns": []
@@ -1326,7 +1330,7 @@
"vmid": 3001,
"hostname": "ml-node-2",
"ipv4": "192.168.11.61",
"preferred_node": "ml110",
"preferred_node": "r630-01",
"category": "ml",
"ports": [],
"fqdns": []
@@ -1335,7 +1339,7 @@
"vmid": 3002,
"hostname": "ml-node-3",
"ipv4": "192.168.11.62",
"preferred_node": "ml110",
"preferred_node": "r630-01",
"category": "ml",
"ports": [],
"fqdns": []
@@ -1343,8 +1347,8 @@
{
"vmid": 3003,
"hostname": "ml-node-4",
"ipv4": "192.168.11.63",
"preferred_node": "ml110",
"ipv4": "192.168.11.66",
"preferred_node": "r630-01",
"category": "ml",
"ports": [],
"fqdns": []
@@ -1813,8 +1817,8 @@
{
"vmid": 10090,
"hostname": "order-portal-public",
"ipv4": "192.168.11.36",
"preferred_node": "r630-01",
"ipv4": "192.168.11.180",
"preferred_node": "r630-04",
"category": "order",
"ports": [
{
@@ -1827,8 +1831,8 @@
{
"vmid": 10091,
"hostname": "order-portal-internal",
"ipv4": "192.168.11.35",
"preferred_node": "r630-01",
"ipv4": "192.168.11.181",
"preferred_node": "r630-04",
"category": "order",
"ports": [],
"fqdns": []
@@ -1836,8 +1840,8 @@
{
"vmid": 10092,
"hostname": "order-mcp-legal",
"ipv4": "192.168.11.37",
"preferred_node": "r630-01",
"ipv4": "192.168.11.182",
"preferred_node": "r630-04",
"category": "order",
"ports": [],
"fqdns": []

View File

@@ -124,9 +124,9 @@ Machine-derived rows below come from `services[]` in `config/proxmox-operational
| 10060 | order-dataroom | 192.168.11.42 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10070 | order-legal | 192.168.11.87 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10080 | order-eresidency | 192.168.11.43 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10090 | order-portal-public | 192.168.11.36 | shared / non-concurrent mapping — verify live owner | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10091 | order-portal-internal | 192.168.11.35 | shared / non-concurrent mapping — verify live owner | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10092 | order-mcp-legal | 192.168.11.37 | shared / non-concurrent mapping — verify live owner | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10090 | order-portal-public | 192.168.11.180 | VLAN11 CT — reconciled 2026-05-11 | The Order service | unspecified | TBD | TBD | r630-04 | N/A | application |
| 10091 | order-portal-internal | 192.168.11.181 | VLAN11 CT — reconciled 2026-05-11 | The Order service | unspecified | TBD | TBD | r630-04 | N/A | application |
| 10092 | order-mcp-legal | 192.168.11.182 | VLAN11 CT — reconciled 2026-05-11 | The Order service | unspecified | TBD | TBD | r630-04 | N/A | application |
| 10100 | dbis-postgres-primary | 192.168.11.105 | unique in template | DBIS stack | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10101 | dbis-postgres-replica-1 | 192.168.11.106 | unique in template | DBIS stack | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10120 | dbis-redis | 192.168.11.125 | unique in template | DBIS stack | unspecified | TBD | TBD | r630-01 | N/A | application |
@@ -137,7 +137,7 @@ Machine-derived rows below come from `services[]` in `config/proxmox-operational
| 10201 | order-grafana | 192.168.11.47 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10202 | order-opensearch | 192.168.11.48 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10210 | order-haproxy | 192.168.11.39 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10230 | order-vault | 192.168.11.55 | unique in template | The Order service | unspecified | TBD | TBD | r630-01 | N/A | application |
| 10230 | order-vault | 192.168.11.55 | unique in template | The Order service | unspecified | TBD | TBD | r630-04 | N/A | application |
| 10232 | ct10232 | 192.168.11.56 | unique in template | General CT | unspecified | TBD | TBD | r630-01 | N/A | standard internal |
| 10233 | npmplus-primary | 192.168.11.167 | unique in template | NPMplus ingress | unspecified | TBD | TBD | r630-01 | N/A | edge ingress |
| 10234 | npmplus-secondary | 192.168.11.168 | unique in template | NPMplus ingress | unspecified | TBD | TBD | r630-02 | N/A | edge ingress |

View File

@@ -82,7 +82,7 @@ Use the full table in **ALL_VMIDS_ENDPOINTS** (“NPMplus Endpoint Configuration
**the-order.sankofa.nexus:** NPMplus → order HAProxy **10210** @ **192.168.11.39:80** (proxies to Sankofa portal **192.168.11.51:3000**). See `scripts/deployment/provision-order-haproxy-10210.sh`.
### 5.1 Order stack (live VMIDs, r630-01 unless noted)
### 5.1 Order stack (live VMIDs; core APIs mostly **r630-01**; portals **r630-04**)
| VMID | Hostname | IP | Role (short) |
|------|----------|-----|----------------|
@@ -92,9 +92,9 @@ Use the full table in **ALL_VMIDS_ENDPOINTS** (“NPMplus Endpoint Configuration
| 10060 | order-dataroom | 192.168.11.42 | Dataroom |
| 10070 | order-legal | **192.168.11.87** | Legal — **moved off .54 2026-03-25** (`IP_ORDER_LEGAL`); .54 is **only** VMID 7804 gov-portals |
| 10080 | order-eresidency | 192.168.11.43 | eResidency |
| 10090 | order-portal-public | 192.168.11.36 | Public portal |
| 10091 | order-portal-internal | 192.168.11.35 | Internal portal |
| 10092 | order-mcp-legal | 192.168.11.37 | MCP legal |
| 10090 | order-portal-public | 192.168.11.180 | Public portal (**r630-04**) |
| 10091 | order-portal-internal | 192.168.11.181 | Internal portal (**r630-04**) |
| 10092 | order-mcp-legal | 192.168.11.182 | MCP legal (**r630-04**) |
| 10200 | order-prometheus | 192.168.11.46 | Metrics |
| 10201 | order-grafana | 192.168.11.47 | Dashboards |
| 10202 | order-opensearch | 192.168.11.48 | Search |

View File

@@ -1,24 +1,24 @@
# Complete VMID and Endpoints Reference
**Last Updated:** 2026-05-09
**Document Version:** 1.3
**Last Updated:** 2026-05-11
**Document Version:** 1.4
**Status:** Active Documentation — **Master (source of truth)** for VMID, IP, port, and domain mapping. Use this with the live Besu fleet map in [../06-besu/BESU_NODE_CONFIGURATION_MAP_20260424.md](../06-besu/BESU_NODE_CONFIGURATION_MAP_20260424.md) and the cluster audit in [`../../scripts/verify/check-cluster-besu-inventory.sh`](../../scripts/verify/check-cluster-besu-inventory.sh).
**Operational template (hosts, peering, deployment gates, JSON):** [../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)
---
**Date**: 2026-05-09
**Date**: 2026-05-11
**Status**: Current Active Configuration (Reconciled)
**Last Updated**: 2026-05-09
**Verification Status**: ✅ Cluster-wide guest inventory — **136** running LXC/QEMU (**2026-05-09** `pvesh get /cluster/resources`); **ml110** **0** guests; primary counts on **r630-01** (57), **r630-02** (41), **r630-03** (19), **r630-04** (19). Besu fleet detail: host audit + [`../../scripts/verify/check-cluster-besu-inventory.sh`](../../scripts/verify/check-cluster-besu-inventory.sh).
**Last Updated**: 2026-05-11
**Verification Status**: ✅ Cluster-wide guest inventory — **137** running LXC/QEMU (regenerate: `bash scripts/it-ops/export-live-inventory-and-drift.sh`**`reports/status/live_inventory.json`**; same collector runs on **r630-01** as **`/opt/proxmox/...`**). Parses **`ipconfig*`** (QEMU) and **`net*`** (LXC). **ml110** **0** guests; primary counts on **r630-01** (57), **r630-02** (41), **r630-03** (20), **r630-04** (19). Besu fleet detail: host audit + [`../../scripts/verify/check-cluster-besu-inventory.sh`](../../scripts/verify/check-cluster-besu-inventory.sh).
---
## Quick Summary
- **Cluster (all nodes, LXC+QEMU) — running:** **136** (**2026-05-09** `pvesh get /cluster/resources`); **all** were `running` in that pass.
- **Per Proxmox node (guests):** **r630-01** 57, **r630-02** 41, **r630-03** 19, **r630-04** 19, **ml110** 0.
- **Cluster (all nodes, LXC+QEMU) — running:** **137** (**2026-05-11** live inventory export); **all** were `running` in that pass.
- **Per Proxmox node (guests):** **r630-01** 57, **r630-02** 41, **r630-03** 20, **r630-04** 19, **ml110** 0.
- **Documented VMID rows** in this file: 50+ service entries (excl. deprecated); category rolls below are **Besu / app taxonomy** — reconcile exact Besu counts with `check-cluster-besu-inventory.sh` and the Besu map doc.
- **Infrastructure Services** (sample category): 10
- **Blockchain Nodes**: 37 canonical Besu nodes (Validators: 5, Sentries: 11, RPC: 21) — verify against live map
@@ -35,23 +35,32 @@
## Infrastructure Services
### Proxmox Infrastructure (r630-02)
### Proxmox Infrastructure (mostly **r630-01**; Omada not present)
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 100 | 192.168.11.32 | proxmox-mail-gateway | ✅ Running | SMTP: 25, 587, 465 | Email gateway |
| 101 | 192.168.11.33 | proxmox-datacenter-manager | ✅ Running | Web: 8006 | Datacenter management |
| 103 | 192.168.11.30 | omada | ✅ Running | Web: 8043 | Omada controller |
| 102 | 192.168.11.34 | cloudflared | ✅ Running | Tunnel | Cloudflare Tunnel (`cloudflared`) |
| 104 | 192.168.11.31 | gitea | ✅ Running | Web: 80, 443 | Git repository |
| 105 | 192.168.11.26 | nginxproxymanager | ✅ Running | Web: 80, 81, 443 | Nginx Proxy Manager (legacy) |
| 130 | 192.168.11.27 | monitoring-1 | ✅ Running | Web: 80, 443 | Monitoring services |
**Not in live cluster inventory (2026-05-11):** **103** (Omada). Prior doc row `192.168.11.30` **omada** is **retired** unless reprovisioned.
### NPMplus (r630-01 / r630-02)
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 10233 | 192.168.11.167 | npmplus | ✅ Running | Web: 80, 81, 443 | NPMplus reverse proxy |
| 10234 | 192.168.11.168 | npmplus-secondary | ✅ Running | Web: 80, 81, 443 | NPMplus secondary (HA); restarted 2026-02-03 |
| 10233 | 192.168.11.167 | npmplus | ✅ Running | Web: 80, 81, 443 | NPMplus reverse proxy (primary ingress) |
| 10234 | 192.168.11.168 | npmplus-secondary | ✅ Running | Web: 80, 81, 443 | NPMplus secondary (HA standby); **r630-02** |
| 10235 | 192.168.11.169 | npmplus-alltra-hybx | ✅ Running | Web: 80, 81, 443 | Third NPM — Alltra/HYBX / rpc-core-2 style paths |
| 10236 | 192.168.11.170 | npmplus-fourth | ✅ Running | Web: 80, 81, 443 | Fourth NPM — dev / Codespaces / Gitea tunnel |
| 10237 | 192.168.11.171 | npmplus-mifos | ✅ Running | Web: 80, 81, 443 | NPMplus (Mifos / Fineract path); **r630-02** |
**Live placement (reconcile with cluster):** Run `bash scripts/maintenance/npmplus-cluster-placement-status.sh`. As of **2026-05**, **10233**, **10235**, **10236** often run on **r630-01**; **10234** on **r630-02**. **Target:** redistribute for blast-radius — see [NPMPLUS_MISSION_CRITICAL_DISTRIBUTION_AND_HA_PLAN.md](NPMPLUS_MISSION_CRITICAL_DISTRIBUTION_AND_HA_PLAN.md).
**Dual-homed (10233):** `net0` **192.168.11.166/24**; `net1` **192.168.11.167/24** (default route / gateway on **.167**). Use **.167** for NPMplus API, public ingress, and runbook examples. `live_inventory.json` may list only the first `net*` address (**.166**).
**Note**: NPMplus primary is on VLAN 11 (192.168.11.167). Secondary NPMplus instance on r630-02 for HA configuration.
@@ -61,11 +70,15 @@
## RPC Translator Supporting Services
**Status (2026-05-11):** VMIDs **106108** are **not** present in cluster `live_inventory.json`. Prior translator LXCs are **retired** unless reprovisioned.
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 106 | 192.168.11.110 | redis-rpc-translator | ✅ Running | Redis: 6379 | Distributed nonce management |
| 107 | 192.168.11.111 | web3signer-rpc-translator | ✅ Running | Web3Signer: 9000 | Transaction signing |
| 108 | 192.168.11.112 | vault-rpc-translator | ✅ Running | Vault: 8200 | Secrets management |
| — | — | (historical) redis-rpc-translator | Retired | Redis: 6379 | Was ~106 @ `.110` |
| — | — | (historical) web3signer-rpc-translator | Retired | Web3Signer: 9000 | Was ~107 @ `.111` |
| — | — | (historical) vault-rpc-translator | Retired | Vault: 8200 | Was ~108 @ `.112` |
**Live reassignment:** `.111` / `.112` are used by **8811** (`sankofa-proxmox-mcp`) and **8812** (`operator-services`) on **r630-04** — see [Supplementary cluster inventory](#supplementary-cluster-inventory-live-2026-05-11).
---
@@ -119,6 +132,7 @@ All RPC nodes have been migrated to a new VMID structure for better organization
| VMID | IP Address | Hostname | Status | Block | Peers | Endpoints | Purpose |
|------|------------|----------|--------|-------|-------|-----------|---------|
| 2101 | 192.168.11.211 | besu-rpc-core-1 | ✅ Running | 1,145,367 | 7 | Besu: 8545/8546, P2P: 30303, Metrics: 9545 | Core RPC node |
| 2102 | 192.168.11.212 | besu-rpc-core-2 | ✅ Running | (live) | (live) | Besu: 8545/8546, P2P: 30303, Metrics: 9545 | Core RPC node 2 (**r630-03**) |
| 2103 | 192.168.11.217 | besu-rpc-core-thirdweb | ✅ Running | Live SSH verified 2026-04-24 | Live SSH verified 2026-04-24 | Besu: 8545/8546, P2P: 30303, Metrics: 9545 | Core Thirdweb admin RPC node |
| **2201** | **192.168.11.221** | besu-rpc-public-1 | ✅ Running | 1,145,367 | 7 | Besu: 8545/8546, P2P: 30303, Metrics: 9545 | Public RPC node **(FIXED PERMANENT)** |
| 2301 | 192.168.11.232 | besu-rpc-private-1 | ✅ Running | Cluster CT confirmed on `r630-03` | - | Besu: 8545/8546, P2P: 30303, Metrics: 9545 | Fireblocks-dedicated RPC on `r630-03` |
@@ -192,16 +206,12 @@ These were found live on `r630-01` during the same SSH pass, but they do not exi
**Status**: Historical migration reference only. The rows below refer to the old `.250-.255/.201-.204` plan, not the live `.172-.174/.246-.248` ALLTRA/HYBX RPCs found during the 2026-04-24 SSH pass.
The following VMIDs have been permanently removed:
**Historic VMIDs 25002505 (Besu RPC, destroyed):** former assignments — **2500** @ `.250`, **2501** @ `.251`, **2502** @ `.252`, **2503** @ `.253`, **2504** @ `.254`, **2505** @ `.201` — superseded by VMIDs **2101**, **2201**, **2301**, **2303**, **2304**, **2305** respectively. Those numeric VMIDs were **later reused** for ALLTRA/HYBX internal RPC (same VMID number, different workload). Current IPs are in **Additional Live Internal ALLTRA / HYBX RPC Nodes**.
The following VMIDs have been permanently removed (**no reuse on live cluster**):
| VMID | Old IP Address | Old Hostname | Status | Replaced By |
|------|----------------|--------------|--------|-------------|
| 2500 | 192.168.11.250 | besu-rpc-1 | 🗑️ Destroyed | VMID 2101 |
| 2501 | 192.168.11.251 | besu-rpc-2 | 🗑️ Destroyed | VMID 2201 |
| 2502 | 192.168.11.252 | besu-rpc-3 | 🗑️ Destroyed | VMID 2301 |
| 2503 | 192.168.11.253 | besu-rpc-ali-0x8a | 🗑️ Destroyed | VMID 2303 |
| 2504 | 192.168.11.254 | besu-rpc-ali-0x1 | 🗑️ Destroyed | VMID 2304 |
| 2505 | 192.168.11.201 | besu-rpc-luis-0x8a | 🗑️ Destroyed | VMID 2305 |
| 2506 | 192.168.11.202 | besu-rpc-luis-0x1 | 🗑️ Destroyed | VMID 2306 |
| 2507 | 192.168.11.203 | besu-rpc-putu-0x8a | 🗑️ Destroyed | VMID 2307 |
| 2508 | 192.168.11.204 | besu-rpc-putu-0x1 | 🗑️ Destroyed | VMID 2308 |
@@ -227,6 +237,8 @@ The following VMIDs have been permanently removed:
**CI/CD:** Gitea `.gitea/workflows/deploy.yml` — secrets `TREASURY_DEPLOY_HOST`, `TREASURY_DEPLOY_USER`, `TREASURY_DEPLOY_SSH_KEY`, `TREASURY_DEPLOY_PATH`; runner must reach **192.168.11.94** on LAN.
**Public edge (2026-05):** **`dealflow.d-bis.org`** → Cloudflare **A** on **`d-bis.org`** (script: **`scripts/update-all-dns-to-public-ip.sh`**) → UDM/NPMplus **76.53.10.36:443** → NPMplus **`https://192.168.11.94:443`** (`forward_scheme` **https**). Backend **`CORS_ORIGIN`** must list **`https://dealflow.d-bis.org`** for **`POST /api/auth/demo-login`** from the browser. TLS: NPM Lets Encrypt ( **`request-npmplus-certificates.sh`** with `CERT_DOMAINS_FILTER` if needed).
---
### Blockchain Explorer
@@ -269,7 +281,7 @@ The following VMIDs have been permanently removed:
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 6000 | 192.168.11.65 | fabric-1 | ✅ Running | Peer: 7051, Orderer: 7050 | Hyperledger Fabric network |
| 6000 | 192.168.11.113 | fabric-1 | ✅ Running | Peer: 7051, Orderer: 7050 | Hyperledger Fabric network |
---
@@ -355,7 +367,7 @@ The following VMIDs have been permanently removed:
---
### The Order — microservices (r630-01)
### The Order — microservices (mostly **r630-01**; portals **r630-04**)
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
@@ -365,17 +377,17 @@ The following VMIDs have been permanently removed:
| 10060 | 192.168.11.42 | order-dataroom | ✅ Running | Web: 80 | Dataroom |
| 10070 | **192.168.11.87** | order-legal | ✅ Running | API | Legal — **use `IP_ORDER_LEGAL` (.87); not .54** |
| 10080 | 192.168.11.43 | order-eresidency | ✅ Running | API | eResidency |
| 10090 | 192.168.11.36 | order-portal-public | ✅ Running | Web | Public portal |
| 10091 | 192.168.11.35 | order-portal-internal | ✅ Running | Web | Internal portal |
| 10092 | 192.168.11.37 | order-mcp-legal | ✅ Running | API | MCP legal |
| 10090 | 192.168.11.180 | order-portal-public | ✅ Running | Web | Public portal |
| 10091 | 192.168.11.181 | order-portal-internal | ✅ Running | Web | Internal portal |
| 10092 | 192.168.11.182 | order-mcp-legal | ✅ Running | API | MCP legal |
| 10200 | 192.168.11.46 | order-prometheus | ✅ Running | 9090 | Metrics (`IP_ORDER_PROMETHEUS`; not Order Redis) |
| 10201 | 192.168.11.47 | order-grafana | ✅ Running | 3000 | Dashboards |
| 10202 | 192.168.11.48 | order-opensearch | ✅ Running | 9200 | Search |
| 10210 | 192.168.11.39 | order-haproxy | ✅ Running | 80 (HAProxy → portal :3000) | Edge for **the-order.sankofa.nexus**; HAProxy config via `config/haproxy/order-haproxy-10210.cfg.template` + `scripts/deployment/provision-order-haproxy-10210.sh` |
**Gov portals vs Order:** VMID **7804** alone uses **192.168.11.54** (`IP_GOV_PORTALS_DEV`). Order-legal must not use .54.
**Note:** **1009010092** are on **r630-04** (not r630-01). **MIM4U** uses **7810/7811** on **.37/.36** (r630-02) — do not conflate with Order portal IPs.
---
**Gov portals vs Order:** VMID **7804** alone uses **192.168.11.54** (`IP_GOV_PORTALS_DEV`). Order-legal must not use .54.
### Phoenix Vault Cluster (8640-8642)
@@ -407,7 +419,10 @@ The following VMIDs have been permanently removed:
| 5800 | 192.168.11.85 | (Mifos) | ✅ Running | Web: 80 | Mifos X + Fineract (OMNL) | LXC on r630-02; mifos.d-bis.org; see [MIFOS_R630_02_DEPLOYMENT.md](MIFOS_R630_02_DEPLOYMENT.md) |
| 5801 | 192.168.11.58 | dapp-smom | — | Web: 80 | DApp (frontend-dapp) for Chain 138 bridge | LXC; see [DAPP_LXC_DEPLOYMENT.md](../03-deployment/DAPP_LXC_DEPLOYMENT.md); NPMplus/tunnel dapp.d-bis.org |
| 10232 | 192.168.11.56 | CT10232 | ✅ Running | Various | Container service | ✅ **IP CONFLICT RESOLVED** |
| 10234 | 192.168.11.168 | npmplus-secondary | ⏸️ Stopped | Web: 80, 81, 443 | NPMplus secondary (HA) | On r630-02 |
| 10203 | 192.168.11.228 | omdnl-org-web | ✅ Running | Web: 80 | OMNL / org web (small CT) | **r630-01**; renumbered from **.222** (2026-05-11) to resolve duplicate with **2104** |
| 2421 | 192.168.11.229 | mev-control-backend | ✅ Running | API / backend | MEV control platform backend | **r630-04**; renumbered from **.223** (2026-05-11) to resolve duplicate with **2202** |
**Note:** **10234** is listed under **NPMplus** above (not stopped); older duplicate rows removed. **10203** / **2421** had briefly shared **.222** / **.223** with canonical Besu Justin RPC CTs — fixed by reassignment to **.228** / **.229**.
---
@@ -423,12 +438,55 @@ The following VMIDs have been permanently removed:
### Machine Learning Nodes
**Placement:** LXCs **30003003** run on **r630-01** (hostname field remains `ml110` from template).
| VMID | IP Address | Hostname | Status | Endpoints | Purpose |
|------|------------|----------|--------|-----------|---------|
| 3000 | 192.168.11.60 | ml110 | ✅ Running | ML Services: Various | ML node 1 |
| 3001 | 192.168.11.61 | ml110 | ✅ Running | ML Services: Various | ML node 2 |
| 3002 | 192.168.11.62 | ml110 | ✅ Running | ML Services: Various | ML node 3 |
| 3003 | 192.168.11.63 | ml110 | ✅ Running | ML Services: Various | ML node 4 |
| 3003 | 192.168.11.66 | ml110 | ✅ Running | ML Services: Various | ML node 4 (**r630-01**) |
---
## Supplementary cluster inventory (live 2026-05-11)
Guests present in `r630-01:/opt/proxmox/reports/status/live_inventory.json` at collection time but **not** listed in category tables above (for automation cross-checks). Canonical Besu / NPMplus rows are omitted here when already duplicated above.
| VMID | IP Address | Hostname | Node | Notes |
|------|------------|----------|------|-------|
| 2410 | 192.168.11.218 | info-defi-oracle-web | r630-01 | |
| 5201 | 192.168.11.177 | cacti-alltra-1 | r630-02 | |
| 5202 | 192.168.11.251 | cacti-hybx-1 | r630-02 | |
| 5700 | 192.168.11.59 | dev-vm | r630-04 | |
| 5701 | 192.168.11.65 | gitea-runner-1 | r630-04 | |
| 5702 | 192.168.11.82 | ai-inf-1 | r630-01 | |
| 5705 | 192.168.11.86 | ai-inf-2 | r630-01 | |
| 5751 | 192.168.11.69 | op-stack-deployer-1 | r630-02 | |
| 5752 | 192.168.11.70 | op-stack-ops-1 | r630-02 | |
| 6001 | 192.168.11.178 | fabric-alltra-1 | r630-02 | |
| 6002 | 192.168.11.252 | fabric-hybx-1 | r630-02 | |
| 6202 | 192.168.11.175 | firefly-alltra-1 | r630-02 | |
| 6203 | 192.168.11.176 | firefly-alltra-2 | r630-02 | |
| 6204 | 192.168.11.249 | firefly-hybx-1 | r630-02 | |
| 6205 | 192.168.11.250 | firefly-hybx-2 | r630-02 | |
| 6401 | 192.168.11.179 | indy-alltra-1 | r630-02 | |
| 6402 | 192.168.11.253 | indy-hybx-1 | r630-02 | |
| 6500 | 192.168.11.88 | aries-1 | r630-02 | |
| 6600 | 192.168.11.93 | caliper-1 | r630-02 | |
| 7806 | 192.168.11.63 | sankofa-public-web | r630-01 | |
| 7807 | — | cc-phase1-lab | r630-01 | No static `192.168.11.x` in `net*` (verify inside CT) |
| 7808 | — | cc-phase1-k3s | r630-01 | No static `192.168.11.x` in `net*` (verify inside CT) |
| 7815 | 192.168.11.75 | cc-phase1-lab | r630-02 | Second cc-phase1 lab CT |
| 8604 | 10.160.0.14 | currencicombo-phoenix-1 | r630-01 | Internal overlay; see Phoenix Extensions above |
| 8811 | 192.168.11.111 | sankofa-proxmox-mcp | r630-04 | |
| 8812 | 192.168.11.112 | operator-services | r630-04 | |
| 10000 | 192.168.11.44 | order-postgres-primary | r630-01 | Also referenced as `ORDER_POSTGRES_PRIMARY` |
| 10001 | 192.168.11.45 | order-postgres-replica | r630-01 | |
| 10020 | 192.168.11.38 | order-redis | r630-04 | |
| 10230 | 192.168.11.55 | order-vault | r630-04 | |
| 10900 | 192.168.11.115 | mailcow-dbis | r630-01 | |
---
@@ -460,7 +518,7 @@ Internet
Cloudflare (DNS + DDoS Protection)
NPMplus (VMID 10233: 192.168.0.166:443)
NPMplus (VMID 10233: 192.168.11.167:443)
VM Nginx (443) → Backend Services
```

156
reports/status/drift.json Normal file
View File

@@ -0,0 +1,156 @@
{
"collected_at": "2026-05-11T17:21:42Z",
"guest_count": 137,
"duplicate_ips": {},
"same_name_duplicate_ip_guests": {},
"guest_ips_not_in_ip_addresses_conf": [
"192.168.11.111",
"192.168.11.112",
"192.168.11.113",
"192.168.11.115",
"192.168.11.172",
"192.168.11.173",
"192.168.11.174",
"192.168.11.200",
"192.168.11.213",
"192.168.11.214",
"192.168.11.215",
"192.168.11.217",
"192.168.11.218",
"192.168.11.219",
"192.168.11.220",
"192.168.11.222",
"192.168.11.223",
"192.168.11.224",
"192.168.11.225",
"192.168.11.226",
"192.168.11.227",
"192.168.11.228",
"192.168.11.229",
"192.168.11.233",
"192.168.11.234",
"192.168.11.235",
"192.168.11.236",
"192.168.11.237",
"192.168.11.238",
"192.168.11.243",
"192.168.11.244",
"192.168.11.245",
"192.168.11.246",
"192.168.11.247",
"192.168.11.248",
"192.168.11.249",
"192.168.11.253",
"192.168.11.27",
"192.168.11.29",
"192.168.11.33",
"192.168.11.34",
"192.168.11.35",
"192.168.11.40",
"192.168.11.41",
"192.168.11.42",
"192.168.11.43",
"192.168.11.47",
"192.168.11.49",
"192.168.11.55",
"192.168.11.56",
"192.168.11.57",
"192.168.11.60",
"192.168.11.61",
"192.168.11.62",
"192.168.11.63",
"192.168.11.69",
"192.168.11.70",
"192.168.11.75",
"192.168.11.80",
"192.168.11.82",
"192.168.11.86",
"192.168.11.88",
"192.168.11.89",
"192.168.11.91",
"192.168.11.92",
"192.168.11.93"
],
"ip_addresses_conf_ips_not_on_guests": [
"1.0.0.1",
"1.1.1.1",
"192.168.11.0",
"192.168.11.167",
"192.168.11.19",
"192.168.11.20",
"192.168.11.201",
"192.168.11.203",
"192.168.11.204",
"192.168.11.23",
"192.168.11.255",
"192.168.11.30",
"192.168.11.67",
"192.168.11.68",
"192.168.11.8",
"76.53.10.32",
"76.53.10.40",
"76.53.10.41",
"76.53.10.42"
],
"guest_lan_ips_not_in_declared_sources": [],
"declared_lan11_ips_not_on_live_guests": [
"192.168.11.0",
"192.168.11.167",
"192.168.11.19",
"192.168.11.20",
"192.168.11.201",
"192.168.11.203",
"192.168.11.204",
"192.168.11.23",
"192.168.11.255",
"192.168.11.30",
"192.168.11.67",
"192.168.11.68",
"192.168.11.8"
],
"vmid_ip_mismatch_live_vs_all_vmids_doc": [
{
"vmid": "10233",
"live_ip": "192.168.11.166",
"all_vmids_doc_ip": "192.168.11.167"
}
],
"vmids_in_all_vmids_doc_not_on_cluster": [
"2420",
"2430",
"2440",
"2460",
"2470",
"2480",
"2506",
"2507",
"2508"
],
"vmids_on_cluster_not_in_all_vmids_table": {
"count": 3,
"sample_vmids": [
"7807",
"7808",
"8604"
],
"note": "ALL_VMIDS_ENDPOINTS pipe tables do not list every guest; large count is normal."
},
"hypervisor_and_infra_ips_excluded_from_guest_match": [
"192.168.11.1",
"192.168.11.10",
"192.168.11.11",
"192.168.11.12",
"192.168.11.2",
"192.168.11.24",
"192.168.11.25",
"192.168.11.26",
"76.53.10.33",
"76.53.10.34"
],
"declared_sources": {
"ip_addresses_conf_ipv4_count": 97,
"all_vmids_md_lan11_count": 136,
"all_vmids_md_row_count": 143
},
"notes": []
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,10 @@ from pathlib import Path
from typing import Dict, List, Optional, Any
from collections import defaultdict
_SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR / "lib"))
from proxmox_guest_lan_ips import parse_guest_network_from_config
# Proxmox Hosts
PROXMOX_HOSTS = {
"ml110": "192.168.11.10",
@@ -121,22 +125,17 @@ def get_vm_config(host: str, node: str, vmid: str, vm_type: str) -> Dict[str, An
def get_vm_ip(host: str, node: str, vmid: str, vm_type: str) -> Optional[str]:
"""Get VM IP address"""
config = get_vm_config(host, node, vmid, vm_type)
static_ip = parse_guest_network_from_config(config).primary_ip
if static_ip:
return static_ip
if vm_type == 'lxc':
# For LXC, get IP from config or running container
config = get_vm_config(host, node, vmid, vm_type)
net_config = config.get('net0', '')
if 'ip=' in net_config:
ip_part = net_config.split('ip=')[1].split(',')[0].split('/')[0]
if ip_part and ip_part not in ['dhcp', 'auto']:
return ip_part
# Try to get IP from running container
if config.get('status') == 'running':
ip = run_ssh_command(host, f"pct exec {vmid} -- hostname -I 2>/dev/null | awk '{{print $1}}'")
if ip and not ip.startswith('127.'):
return ip
else:
# For QEMU, try agent
command = f"pvesh get /nodes/{node}/qemu/{vmid}/agent/network-get-interfaces --output-format json"
output = run_ssh_command(host, command)
if output:
@@ -150,7 +149,7 @@ def get_vm_ip(host: str, node: str, vmid: str, vm_type: str) -> Optional[str]:
return ip_info['ip-address']
except json.JSONDecodeError:
pass
return None
def get_vm_hostname(host: str, node: str, vmid: str, vm_type: str) -> Optional[str]:

View File

@@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""Merge live JSON with config/ip-addresses.conf; write live_inventory.json + drift.json."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
IPV4_RE = re.compile(
r"(?<![0-9.])(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?![0-9.])"
)
# VMID | IP | ... (optional ** markdown bold around cells)
MD_VMID_IP_ROW = re.compile(
r"^\|\s*\*{0,2}(\d+)\*{0,2}\s*\|\s*\*{0,2}((?:[0-9]{1,3}\.){3}[0-9]{1,3})\*{0,2}\s*\|"
)
def is_lan_11(ip: str) -> bool:
return ip.startswith("192.168.11.")
def parse_all_vmids_markdown(path: Path) -> tuple[set[str], dict[str, str]]:
"""Extract declared LAN IPs and vmid->ip from ALL_VMIDS pipe tables."""
ips: set[str] = set()
vmid_to_ip: dict[str, str] = {}
if not path.is_file():
return ips, vmid_to_ip
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
m = MD_VMID_IP_ROW.match(line.strip())
if not m:
continue
vmid, ip = m.group(1), m.group(2)
if is_lan_11(ip):
ips.add(ip)
vmid_to_ip[vmid] = ip
return ips, vmid_to_ip
def parse_ip_addresses_conf(path: Path) -> tuple[dict[str, str], set[str]]:
var_map: dict[str, str] = {}
all_ips: set[str] = set()
if not path.is_file():
return var_map, all_ips
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
s = line.strip()
if not s or s.startswith("#") or "=" not in s:
continue
key, _, val = s.partition("=")
key = key.strip()
val = val.strip()
if val.startswith('"') and val.endswith('"'):
val = val[1:-1]
elif val.startswith("'") and val.endswith("'"):
val = val[1:-1]
var_map[key] = val
for m in IPV4_RE.findall(val):
all_ips.add(m)
return var_map, all_ips
def hypervisor_related_keys(var_map: dict[str, str]) -> set[str]:
keys = set()
for k in var_map:
ku = k.upper()
if any(
x in ku
for x in (
"PROXMOX_HOST",
"PROXMOX_ML110",
"PROXMOX_R630",
"PROXMOX_R750",
"WAN_AGGREGATOR",
"NETWORK_GATEWAY",
"UDM_PRO",
"PUBLIC_IP_GATEWAY",
"PUBLIC_IP_ER605",
)
):
keys.add(k)
return keys
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--live", type=Path, help="live JSON file (default stdin)")
ap.add_argument(
"--ip-conf",
type=Path,
default=Path("config/ip-addresses.conf"),
help="path to ip-addresses.conf",
)
ap.add_argument("--out-dir", type=Path, required=True)
ap.add_argument(
"--all-vmids-md",
type=Path,
default=None,
help="optional ALL_VMIDS_ENDPOINTS.md for declared VMID/IP tables",
)
args = ap.parse_args()
if args.live:
live_raw = args.live.read_text(encoding="utf-8")
else:
live_raw = sys.stdin.read()
try:
live = json.loads(live_raw)
except json.JSONDecodeError as e:
print(f"Invalid live JSON: {e}", file=sys.stderr)
sys.exit(1)
guests = live.get("guests") or []
var_map, conf_ips = parse_ip_addresses_conf(args.ip_conf)
doc_ips: set[str] = set()
vmid_to_ip_doc: dict[str, str] = {}
if args.all_vmids_md:
doc_ips, vmid_to_ip_doc = parse_all_vmids_markdown(args.all_vmids_md)
declared_union = conf_ips | doc_ips
hyp_keys = hypervisor_related_keys(var_map)
hyp_ips: set[str] = set()
for k in hyp_keys:
if k not in var_map:
continue
for m in IPV4_RE.findall(var_map[k]):
hyp_ips.add(m)
ip_to_rows: dict[str, list[dict]] = {}
vmid_to_ip_live: dict[str, str] = {}
live_vmids_all: set[str] = set()
for g in guests:
ip = (g.get("ip") or "").strip()
vmid = str(g.get("vmid", "")).strip()
if vmid:
live_vmids_all.add(vmid)
if ip:
ip_to_rows.setdefault(ip, []).append(g)
if vmid and ip:
vmid_to_ip_live[vmid] = ip
doc_vmids = set(vmid_to_ip_doc.keys())
vmids_in_all_vmids_doc_not_on_cluster = sorted(
doc_vmids - live_vmids_all, key=lambda x: int(x) if x.isdigit() else 0
)
only_live_not_in_doc = live_vmids_all - doc_vmids
vmids_on_cluster_not_in_all_vmids_table_count = len(only_live_not_in_doc)
vmids_on_cluster_not_in_all_vmids_table_sample = sorted(
only_live_not_in_doc, key=lambda x: int(x) if x.isdigit() else 0
)[:100]
ip_to_vmids: dict[str, list[str]] = {
ip: [str(r.get("vmid", "") or "?").strip() or "?" for r in rows]
for ip, rows in ip_to_rows.items()
}
duplicate_ips: dict[str, list[str]] = {}
same_name_duplicate_ip: dict[str, list[str]] = {}
for ip, rows in ip_to_rows.items():
if len(rows) < 2:
continue
names = {(str(r.get("name") or "").strip().lower()) for r in rows}
names.discard("")
vmids = [str(r.get("vmid", "") or "?").strip() or "?" for r in rows]
if len(names) == 1:
# Same guest name on multiple VMIDs (e.g. clone/migration) — informational only.
same_name_duplicate_ip[ip] = sorted(vmids, key=lambda x: int(x) if x.isdigit() else 0)
else:
duplicate_ips[ip] = vmids
guest_ip_set = set(ip_to_vmids.keys())
conf_only = sorted(conf_ips - guest_ip_set - hyp_ips)
live_only_legacy = sorted(guest_ip_set - conf_ips)
declared_lan11 = {ip for ip in declared_union if is_lan_11(ip)}
guest_lan11 = {ip for ip in guest_ip_set if is_lan_11(ip)}
guest_lan_not_declared = sorted(
guest_lan11 - declared_union - hyp_ips
)
declared_lan11_not_on_guests = sorted(
declared_lan11 - guest_ip_set - hyp_ips
)
vmid_ip_mismatch: list[dict[str, str]] = []
for vmid, doc_ip in vmid_to_ip_doc.items():
lip = vmid_to_ip_live.get(vmid)
if lip and doc_ip and lip != doc_ip:
vmid_ip_mismatch.append(
{"vmid": vmid, "live_ip": lip, "all_vmids_doc_ip": doc_ip}
)
drift = {
"collected_at": live.get("collected_at"),
"guest_count": len(guests),
"duplicate_ips": duplicate_ips,
"same_name_duplicate_ip_guests": same_name_duplicate_ip,
"guest_ips_not_in_ip_addresses_conf": live_only_legacy,
"ip_addresses_conf_ips_not_on_guests": conf_only,
"guest_lan_ips_not_in_declared_sources": guest_lan_not_declared,
"declared_lan11_ips_not_on_live_guests": declared_lan11_not_on_guests,
"vmid_ip_mismatch_live_vs_all_vmids_doc": vmid_ip_mismatch,
"vmids_in_all_vmids_doc_not_on_cluster": vmids_in_all_vmids_doc_not_on_cluster,
"vmids_on_cluster_not_in_all_vmids_table": {
"count": vmids_on_cluster_not_in_all_vmids_table_count,
"sample_vmids": vmids_on_cluster_not_in_all_vmids_table_sample,
"note": "ALL_VMIDS_ENDPOINTS pipe tables do not list every guest; large count is normal.",
},
"hypervisor_and_infra_ips_excluded_from_guest_match": sorted(hyp_ips),
"declared_sources": {
"ip_addresses_conf_ipv4_count": len(conf_ips),
"all_vmids_md_lan11_count": len(doc_ips),
"all_vmids_md_row_count": len(doc_vmids),
},
"notes": [],
}
if live.get("error"):
drift["notes"].append(str(live["error"]))
if same_name_duplicate_ip:
drift["notes"].append(
"same_name_duplicate_ip_guests: multiple VMIDs share an IP but identical "
"guest name — resolve duplicate CTs/VMs in Proxmox; drift exit code not raised."
)
inv_out = {
"collected_at": live.get("collected_at"),
"source": "proxmox_cluster_pvesh_plus_config",
"guests": guests,
}
neigh = live.get("ip_neigh_vmbr0_sample")
if isinstance(neigh, dict):
inv_out["ip_neigh_vmbr0_sample"] = neigh
args.out_dir.mkdir(parents=True, exist_ok=True)
(args.out_dir / "live_inventory.json").write_text(
json.dumps(inv_out, indent=2), encoding="utf-8"
)
(args.out_dir / "drift.json").write_text(
json.dumps(drift, indent=2), encoding="utf-8"
)
print(f"Wrote {args.out_dir / 'live_inventory.json'}")
print(f"Wrote {args.out_dir / 'drift.json'}")
# Exit 2 only when the same LAN IP is claimed by guests with different names
# (likely address conflict). Same-name clones are in same_name_duplicate_ip_guests only.
sys.exit(2 if duplicate_ips else 0)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Live Proxmox guest inventory + drift vs config/ip-addresses.conf.
# Usage: bash scripts/it-ops/export-live-inventory-and-drift.sh
# Requires: SSH key root@SEED, python3 locally and on PVE.
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
SEED="${SEED_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
OUT_DIR="${OUT_DIR:-${PROJECT_ROOT}/reports/status}"
TS="$(date +%Y%m%d_%H%M%S)"
TMP="${TMPDIR:-/tmp}/live_inv_${TS}.json"
PY="${SCRIPT_DIR}/lib/collect_inventory_remote.py"
LAN_IPS_PY="${PROJECT_ROOT}/scripts/lib/proxmox_guest_lan_ips.py"
mkdir -p "$OUT_DIR"
stub_unreachable() {
python3 - <<'PY'
import json
from datetime import datetime, timezone
print(json.dumps({
"collected_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"error": "seed_unreachable",
"guests": [],
}, indent=2))
PY
}
if ! ping -c1 -W2 "$SEED" >/dev/null 2>&1; then
stub_unreachable >"$TMP"
else
REMOTE_DIR="$(ssh -o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=no \
"root@${SEED}" 'mktemp -d /tmp/pve-inv-collect.XXXXXX')"
cleanup_remote() {
ssh -o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=no \
"root@${SEED}" "rm -rf '${REMOTE_DIR}'" 2>/dev/null || true
}
trap cleanup_remote EXIT
scp -o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=no \
"$LAN_IPS_PY" "$PY" "root@${SEED}:${REMOTE_DIR}/" >/dev/null
REMOTE_ENV=()
case "${IT_COLLECT_IP_NEIGH:-}" in
1|yes|true|TRUE|Yes) REMOTE_ENV+=(IT_COLLECT_IP_NEIGH=1) ;;
esac
if ! ssh -o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=no \
"root@${SEED}" \
"PYTHONPATH='${REMOTE_DIR}' ${REMOTE_ENV[*]} python3 '${REMOTE_DIR}/collect_inventory_remote.py'" \
>"$TMP" 2>/dev/null; then
stub_unreachable >"$TMP"
fi
trap - EXIT
cleanup_remote
fi
set +e
python3 "${SCRIPT_DIR}/compute_ipam_drift.py" --live "$TMP" \
--ip-conf "${PROJECT_ROOT}/config/ip-addresses.conf" \
--all-vmids-md "${PROJECT_ROOT}/docs/04-configuration/ALL_VMIDS_ENDPOINTS.md" \
--out-dir "$OUT_DIR"
DRIFT_RC=$?
set -e
cp -f "$OUT_DIR/live_inventory.json" "${OUT_DIR}/live_inventory_${TS}.json" 2>/dev/null || true
cp -f "$OUT_DIR/drift.json" "${OUT_DIR}/drift_${TS}.json" 2>/dev/null || true
rm -f "$TMP"
if [[ -n "${IT_BFF_SNAPSHOT_DB:-}" ]]; then
python3 "${SCRIPT_DIR}/persist-it-snapshot-sqlite.py" "$IT_BFF_SNAPSHOT_DB" "$OUT_DIR" "${DRIFT_RC}" 2>/dev/null || true
fi
echo "Latest: ${OUT_DIR}/live_inventory.json , ${OUT_DIR}/drift.json"
exit "${DRIFT_RC}"

View File

@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""Run ON a Proxmox cluster node (as root). Stdout: JSON live guest inventory."""
from __future__ import annotations
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
def _parser_search_paths() -> list[Path]:
paths: list[Path] = []
for entry in os.environ.get("PYTHONPATH", "").split(":"):
if entry:
paths.append(Path(entry))
paths.append(_SCRIPT_DIR)
for parent in _SCRIPT_DIR.parents:
paths.append(parent / "lib")
paths.append(parent / "scripts" / "lib")
return paths
for _path in _parser_search_paths():
if (_path / "proxmox_guest_lan_ips.py").is_file():
sys.path.insert(0, str(_path))
break
from proxmox_guest_lan_ips import ( # noqa: E402
parse_guest_network_from_conf_text,
parse_guest_network_from_config,
)
def _run(cmd: list[str]) -> str:
return subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL)
def _read_config(path: str) -> str:
try:
with open(path, encoding="utf-8", errors="replace") as f:
return f.read()
except OSError:
return ""
def _guest_network(
guest_type: str, node: str, vmid_s: str, body: str
) -> tuple[str, str, tuple[str, ...]]:
if body.strip():
net = parse_guest_network_from_conf_text(body)
else:
net = parse_guest_network_from_config({})
if not net.ips:
try:
cfg_raw = _run(
[
"pvesh",
"get",
f"/nodes/{node}/{guest_type}/{vmid_s}/config",
"--output-format",
"json",
]
)
net = parse_guest_network_from_config(json.loads(cfg_raw))
except (subprocess.CalledProcessError, json.JSONDecodeError, OSError):
pass
return net.primary_ip, (net.macs[0] if net.macs else ""), net.ips
def main() -> None:
collected_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
try:
raw = _run(
["pvesh", "get", "/cluster/resources", "--output-format", "json"]
)
resources = json.loads(raw)
except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
json.dump(
{
"collected_at": collected_at,
"error": f"pvesh_cluster_resources_failed: {e}",
"guests": [],
},
sys.stdout,
indent=2,
)
return
guests: list[dict] = []
for r in resources:
t = r.get("type")
if t not in ("lxc", "qemu"):
continue
vmid = r.get("vmid")
node = r.get("node")
if vmid is None or not node:
continue
vmid_s = str(vmid)
name = r.get("name") or ""
status = r.get("status") or ""
if t == "lxc":
cfg_path = f"/etc/pve/nodes/{node}/lxc/{vmid_s}.conf"
else:
cfg_path = f"/etc/pve/nodes/{node}/qemu-server/{vmid_s}.conf"
body = _read_config(cfg_path)
ip, mac, ips = _guest_network(t, str(node), vmid_s, body)
guest: dict = {
"vmid": vmid_s,
"type": t,
"node": str(node),
"name": name,
"status": status,
"ip": ip,
"mac": mac,
"config_path": cfg_path,
}
if len(ips) > 1:
guest["ips"] = list(ips)
guests.append(guest)
out: dict = {
"collected_at": collected_at,
"source": "proxmox_cluster_pvesh_plus_config",
"guests": sorted(guests, key=lambda g: int(g["vmid"])),
}
if os.environ.get("IT_COLLECT_IP_NEIGH", "").strip().lower() in (
"1",
"yes",
"true",
):
neigh_lines: list[str] = []
try:
raw_neigh = subprocess.check_output(
["ip", "-4", "neigh", "show", "dev", "vmbr0"],
text=True,
stderr=subprocess.DEVNULL,
timeout=30,
)
neigh_lines = [
ln.strip() for ln in raw_neigh.splitlines() if ln.strip()
][:500]
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
neigh_lines = []
out["ip_neigh_vmbr0_sample"] = {
"collected_at": collected_at,
"line_count": len(neigh_lines),
"lines": neigh_lines,
}
json.dump(out, sys.stdout, indent=2)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,113 @@
"""Parse static LAN IPv4/MAC from Proxmox LXC/QEMU guest config (net* / ipconfig*)."""
from __future__ import annotations
import re
from dataclasses import dataclass
_IP_VALUE_RE = re.compile(
r"(?:^|[,=])ip=([0-9]{1,3}(?:\.[0-9]{1,3}){3})(?:/|,|$)",
re.IGNORECASE,
)
_HWADDR_RE = re.compile(r"hwaddr=([0-9A-Fa-f:]+)", re.IGNORECASE)
_VIRTIO_MAC_RE = re.compile(
r"(?:^|[,=])virtio=([0-9A-Fa-f:]+)(?:,|$)",
re.IGNORECASE,
)
_NIC_INDEX_RE = re.compile(r"^(net|ipconfig)(\d+)$", re.IGNORECASE)
@dataclass(frozen=True)
class GuestLanNetwork:
ips: tuple[str, ...]
macs: tuple[str, ...]
primary_lan11: str | None
@property
def primary_ip(self) -> str:
if self.primary_lan11:
return self.primary_lan11
return self.ips[0] if self.ips else ""
def _nic_index(key: str) -> tuple[str, int] | None:
m = _NIC_INDEX_RE.match(key)
if not m:
return None
return m.group(1).lower(), int(m.group(2))
def _extract_ipv4(value: str) -> str | None:
m = _IP_VALUE_RE.search(value)
if not m:
return None
ip = m.group(1)
if ip in ("dhcp", "auto"):
return None
return ip
def _extract_mac(value: str) -> str | None:
m = _HWADDR_RE.search(value)
if m:
return m.group(1)
m = _VIRTIO_MAC_RE.search(value)
if m:
return m.group(1)
return None
def _ordered_nic_keys(keys: list[str]) -> list[str]:
indexed: list[tuple[int, int, str]] = []
for key in keys:
parsed = _nic_index(key)
if not parsed:
continue
kind, idx = parsed
kind_order = 0 if kind == "ipconfig" else 1
indexed.append((idx, kind_order, key))
indexed.sort()
return [key for _, _, key in indexed]
def parse_guest_network_from_config(config: dict[str, object]) -> GuestLanNetwork:
"""Return static IPv4/MAC from pvesh config dict or parsed .conf key/value map."""
ips: list[str] = []
macs: list[str] = []
for key in _ordered_nic_keys(list(config.keys())):
raw = config.get(key)
if not isinstance(raw, str):
continue
kind, _ = _nic_index(key) or ("", 0)
if kind == "ipconfig":
ip = _extract_ipv4(raw)
if ip:
ips.append(ip)
continue
if kind == "net":
ip = _extract_ipv4(raw)
if ip:
ips.append(ip)
mac = _extract_mac(raw)
if mac:
macs.append(mac)
dedup_ips = tuple(dict.fromkeys(ips))
dedup_macs = tuple(dict.fromkeys(macs))
primary_lan11 = next((ip for ip in dedup_ips if ip.startswith("192.168.11.")), None)
return GuestLanNetwork(
ips=dedup_ips,
macs=dedup_macs,
primary_lan11=primary_lan11,
)
def parse_guest_network_from_conf_text(body: str) -> GuestLanNetwork:
config: dict[str, str] = {}
for line in body.splitlines():
if ":" not in line:
continue
key, value = line.split(":", 1)
key = key.strip()
if not key:
continue
config[key] = value.strip()
return parse_guest_network_from_config(config)

View File

@@ -150,9 +150,9 @@ declare -A CONTAINERS=(
["10060"]="order-dataroom:${IP_SERVICE_42:-${IP_SERVICE_42:-${IP_SERVICE_42:-192.168.11.42}}}:2048:2:20"
["10070"]="order-legal:${IP_ORDER_LEGAL:-192.168.11.87}:2048:2:20"
["10080"]="order-eresidency:${IP_SERVICE_43:-${IP_SERVICE_43:-${IP_SERVICE_43:-192.168.11.43}}}:2048:2:20"
["10090"]="order-portal-public:${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-192.168.11.36}}}}}}:2048:2:20"
["10091"]="order-portal-internal:${IP_SERVICE_35:-${IP_SERVICE_35:-${IP_SERVICE_35:-${IP_SERVICE_35:-${IP_SERVICE_35:-${IP_SERVICE_35:-192.168.11.35}}}}}}:2048:2:20"
["10092"]="order-mcp-legal:${IP_MIM_WEB:-192.168.11.37}:2048:2:20"
["10090"]="order-portal-public:${ORDER_PORTAL_PUBLIC_IP:-192.168.11.180}:2048:2:20"
["10091"]="order-portal-internal:${ORDER_PORTAL_INTERNAL_IP:-192.168.11.181}:2048:2:20"
["10092"]="order-mcp-legal:${ORDER_MCP_LEGAL_IP:-192.168.11.182}:2048:2:20"
["10100"]="dbis-postgres-primary:${PROXMOX_HOST_ML110}5:4096:4:50"
["10101"]="dbis-postgres-replica-1:${PROXMOX_HOST_ML110}6:4096:4:50"
["10120"]="dbis-redis:${PROXMOX_HOST_R630_02}0:2048:2:20"