#!/usr/bin/env bash set -euo pipefail # ----------------------------------------------------------------------------- # Proxmox VM Inventory Exporter # # Exports one JSON object per VM with: # - CPU / RAM # - Network IPs # - Snapshots # - Backup detection # - VM notes # - Disk information # # Requirements: # - curl # - jq # - for ipv4 and ipv6 collection install the proxmox agent on the VM # # Files: # .env # # Example .env: # PM_HOST="https://proxmox.example.com:8006" # PM_USER="root@pam" # PM_PASS="password" # # Output: # ./output/.json # ----------------------------------------------------------------------------- SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")" SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)" OUTPUT_DIR="${SCRIPT_DIR}/output" mkdir -p "$OUTPUT_DIR" ENV_FILE="${SCRIPT_DIR}/.env" # Load environment variables if [[ -f "$ENV_FILE" ]]; then set -a source "$ENV_FILE" set +a else echo ".env file missing: $ENV_FILE" exit 1 fi # Validate vars : "${PM_HOST:?Missing PM_HOST}" : "${PM_USER:?Missing PM_USER}" : "${PM_PASS:?Missing PM_PASS}" # Authenticate AUTH_RESPONSE=$(curl -sk \ --data-urlencode "username=${PM_USER}" \ --data-urlencode "password=${PM_PASS}" \ "${PM_HOST}/api2/json/access/ticket") TICKET=$(echo "$AUTH_RESPONSE" | jq -r '.data.ticket // empty') CSRF=$(echo "$AUTH_RESPONSE" | jq -r '.data.CSRFPreventionToken // empty') if [[ "$TICKET" == "null" || -z "$TICKET" ]]; then echo "Authentication failed" exit 1 fi api_get() { local endpoint="$1" curl -sk \ -H "Cookie: PVEAuthCookie=${TICKET}" \ -H "CSRFPreventionToken: ${CSRF}" \ "${PM_HOST}${endpoint}" } echo "Collecting VMs..." VMS=$(api_get "/api2/json/cluster/resources?type=vm") echo "$VMS" | jq -c '.data[]' | while read -r VM; do VMID=$(echo "$VM" | jq -r '.vmid') NODE=$(echo "$VM" | jq -r '.node') NAME=$(echo "$VM" | jq -r '.name') STATUS=$(echo "$VM" | jq -r '.status') CPU=$(echo "$VM" | jq -r '.maxcpu') MEMORY_MB=$(echo "$VM" | jq -r '.maxmem / 1024 / 1024 | floor') echo "Processing VM ${VMID} (${NAME})" # VM Config CONFIG=$(api_get "/api2/json/nodes/${NODE}/qemu/${VMID}/config") DESCRIPTION=$(echo "$CONFIG" | jq -r '.data.description // empty') # Try to detect OS OS_INFO=$(api_get "/api2/json/nodes/${NODE}/qemu/${VMID}/agent/get-osinfo" || true) OS=$(echo "$OS_INFO" | jq -r ' .data["pretty-name"] // empty ') # Snapshot count SNAPSHOTS=$(api_get "/api2/json/nodes/${NODE}/qemu/${VMID}/snapshot") SNAPSHOT_COUNT=$(echo "$SNAPSHOTS" | jq '.data | length - 1') # Backup detection BACKUP_ENABLED="no" STORAGES=$(api_get "/api2/json/storage" | jq -r '.data[].storage') for STORAGE in $STORAGES; do CONTENT=$(api_get "/api2/json/nodes/${NODE}/storage/${STORAGE}/content" || true) if echo "$CONTENT" | jq -e --arg VMID "$VMID" ' .data[]? | select( .volid | test("vzdump-qemu-" + $VMID + "-") ) ' >/dev/null; then BACKUP_ENABLED="yes" break fi done # Network interfaces IPV4=() IPV6=() AGENT=$(api_get "/api2/json/nodes/${NODE}/qemu/${VMID}/agent/network-get-interfaces" || true) if echo "$AGENT" | jq -e '.data.result' >/dev/null 2>&1; then while read -r IP; do IPV4+=("$IP") done < <( echo "$AGENT" | jq -r ' .data.result[]."ip-addresses"[]? | select(."ip-address-type" == "ipv4") | ."ip-address" ' \ | grep -v '^127\.' \ | grep -v '^172\.17\.' \ | grep -v '^172\.18\.' \ | grep -v '^172\.19\.' \ | grep -v '^172\.20\.' \ | grep -v '^172\.21\.' \ | grep -v '^172\.22\.' \ | grep -v '^172\.30\.' ) while read -r IP; do IPV6+=("$IP") done < <( echo "$AGENT" | jq -r ' .data.result[]."ip-addresses"[]? | select(."ip-address-type" == "ipv6") | ."ip-address" ' \ | grep -v '^::1' \ | grep -v '^fe80:' ) fi # MAC Address MAC=$(echo "$CONFIG" | jq -r ' .data.net0 // "" ' | sed -n 's/.*virtio=\([^,]*\).*/\1/p' \ | tr '[:upper:]' '[:lower:]' \ | tr -d ':') # Disks DISKS_JSON="[]" while read -r DISKKEY; do DISKCONF=$(echo "$CONFIG" | jq -r ".data.${DISKKEY}") STORAGE=$(echo "$DISKCONF" | cut -d':' -f1) DISK_LOCATION=$(echo "$DISKCONF" | cut -d',' -f1) SIZE=$(echo "$DISKCONF" | sed -n 's/.*size=\([^,]*\).*/\1/p') DISKS_JSON=$(echo "$DISKS_JSON" | jq \ --arg name "$DISKKEY" \ --arg size "$SIZE" \ --arg used "$SIZE" \ --arg location "$DISK_LOCATION" \ '. += [{ "disk_name": $name, "disk_space": $size, "disk_used": $used, "disk_location": $location }]' ) done < <( echo "$CONFIG" | jq -r ' .data | keys[] | select(test("^(virtio|scsi|sata|ide)[0-9]+$")) ' ) # Build JSON jq -n \ --arg vmid "$MAC" \ --arg vmhost "$NAME" \ --arg power "$STATUS" \ --arg hostname "$NAME" \ --arg os "$OS" \ --argjson cpu "$CPU" \ --argjson memory "$MEMORY_MB" \ --argjson snapshot "$SNAPSHOT_COUNT" \ --arg description "$DESCRIPTION" \ --argjson disks "$DISKS_JSON" \ --argjson ipv4 "$(printf '%s\n' "${IPV4[@]}" | jq -R . | jq -s .)" \ --argjson ipv6 "$(printf '%s\n' "${IPV6[@]}" | jq -R . | jq -s .)" \ --arg backup "$BACKUP_ENABLED" ' { "server_vm_id": $vmid, "server_vm_host_name": $vmhost, "server_power_state": $power, "server_state": "new", "server_hostname": $hostname } + ( if $os != "" then { "server_os": $os } else {} end ) + { "server_cpu": $cpu, "server_memory": $memory, "server_memory_demand": $memory, "server_disks": $disks, "server_ipv4": $ipv4, "server_ipv6": $ipv6, "server_vm_snapshot": $snapshot, "server_backup": ( if $backup == "yes" then [{"+proxmox":"yes"}] else [] end ), "server_description": $description } ' > "${OUTPUT_DIR}/${VMID}.json" done echo echo "Done." echo "JSON files written to: ${OUTPUT_DIR}/"