mirror of
https://github.com/ergosteur/ergo-proxmox-scripts.git
synced 2026-10-28 13:00:37 -04:00
Proxmox imports OVA/OVF natively now through a storage with the "import" content type, or qm importovf. The built-in path also handles per-storage disk format conversion, which neither script did. Recorded the known bugs in each header rather than fixing them, since nothing will run these again: import-ova-pve.py imports every VMDK but only attaches disk-0, leaving multi-disk appliances partly orphaned, and assumes a vm-<vmid>-disk-N name that not every storage type uses; import-ova-to-proxmox.sh passes --format qcow2 unconditionally, which fails on ZFS and LVM-thin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
172 lines
5.4 KiB
Bash
Executable File
172 lines
5.4 KiB
Bash
Executable File
#!/bin/bash
|
||
#
|
||
# RETIRED. Kept for reference only.
|
||
#
|
||
# Proxmox imports OVA/OVF natively now, via a storage with the "import" content
|
||
# type: upload the .ova there and use Create VM > Import, or `qm importovf`.
|
||
# The built-in path handles disk format conversion per storage type, which this
|
||
# script did not.
|
||
#
|
||
# Known bug, left unfixed since the script is retired: importdisk is called with
|
||
# --format qcow2 unconditionally, which fails on ZFS and LVM-thin storages that
|
||
# do not take a format argument.
|
||
|
||
set -euo pipefail
|
||
|
||
# === OVF/OVA Importer for Proxmox ===
|
||
# Supports: --force, OVF parsing, accurate storage check, multi-disk VMDK, CPU/RAM
|
||
|
||
FORCE=0
|
||
POSITIONAL=()
|
||
|
||
# === Parse args manually to allow --force anywhere ===
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--force)
|
||
FORCE=1
|
||
;;
|
||
*)
|
||
POSITIONAL+=("$arg")
|
||
;;
|
||
esac
|
||
done
|
||
|
||
set -- "${POSITIONAL[@]}"
|
||
if [[ $# -lt 3 ]]; then
|
||
echo "Usage: $0 <ova|ovf-file> <vmid> <storage> [vm-name] [--force]"
|
||
exit 1
|
||
fi
|
||
|
||
INPUT="$1"
|
||
VMID="$2"
|
||
STORAGE="$3"
|
||
VMNAME="${4:-}"
|
||
|
||
# === Auto-suggest VM name from filename if not given ===
|
||
if [[ -z "$VMNAME" || "$VMNAME" == "--force" ]]; then
|
||
BASENAME=$(basename "$INPUT")
|
||
BASENAME="${BASENAME%.*}"
|
||
VMNAME=$(echo "$BASENAME" | sed -E 's/[^a-zA-Z0-9]+/-/g' | sed -E 's/^-+|-+$//g' | tr '[:upper:]' '[:lower:]')
|
||
echo "==> No VM name provided."
|
||
echo "==> Suggested VM Name: $VMNAME"
|
||
echo "==> Continuing in 5 seconds... (Ctrl-C to cancel)"
|
||
sleep 5
|
||
fi
|
||
|
||
# === Validate VM ID ===
|
||
if qm status "$VMID" &>/dev/null; then
|
||
echo "❌ ERROR: VM ID $VMID already exists"
|
||
exit 1
|
||
else
|
||
echo "✅ VM ID $VMID is available"
|
||
fi
|
||
|
||
# === Validate storage ===
|
||
if ! pvesm status | awk '{print $1}' | grep -qx "$STORAGE"; then
|
||
echo "❌ ERROR: Storage '$STORAGE' not found!"
|
||
echo "👉 Available storages:"
|
||
pvesm status | awk 'NR>1 {print $1}'
|
||
exit 1
|
||
else
|
||
echo "✅ Storage '$STORAGE' exists"
|
||
fi
|
||
|
||
# === Get free space in bytes from pvesm (field 6 × 1024) ===
|
||
STORAGE_FREE_KB=$(pvesm status | awk -v storage="$STORAGE" '$1 == storage {print $6}')
|
||
if [[ -z "$STORAGE_FREE_KB" || "$STORAGE_FREE_KB" -eq 0 ]]; then
|
||
echo "❌ ERROR: Could not determine available space for '$STORAGE'"
|
||
exit 1
|
||
fi
|
||
STORAGE_FREE=$((STORAGE_FREE_KB * 1024))
|
||
|
||
# === Temp workspace ===
|
||
TMPDIR=$(mktemp -d)
|
||
trap 'rm -rf "$TMPDIR"' EXIT
|
||
|
||
echo "==> Preparing to import VM $VMID from $INPUT (name: $VMNAME)"
|
||
|
||
# === Extract OVA or OVF ===
|
||
if [[ "$INPUT" == *.ova ]]; then
|
||
echo "==> Detected OVA bundle. Extracting..."
|
||
tar -xvf "$INPUT" -C "$TMPDIR"
|
||
elif [[ "$INPUT" == *.ovf ]]; then
|
||
echo "==> Detected OVF file. Copying OVF and VMDKs..."
|
||
cp "$INPUT" "$TMPDIR"
|
||
for vmdk in "${INPUT%.ovf}"*.vmdk; do cp "$vmdk" "$TMPDIR"; done
|
||
else
|
||
echo "❌ ERROR: Input must be .ova or .ovf"
|
||
exit 1
|
||
fi
|
||
|
||
cd "$TMPDIR"
|
||
OVF=$(find . -name "*.ovf" | head -n 1)
|
||
[[ -f "$OVF" ]] || { echo "❌ ERROR: OVF file not found!"; exit 1; }
|
||
|
||
# === Get total declared capacity from OVF ===
|
||
REQUIRED_CAPACITY=$(grep -oP 'ovf:capacity="\K[0-9]+' "$OVF" | awk '{sum+=$1} END{print sum}')
|
||
REQUIRED_FMT=$(numfmt --to=iec "$REQUIRED_CAPACITY" 2>/dev/null || echo "$REQUIRED_CAPACITY B")
|
||
STORAGE_FMT=$(numfmt --to=iec "$STORAGE_FREE" 2>/dev/null || echo "$STORAGE_FREE B")
|
||
|
||
echo "🧮 Storage free space: $STORAGE_FMT"
|
||
echo "📦 Total VMDK declared size: $REQUIRED_FMT"
|
||
|
||
if [[ "$FORCE" -eq 1 ]]; then
|
||
echo "⚠️ --force enabled, skipping free space check"
|
||
elif [[ -z "$REQUIRED_CAPACITY" || -z "$STORAGE_FREE" ]]; then
|
||
echo "❌ ERROR: Capacity values missing"
|
||
exit 1
|
||
elif ! [[ "$REQUIRED_CAPACITY" =~ ^[0-9]+$ && "$STORAGE_FREE" =~ ^[0-9]+$ ]]; then
|
||
echo "❌ ERROR: Capacity values not integers"
|
||
exit 1
|
||
elif (( STORAGE_FREE < REQUIRED_CAPACITY )); then
|
||
echo "❌ Not enough free space on storage '$STORAGE'"
|
||
exit 1
|
||
else
|
||
echo "✅ Storage has enough capacity"
|
||
fi
|
||
|
||
# === Parse RAM and CPU ===
|
||
OVF_RAM=$(xmllint --xpath 'string(//*[local-name()="Item"][.//*[local-name()="ResourceType"]="4"]/*[local-name()="VirtualQuantity"])' "$OVF" 2>/dev/null || echo "2048")
|
||
OVF_CPU=$(xmllint --xpath 'string(//*[local-name()="Item"][.//*[local-name()="ResourceType"]="3"]/*[local-name()="VirtualQuantity"])' "$OVF" 2>/dev/null || echo "2")
|
||
|
||
[[ "$OVF_RAM" =~ ^[0-9]+$ ]] || OVF_RAM=2048
|
||
[[ "$OVF_CPU" =~ ^[0-9]+$ ]] || OVF_CPU=2
|
||
|
||
echo "🧠 Parsed RAM from OVF: ${OVF_RAM} MB"
|
||
echo "🧮 Parsed CPU count from OVF: $OVF_CPU"
|
||
|
||
# === Create VM ===
|
||
echo "==> Creating VM $VMID..."
|
||
qm create "$VMID" --name "$VMNAME" --memory "$OVF_RAM" --cores "$OVF_CPU" --net0 virtio,bridge=vmbr0 --ostype l26
|
||
|
||
# === Parse VMDK files reliably ===
|
||
mapfile -t VMDK_FILES < <(xmllint --xpath "//*[local-name()='File']/@*" "$OVF" 2>/dev/null \
|
||
| grep -oE 'href="[^"]+\.vmdk"' | sed -E 's/^href="|"$//g')
|
||
|
||
if [[ ${#VMDK_FILES[@]} -eq 0 ]]; then
|
||
echo "❌ ERROR: No VMDK files found in OVF"
|
||
exit 1
|
||
fi
|
||
|
||
echo "📄 VMDK files to import:"
|
||
for f in "${VMDK_FILES[@]}"; do echo " - $f"; done
|
||
|
||
i=0
|
||
for VMDK in "${VMDK_FILES[@]}"; do
|
||
VMDK_FILE=$(basename "$VMDK")
|
||
[[ -f "$VMDK_FILE" ]] || { echo "❌ Missing VMDK file: $VMDK_FILE"; exit 1; }
|
||
|
||
echo "💾 Importing $VMDK_FILE to $STORAGE..."
|
||
qm importdisk "$VMID" "$VMDK_FILE" "$STORAGE" --format qcow2
|
||
|
||
echo "🔗 Attaching disk as scsi$i..."
|
||
qm set "$VMID" --scsi$i "$STORAGE:vm-$VMID-disk-$i"
|
||
((i++))
|
||
done
|
||
|
||
# === Finalize configuration ===
|
||
qm set "$VMID" --scsihw virtio-scsi-pci --boot c --bootdisk scsi0
|
||
qm set "$VMID" --vga std
|
||
|
||
echo "✅ VM $VMID ('$VMNAME') successfully imported and ready to start!"
|