Files
proxmox-to-sentri/proxmox-to-sentri.sh
2026-05-17 19:02:44 +02:00

269 lines
6.1 KiB
Bash
Executable File

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