#!/usr/bin/env python3 """Run ON a Proxmox cluster node (as root). Stdout: JSON live guest inventory.""" from __future__ import annotations import json import re import subprocess import sys from datetime import datetime, timezone def _run(cmd: list[str]) -> str: return subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL) def _extract_ip_from_net_line(line: str) -> str | None: m = re.search(r"ip=([0-9.]+)", line) return m.group(1) if m else None def _read_config(path: str) -> str: try: with open(path, encoding="utf-8", errors="replace") as f: return f.read() except OSError: return "" 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 = "" for line in body.splitlines(): if line.startswith("net0:"): got = _extract_ip_from_net_line(line) if got: ip = got break if not ip and t == "qemu": for line in body.splitlines(): if line.startswith("ipconfig0:"): got = _extract_ip_from_net_line(line) if got: ip = got break if not ip and t == "qemu": for line in body.splitlines(): if line.startswith("net0:"): got = _extract_ip_from_net_line(line) if got: ip = got break guests.append( { "vmid": vmid_s, "type": t, "node": str(node), "name": name, "status": status, "ip": ip, "config_path": cfg_path, } ) out = { "collected_at": collected_at, "guests": sorted(guests, key=lambda g: int(g["vmid"])), } json.dump(out, sys.stdout, indent=2) if __name__ == "__main__": main()