Files
proxmox/scripts/it-ops/export-live-inventory-and-drift.sh
defiQUG 1f44a50a25 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>
2026-05-11 10:22:59 -07:00

73 lines
2.6 KiB
Bash
Executable File

#!/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}"