#!/usr/bin/env python3 """Pull LXC configs from Proxmox via SSH+pct (runs on NAS).""" import json, os, re, ssl, subprocess, urllib.parse, urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parents[1] APPS = ROOT / "apps" PASSWORD = os.environ.get("PROXMOX_PASSWORD", "WaQTUw2t") HOSTS = [("192.168.1.216", "pve"), ("192.168.1.56", "proxmox")] ssl._create_default_https_context = ssl._create_unverified_context def ssh(host: str, cmd: str) -> str: inner = f"apk add --no-cache openssh-client sshpass >/dev/null 2>&1 && sshpass -p '{PASSWORD}' ssh -o StrictHostKeyChecking=no root@{host} {json.dumps(cmd)}" r = subprocess.run(["docker", "run", "--rm", "alpine", "sh", "-c", inner], capture_output=True, text=True, timeout=120) return r.stdout if r.returncode == 0 else "" def pve_login(host: str): data = urllib.parse.urlencode({"username": "root@pam", "password": PASSWORD}).encode() req = urllib.request.Request(f"https://{host}:8006/api2/json/access/ticket", data=data, method="POST") with urllib.request.urlopen(req, timeout=15) as r: a = json.loads(r.read())["data"] return a["ticket"], a["CSRFPreventionToken"] def pve_get(host, path, ticket, csrf): headers = {"Cookie": f"PVEAuthCookie={ticket}", "CSRFPreventionToken": csrf} req = urllib.request.Request(f"https://{host}:8006/api2/json{path}", headers=headers) with urllib.request.urlopen(req, timeout=15) as r: return json.loads(r.read())["data"] def slug(name: str) -> str: return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") def lxc_ip(host, node, vmid, ticket, csrf): try: ifaces = pve_get(host, f"/nodes/{node}/lxc/{vmid}/interfaces", ticket, csrf) for iface in ifaces: for addr in iface.get("ip-addresses", []): if addr.get("ip-address-type") == "inet" and not addr["ip-address"].startswith("127."): return addr["ip-address"] except Exception: pass return "" def pull_running(host, node, vmid, name, ip): sname = slug(name) or f"ct-{vmid}" appdir = APPS / sname cfg = appdir / "config" cfg.mkdir(parents=True, exist_ok=True) find_cmd = f"pct exec {vmid} -- sh -c 'find /opt /root /data /vaultwarden /home -maxdepth 5 \\( -name docker-compose.yml -o -name docker-compose.yaml -o -name compose.yml -o -name .env \\) 2>/dev/null | head -40'" files = [f.strip() for f in ssh(host, find_cmd).splitlines() if f.strip()] for i, fpath in enumerate(files, 1): safe = re.sub(r"[^a-zA-Z0-9._-]", "_", fpath)[:80] content = ssh(host, f"pct exec {vmid} -- cat {json.dumps(fpath)}") if content.strip(): (cfg / f"{i:02d}-{safe}").write_text(content) # NPM data snapshot if "nginx" in sname or vmid == 109: snap = ssh(host, f"pct exec {vmid} -- sh -c 'ls -la /data 2>/dev/null; ls /data/nginx 2>/dev/null'") if snap.strip(): (cfg / "npm-data-listing.txt").write_text(snap) # pve-scripts if "script" in sname: listing = ssh(host, f"pct exec {vmid} -- sh -c 'find /opt/ProxmoxVE-Local -maxdepth 2 -type f 2>/dev/null | head -30'") if listing.strip(): (cfg / "proxmoxve-local-files.txt").write_text(listing) meta = f"""# Auto-generated host: {node} proxmox_ip: {host} vmid: {vmid} hostname: {name} ip: {ip} status: running """ (appdir / "proxmox.meta.yaml").write_text(meta) readme = f"""# {name} | | | |---|---| | **Proxmox** | {node} (CT {vmid}) | | **IP** | {ip or 'dhcp'} | | **Host** | {host} | Config in `config/` (gepull'd van LXC). ```bash # Op Proxmox host: pct enter {vmid} ``` """ (appdir / "README.md").write_text(readme) print(f" pulled {sname} ({ip})") def stub_stopped(host, node, vmid, name): sname = slug(name) or f"ct-{vmid}" appdir = APPS / sname if (appdir / "config").exists() and any((appdir / "config").iterdir()): return # already pulled when was running appdir.mkdir(parents=True, exist_ok=True) (appdir / "config").mkdir(exist_ok=True) meta = f"""# Auto-generated host: {node} proxmox_ip: {host} vmid: {vmid} hostname: {name} status: stopped """ (appdir / "proxmox.meta.yaml").write_text(meta) readme = f"""# {name} | | | |---|---| | **Proxmox** | {node} (CT {vmid}) | | **Status** | gestopt | Container-definitie: `apps/proxmox/hosts/{node}/lxc/{vmid}.conf` Start CT en draai `scripts/pull-lxc-from-proxmox.py` opnieuw om app-config te pullen. """ (appdir / "README.md").write_text(readme) def main(): print(f"Pull → {APPS}") seen = set() for host, node in HOSTS: ticket, csrf = pve_login(host) lxcs = pve_get(host, f"/nodes/{node}/lxc", ticket, csrf) for ct in lxcs: vmid = ct["vmid"] name = ct.get("name") or f"ct-{vmid}" sname = slug(name) if sname in seen: sname = f"{sname}-{node}" seen.add(sname) ip = lxc_ip(host, node, vmid, ticket, csrf) if ct["status"] == "running" else "" if ct["status"] == "running": pull_running(host, node, vmid, name, ip) else: stub_stopped(host, node, vmid, name) print("Klaar.") if __name__ == "__main__": main()