Files
proxmox-to-sentri/proxmox-to-sentri.sh

313 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
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}"
# Sentri API configuration
: "${SENTRI_URL:?Missing SENTRI_URL}"
: "${SENTRI_TOKEN:?Missing SENTRI_TOKEN}"
SERVERS_JSON="[]"
# 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")
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
| if . == "running" then "Running"
elif . == "stopped" then "Off"
else "Running"
end
')
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' \
| awk '
function ceil(x) { return (x == int(x)) ? x : int(x)+1 }
{
if ($0 ~ /G$/) {
gsub(/G/, "", $0)
printf "%d", ceil($0)
}
else if ($0 ~ /M$/) {
gsub(/M/, "", $0)
printf "%d", ceil($0 / 1024)
}
else if ($0 ~ /T$/) {
gsub(/T/, "", $0)
printf "%d", ceil($0 * 1024)
}
else {
printf "%d", $0
}
}
')
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
SERVER_JSON=$(
jq -n \
--arg vmid "$MAC" \
--arg vmhost "$NAME" \
--arg power "$STATUS" \
--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",
}
+
(
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
[{"-proxmox":"no"}]
end
),
"server_description": $description
}
')
# Save per-VM JSON
echo "$SERVER_JSON" > "${OUTPUT_DIR}/${VMID}.json"
# Add VM to combined payload
SERVERS_JSON=$(jq -n \
--argjson old "$SERVERS_JSON" \
--argjson server "$SERVER_JSON" \
'$old + [$server]')
done < <(
echo "$VMS" | jq -c '.data[]? // empty'
)
FINAL_PAYLOAD=$(jq -n \
--argjson servers "$SERVERS_JSON" '
{
"servers": $servers
}
')
# Save combined payload
echo "$FINAL_PAYLOAD" | jq . > "${OUTPUT_DIR}/servers.json"
echo
echo "Uploading payload to Sentri..."
RESPONSE=$(curl -sk \
-X POST \
-H "Authorization: Bearer ${SENTRI_TOKEN}" \
-H "Content-Type: application/json" \
--data "$FINAL_PAYLOAD" \
"${SENTRI_URL}"
)
echo
echo "Sentri API response:"
echo "$RESPONSE" | jq . || echo "$RESPONSE"
echo "Combined payload written to:"
echo "${OUTPUT_DIR}/servers.json"
echo "Individual server json files written to: ${OUTPUT_DIR}/"
echo
echo "Done."