initial commit
This commit is contained in:
3
.env-sample
Normal file
3
.env-sample
Normal file
@@ -0,0 +1,3 @@
|
||||
PM_HOST="https://proxmox.example.com:8006"
|
||||
PM_USER="root@pam"
|
||||
PM_PASS="super-secret-password"
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
/output/
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Marco Mooij
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
84
README.md
Normal file
84
README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Proxmox to Sentri
|
||||
Proxmox to Sentri is a utility script that retrieves all virtual machines from a Proxmox cluster and imports them into Sentri as servers.
|
||||
|
||||
The script collects information such as:
|
||||
|
||||
- VM name
|
||||
- Power state
|
||||
- CPU and memory allocation
|
||||
- Disk configuration
|
||||
- IPv4 and IPv6 addresses
|
||||
- Snapshot count
|
||||
- Backup availability
|
||||
- Operating system information
|
||||
- VM descriptions/notes
|
||||
|
||||
---
|
||||
# Requirements
|
||||
## System Requirements
|
||||
The machine running this script must:
|
||||
|
||||
- Have network access to the Proxmox web interface/API
|
||||
- Have the following packages installed:
|
||||
- `curl`
|
||||
- `jq`
|
||||
- `git` (required for installation)
|
||||
|
||||
## Proxmox Requirements
|
||||
A Proxmox user account with permission to view VMs and storage information is required.
|
||||
|
||||
Recommended role:
|
||||
- `PVEAuditor`
|
||||
## Optional: Guest IP Address Collection
|
||||
To collect IPv4, IPv6, and operating system information from virtual machines, install the QEMU Guest Agent inside each VM.
|
||||
|
||||
Example for AlmaLinux/RHEL:
|
||||
```bash
|
||||
sudo dnf install -y qemu-guest-agent
|
||||
sudo systemctl enable --now qemu-guest-agent
|
||||
```
|
||||
Then enable the QEMU Guest Agent in Proxmox:
|
||||
```
|
||||
qm set <VMID> --agent enabled=1
|
||||
```
|
||||
|
||||
---
|
||||
# Installation
|
||||
Clone the repository and configure the environment file:
|
||||
```
|
||||
cd /optgit clone https://gitea.mooij.me/meteo/proxmox-to-sentri.gitcd proxmox-to-sentricp .env-sample .envchmod 600 .envchmod 740 proxmox-to-sentri.sh
|
||||
```
|
||||
Optional: Create a global command:
|
||||
```
|
||||
ln -sf /opt/proxmox-to-sentri/proxmox-to-sentri.sh /usr/bin/proxmox-to-sentri
|
||||
```
|
||||
|
||||
---
|
||||
# Configuration
|
||||
Edit the `.env` file and configure the Proxmox connection settings:
|
||||
```
|
||||
PM_HOST="https://proxmox.example.com:8006"
|
||||
PM_USER="root@pam"
|
||||
PM_PASS="your-password"
|
||||
```
|
||||
|
||||
---
|
||||
# Running the Script
|
||||
|
||||
Run the script directly:
|
||||
```
|
||||
./proxmox-to-sentri.sh
|
||||
```
|
||||
Or, if you created the symlink:
|
||||
```
|
||||
proxmox-to-sentri
|
||||
```
|
||||
|
||||
---
|
||||
# Output
|
||||
|
||||
Generated JSON files are written to:
|
||||
```
|
||||
/opt/proxmox-to-sentri/output/
|
||||
```
|
||||
One JSON file is generated per VM.
|
||||
269
proxmox-to-sentri.sh
Executable file
269
proxmox-to-sentri.sh
Executable file
@@ -0,0 +1,269 @@
|
||||
#!/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}/"
|
||||
Reference in New Issue
Block a user