#!/usr/bin/env bash # # proxmox-inventory-json.sh # --------------------------- # Baut dasselbe Inventar wie proxmox-inventory.sh / inventory.sh, aber als # JSON, und sendet es per curl an einen n8n-Webhook. Das bestehende # Text-Script bleibt unverändert für manuelle Ausführung nutzbar. # # Voraussetzung: "jq" muss installiert sein. # apt install -y jq # # Nutzung: # chmod +x proxmox-inventory-json.sh # ./proxmox-inventory-json.sh # baut JSON + sendet an n8n (alle VMIDs) # ./proxmox-inventory-json.sh --dry-run # baut JSON, zeigt es nur an, sendet NICHT # ./proxmox-inventory-json.sh --print-only > inventory.json # nur JSON in Datei # ./proxmox-inventory-json.sh --vmids=101,102,138 # nur diese VMIDs verarbeiten (schnell zum Testen) # # Alternativ dauerhaft in der VMID_FILTER-Variable unten eintragen, z.B. VMID_FILTER="101,102,138". # Leer lassen ("") um wie gewohnt ALLE Container/VMs zu verarbeiten. # --vmids= überschreibt die Variable, falls beides gesetzt ist. # # Geplant ausführen (Cron, z. B. täglich 06:00 Uhr): # crontab -e # 0 6 * * * /root/proxmox-inventory-json.sh >> /var/log/proxmox-inventory.log 2>&1 # set -euo pipefail # --------------------------------------------------------------------------- # KONFIGURATION - hier eure n8n-Webhook-URL eintragen # --------------------------------------------------------------------------- N8N_WEBHOOK_URL="https://n8n.example.com/webhook/REPLACE_ME" # Komma-getrennte Liste von VMIDs, die verarbeitet werden sollen, z.B. "101,102,138" # Leer lassen ("") um wie gewohnt ALLE Container/VMs zu verarbeiten. VMID_FILTER="" DRY_RUN=false PRINT_ONLY=false for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=true ;; --print-only) PRINT_ONLY=true ;; --vmids=*) VMID_FILTER="${arg#--vmids=}" ;; esac done # Prüft, ob eine VMID laut Filter verarbeitet werden soll. # Ohne Filter (VMID_FILTER leer) wird alles verarbeitet. should_process() { local vmid="$1" [ -z "$VMID_FILTER" ] && return 0 case ",${VMID_FILTER}," in *",${vmid},"*) return 0 ;; *) return 1 ;; esac } if ! command -v jq >/dev/null 2>&1; then echo "'jq' ist nicht installiert - versuche automatische Installation..." >&2 if command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq jq fi command -v jq >/dev/null 2>&1 || { echo "FEHLER: 'jq' konnte nicht automatisch installiert werden. Bitte manuell ausführen: apt install -y jq" >&2; exit 1; } echo "'jq' erfolgreich installiert." >&2 fi cidr_to_netmask() { local cidr="$1" if ! [[ "$cidr" =~ ^[0-9]+$ ]] || [ "$cidr" -lt 0 ] || [ "$cidr" -gt 32 ]; then echo "" return fi local mask=0 if [ "$cidr" -gt 0 ]; then mask=$(( 0xffffffff ^ ((1 << (32 - cidr)) - 1) )) fi printf "%d.%d.%d.%d" \ "$(( (mask >> 24) & 255 ))" \ "$(( (mask >> 16) & 255 ))" \ "$(( (mask >> 8) & 255 ))" \ "$(( mask & 255 ))" } TMP_LXC=$(mktemp) TMP_VM=$(mktemp) trap 'rm -f "$TMP_LXC" "$TMP_VM"' EXIT # --------------------------------------------------------------------------- # LXC Container # --------------------------------------------------------------------------- for VMID in $(pct list | awk 'NR>1 {print $1}'); do should_process "$VMID" || continue NAME=$(pct list | awk -v id="$VMID" '$1==id{print $NF}') STATUS=$(pct status "$VMID" 2>/dev/null | awk '{print $2}') CONFIG=$(pct config "$VMID" 2>/dev/null || true) HOSTNAME_CFG=$(echo "$CONFIG" | grep -E '^hostname:' | cut -d' ' -f2- || echo "") CORES=$(echo "$CONFIG" | grep -E '^cores:' | cut -d' ' -f2- || echo "") MEMORY=$(echo "$CONFIG" | grep -E '^memory:' | cut -d' ' -f2- || echo "") SWAP=$(echo "$CONFIG" | grep -E '^swap:' | cut -d' ' -f2- || echo "") DISK=$(echo "$CONFIG" | grep -E '^rootfs:' | grep -oP 'size=\K[^,]+' || echo "") STORAGE_POOL=$(echo "$CONFIG" | grep -E '^rootfs:' | cut -d' ' -f2- | cut -d: -f1 || echo "") NET0_LINE=$(echo "$CONFIG" | grep -E '^net0:' || true) CFG_GW=$(echo "$NET0_LINE" | grep -oP 'gw=\K[0-9.]+' || echo "") CFG_CIDR=$(echo "$NET0_LINE" | grep -oP 'ip=[0-9.]+/\K[0-9]+' || echo "") MAC=$(echo "$NET0_LINE" | grep -oP 'hwaddr=\K[0-9A-Fa-f:]+' || echo "") IP="" LIVE_GW="" LIVE_CIDR="" if [ "$STATUS" = "running" ]; then IP=$(pct exec "$VMID" -- ip -4 -o addr show scope global 2>/dev/null | head -1 | awk '{print $4}' | cut -d/ -f1 || echo "") LIVE_CIDR=$(pct exec "$VMID" -- ip -4 -o addr show scope global 2>/dev/null | head -1 | awk '{print $4}' | cut -d/ -f2 || echo "") LIVE_GW=$(pct exec "$VMID" -- ip -4 route show default 2>/dev/null | awk '{print $3}' | head -1 || echo "") fi GW="${LIVE_GW:-$CFG_GW}" CIDR="${LIVE_CIDR:-$CFG_CIDR}" NETMASK="" [ -n "$CIDR" ] && NETMASK=$(cidr_to_netmask "$CIDR") jq -n \ --argjson vmid "$VMID" \ --arg type "lxc" \ --arg hostname "${HOSTNAME_CFG:-$NAME}" \ --arg status "$STATUS" \ --arg node "$(hostname)" \ --arg cores "${CORES:-null}" \ --arg memory_mb "${MEMORY:-null}" \ --arg swap_mb "${SWAP:-null}" \ --arg disk "$DISK" \ --arg storage_pool "$STORAGE_POOL" \ --arg ip "$IP" \ --arg gateway "$GW" \ --arg netmask "$NETMASK" \ --arg mac "$MAC" \ '{ vmid: $vmid, type: $type, hostname: $hostname, status: $status, proxmox_node: $node, cores: (if $cores == "null" then null else ($cores|tonumber) end), memory_mb: (if $memory_mb == "null" then null else ($memory_mb|tonumber) end), swap_mb: (if $swap_mb == "null" then null else ($swap_mb|tonumber) end), disk: $disk, storage_pool: $storage_pool, ip: $ip, gateway: $gateway, netmask: $netmask, mac: $mac }' >> "$TMP_LXC" done # --------------------------------------------------------------------------- # VMs # --------------------------------------------------------------------------- for VMID in $(qm list | awk 'NR>1 {print $1}'); do should_process "$VMID" || continue NAME=$(qm list | awk -v id="$VMID" '$1==id{print $2}') STATUS=$(qm status "$VMID" 2>/dev/null | awk '{print $2}') CONFIG=$(qm config "$VMID" 2>/dev/null || true) CORES=$(echo "$CONFIG" | grep -E '^cores:' | cut -d' ' -f2- || echo "") MEMORY=$(echo "$CONFIG" | grep -E '^memory:' | cut -d' ' -f2- || echo "") DISKS_JSON="[]" DISKS_RAW=$(echo "$CONFIG" | grep -E '^(scsi|sata|virtio|ide)[0-9]+:' || true) if [ -n "$DISKS_RAW" ]; then DISKS_TMP=$(mktemp) echo "$DISKS_RAW" | while IFS= read -r LINE; do DEV=$(echo "$LINE" | cut -d: -f1) SIZE=$(echo "$LINE" | grep -oP 'size=\K[^,]+' || echo "") jq -n --arg device "$DEV" --arg size "$SIZE" '{device:$device, size:$size}' done > "$DISKS_TMP" DISKS_JSON=$(jq -s '.' "$DISKS_TMP") rm -f "$DISKS_TMP" fi IPCONFIG_LINE=$(echo "$CONFIG" | grep -E '^ipconfig0:' || true) CFG_GW=$(echo "$IPCONFIG_LINE" | grep -oP 'gw=\K[0-9.]+' || echo "") CFG_CIDR=$(echo "$IPCONFIG_LINE" | grep -oP 'ip=[0-9.]+/\K[0-9]+' || echo "") IP="" LIVE_CIDR="" if [ "$STATUS" = "running" ]; then AGENT_JSON=$(qm guest cmd "$VMID" network-get-interfaces 2>/dev/null || echo "") if [ -n "$AGENT_JSON" ]; then IP=$(echo "$AGENT_JSON" | grep -o '"ip-address":"[^"]*"' | grep -v '127.0.0.1' | cut -d'"' -f4 | head -1 || echo "") LIVE_CIDR=$(echo "$AGENT_JSON" | grep -oP '"prefix":\K[0-9]+' | head -1 || echo "") fi fi GW="${CFG_GW:-}" CIDR="${LIVE_CIDR:-$CFG_CIDR}" NETMASK="" [ -n "$CIDR" ] && NETMASK=$(cidr_to_netmask "$CIDR") jq -n \ --argjson vmid "$VMID" \ --arg type "vm" \ --arg hostname "$NAME" \ --arg status "$STATUS" \ --arg node "$(hostname)" \ --arg cores "${CORES:-null}" \ --arg memory_mb "${MEMORY:-null}" \ --argjson disks "$DISKS_JSON" \ --arg ip "$IP" \ --arg gateway "$GW" \ --arg netmask "$NETMASK" \ '{ vmid: $vmid, type: $type, hostname: $hostname, status: $status, proxmox_node: $node, cores: (if $cores == "null" then null else ($cores|tonumber) end), memory_mb: (if $memory_mb == "null" then null else ($memory_mb|tonumber) end), disks: $disks, ip: $ip, gateway: $gateway, netmask: $netmask }' >> "$TMP_VM" done CONTAINERS_JSON=$(jq -s '.' "$TMP_LXC") VMS_JSON=$(jq -s '.' "$TMP_VM") # Prüfen, ob per --vmids/VMID_FILTER angeforderte VMIDs in Proxmox nicht existieren MISSING_JSON="[]" if [ -n "$VMID_FILTER" ]; then ALL_KNOWN_VMIDS=$( (pct list | awk 'NR>1{print $1}'; qm list | awk 'NR>1{print $1}') | sort -u) MISSING_TMP=$(mktemp) IFS=',' read -ra REQUESTED_VMIDS <<< "$VMID_FILTER" for rid in "${REQUESTED_VMIDS[@]}"; do if ! echo "$ALL_KNOWN_VMIDS" | grep -qx "$rid"; then jq -n --arg vmid "$rid" '{vmid: ($vmid|tonumber), reason: "VMID in Proxmox nicht gefunden (weder pct noch qm list)"}' >> "$MISSING_TMP" fi done if [ -s "$MISSING_TMP" ]; then MISSING_JSON=$(jq -s '.' "$MISSING_TMP") fi rm -f "$MISSING_TMP" fi PAYLOAD=$(jq -n \ --arg host "$(hostname)" \ --arg ts "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \ --argjson containers "$CONTAINERS_JSON" \ --argjson vms "$VMS_JSON" \ --argjson missing "$MISSING_JSON" \ '{proxmox_host: $host, generated_at: $ts, containers: $containers, vms: $vms, missing_vmids: $missing}') if [ "$PRINT_ONLY" = true ]; then echo "$PAYLOAD" exit 0 fi echo "$PAYLOAD" | jq '.' >&2 if [ "$DRY_RUN" = true ]; then echo "Dry-Run: es wurde NICHTS an n8n gesendet." >&2 exit 0 fi HTTP_CODE=$(curl -s -o /tmp/n8n-response.json -w "%{http_code}" \ -X POST "$N8N_WEBHOOK_URL" \ -H "Content-Type: application/json" \ --data "$PAYLOAD") if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then echo "OK: an n8n gesendet (HTTP $HTTP_CODE)" >&2 else echo "FEHLER: n8n antwortete mit HTTP $HTTP_CODE" >&2 cat /tmp/n8n-response.json >&2 exit 1 fi