Files
loc_az_hci/scripts/vm-management/create/create-vm-from-image.sh
defiQUG c39465c2bd
Some checks failed
Test / test (push) Has been cancelled
Initial commit: loc_az_hci (smom-dbis-138 excluded via .gitignore)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 09:04:46 -08:00

723 lines
18 KiB
Bash
Executable File

#!/bin/bash
source ~/.bashrc
# Create Proxmox VM from QCOW2/RAW Image - Comprehensive Automation Script
#
# This script automates the complete workflow for creating a VM from any disk image
# in Proxmox VE using the qm command-line interface.
#
# Reference: https://pve.proxmox.com/pve-docs/qm.1.html
#
# Usage:
# ./scripts/create-vm-from-image.sh --vmid 9000 --name "ubuntu-24.04" \
# --image /path/to/image.img --storage local-lvm
set -e
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# Default values
VMID=""
VMNAME=""
IMAGE=""
STORAGE="local-lvm"
MEMORY=4096
CORES=2
BRIDGE="vmbr0"
VLAN_TAG=""
ENABLE_CLOUD_INIT=false
ENABLE_UEFI=false
ENABLE_TEMPLATE=false
ENABLE_SERIAL=false
CIUSER=""
CIPASSWORD=""
SSHKEY=""
IPCONFIG=""
NAMESERVER=""
SEARCHDOMAIN=""
CPU_TYPE="host"
ENABLE_AGENT=true
IOTHREAD=true
CACHE_MODE="none"
ENABLE_DISCARD=false
BALLOON=0
DESCRIPTION=""
TAGS=""
NODE=""
DRY_RUN=false
# Load environment variables from .env if available
if [ -f .env ]; then
set -a
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
set +a
fi
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--vmid)
VMID="$2"
shift 2
;;
--name)
VMNAME="$2"
shift 2
;;
--image)
IMAGE="$2"
shift 2
;;
--storage)
STORAGE="$2"
shift 2
;;
--memory)
MEMORY="$2"
shift 2
;;
--cores)
CORES="$2"
shift 2
;;
--bridge)
BRIDGE="$2"
shift 2
;;
--vlan)
VLAN_TAG="$2"
shift 2
;;
--cloud-init)
ENABLE_CLOUD_INIT=true
shift
;;
--uefi)
ENABLE_UEFI=true
shift
;;
--template)
ENABLE_TEMPLATE=true
shift
;;
--serial)
ENABLE_SERIAL=true
shift
;;
--ciuser)
CIUSER="$2"
shift 2
;;
--cipassword)
CIPASSWORD="$2"
shift 2
;;
--sshkey)
SSHKEY="$2"
shift 2
;;
--sshkey-file)
if [ -f "$2" ]; then
SSHKEY="$(cat "$2")"
else
log_error "SSH key file not found: $2"
exit 1
fi
shift 2
;;
--ipconfig)
IPCONFIG="$2"
shift 2
;;
--nameserver)
NAMESERVER="$2"
shift 2
;;
--searchdomain)
SEARCHDOMAIN="$2"
shift 2
;;
--cpu)
CPU_TYPE="$2"
shift 2
;;
--no-agent)
ENABLE_AGENT=false
shift
;;
--no-iothread)
IOTHREAD=false
shift
;;
--cache)
CACHE_MODE="$2"
shift 2
;;
--discard)
ENABLE_DISCARD=true
shift
;;
--balloon)
BALLOON="$2"
shift 2
;;
--description)
DESCRIPTION="$2"
shift 2
;;
--tags)
TAGS="$2"
shift 2
;;
--node)
NODE="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--help)
show_help
exit 0
;;
*)
log_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
}
# Show help message
show_help() {
cat << EOF
Create Proxmox VM from QCOW2/RAW Image
Usage: $0 [OPTIONS]
Required Options:
--vmid ID VM ID (e.g., 9000)
--name NAME VM name (e.g., "ubuntu-24.04-cloudinit")
--image PATH Full path to image file
Optional Options:
--storage STORAGE Storage pool (default: local-lvm)
--memory MB Memory in MB (default: 4096)
--cores NUM CPU cores (default: 2)
--bridge BRIDGE Network bridge (default: vmbr0)
--vlan TAG VLAN tag number
Cloud-Init Options:
--cloud-init Enable Cloud-Init support
--ciuser USER Cloud-Init username
--cipassword PASS Cloud-Init password (not recommended)
--sshkey KEY SSH public key (or use --sshkey-file)
--sshkey-file FILE Read SSH key from file
--ipconfig CONFIG IP configuration (e.g., "ip=192.168.1.100/24,gw=192.168.1.1")
--nameserver DNS DNS servers (space-separated)
--searchdomain DOMAIN Search domains
VM Configuration:
--uefi Enable UEFI/OVMF (recommended for modern images)
--cpu TYPE CPU type (default: host, options: host, kvm64, etc.)
--no-agent Disable QEMU Guest Agent
--no-iothread Disable IO thread
--cache MODE Disk cache mode (none, writeback, writethrough)
--discard Enable discard (for thin provisioning)
--balloon MB Memory balloon size in MB
Other Options:
--template Convert to template after creation
--serial Enable serial console
--description TEXT VM description
--tags TAGS Tags (comma-separated, e.g., "dev,web")
--node NODE Target Proxmox node
--dry-run Show commands without executing
--help Show this help message
Examples:
# Basic VM creation
$0 --vmid 9000 --name "ubuntu-24.04" \\
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img
# Full cloud-init VM
$0 --vmid 9000 --name "ubuntu-24.04-cloudinit" \\
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
--storage local-lvm --memory 4096 --cores 2 \\
--cloud-init --uefi --serial \\
--ciuser ubuntu --sshkey-file ~/.ssh/id_rsa.pub \\
--ipconfig "ip=192.168.1.100/24,gw=192.168.1.1"
# Create and convert to template
$0 --vmid 9000 --name "ubuntu-template" \\
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
--cloud-init --uefi --template \\
--ciuser ubuntu --sshkey-file ~/.ssh/id_rsa.pub
EOF
}
# Validate required arguments
validate_args() {
if [ -z "$VMID" ]; then
log_error "VMID is required. Use --vmid option."
exit 1
fi
if [ -z "$VMNAME" ]; then
log_error "VM name is required. Use --name option."
exit 1
fi
if [ -z "$IMAGE" ]; then
log_error "Image path is required. Use --image option."
exit 1
fi
if [ ! -f "$IMAGE" ]; then
log_error "Image file not found: $IMAGE"
exit 1
fi
# Validate VMID is numeric
if ! [[ "$VMID" =~ ^[0-9]+$ ]]; then
log_error "VMID must be numeric: $VMID"
exit 1
fi
# Check if VMID already exists
if qm list | grep -q "^\s*$VMID\s"; then
log_error "VM with ID $VMID already exists"
exit 1
fi
# Validate storage exists
if ! pvesm status | grep -q "^$STORAGE\s"; then
log_warn "Storage '$STORAGE' not found in pvesm status"
log_info "Available storage:"
pvesm status
log_warn "Continuing anyway..."
fi
}
# Validate image
validate_image() {
log_step "Validating image: $IMAGE"
# Check image format
if ! command -v qemu-img &> /dev/null; then
log_warn "qemu-img not found, skipping image validation"
return
fi
local image_info
image_info=$(qemu-img info "$IMAGE" 2>&1)
if [ $? -ne 0 ]; then
log_error "Failed to read image: $IMAGE"
log_error "$image_info"
exit 1
fi
log_info "Image format: $(echo "$image_info" | grep "file format" | awk '{print $3}')"
log_info "Virtual size: $(echo "$image_info" | grep "virtual size" | awk -F'[()]' '{print $2}')"
}
# Create VM shell
create_vm_shell() {
log_step "Creating VM shell (ID: $VMID, Name: $VMNAME)"
local cmd="qm create $VMID --name \"$VMNAME\" --memory $MEMORY --cores $CORES"
# Add node if specified
if [ -n "$NODE" ]; then
cmd="$cmd --target $NODE"
fi
# Configure network
if [ -n "$VLAN_TAG" ]; then
cmd="$cmd --net0 virtio,bridge=$BRIDGE,tag=$VLAN_TAG"
else
cmd="$cmd --net0 virtio,bridge=$BRIDGE"
fi
# Configure CPU
cmd="$cmd --cpu $CPU_TYPE"
# Enable agent
if [ "$ENABLE_AGENT" = true ]; then
cmd="$cmd --agent 1"
fi
# Add description if provided
if [ -n "$DESCRIPTION" ]; then
cmd="$cmd --description \"$DESCRIPTION\""
fi
# Add tags if provided
if [ -n "$TAGS" ]; then
cmd="$cmd --tags $TAGS"
fi
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ VM shell created"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
}
# Import disk
import_disk() {
log_step "Importing disk from image: $IMAGE"
local cmd="qm importdisk $VMID \"$IMAGE\" $STORAGE"
log_info "Command: $cmd"
log_info "This may take several minutes depending on image size..."
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ Disk imported"
# Get the volume name (usually vm-<VMID>-disk-0)
local volume_name="vm-${VMID}-disk-0"
log_info "Imported volume: $volume_name"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
}
# Attach disk
attach_disk() {
log_step "Attaching imported disk"
local volume_name="vm-${VMID}-disk-0"
local cmd="qm set $VMID --scsihw virtio-scsi-pci --scsi0 ${STORAGE}:${volume_name}"
# Add IO thread if enabled
if [ "$IOTHREAD" = true ]; then
cmd="$cmd --iothread 1"
fi
# Add cache mode
cmd="$cmd --cache $CACHE_MODE"
# Add discard if enabled
if [ "$ENABLE_DISCARD" = true ]; then
cmd="$cmd --discard on"
fi
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ Disk attached"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
}
# Configure boot
configure_boot() {
log_step "Configuring boot settings"
local cmd="qm set $VMID --boot order=scsi0"
# Configure BIOS/UEFI
if [ "$ENABLE_UEFI" = true ]; then
cmd="$cmd --bios ovmf --efidisk0 ${STORAGE}:1,format=raw"
log_info "UEFI/OVMF enabled"
else
cmd="$cmd --bios seabios"
log_info "BIOS (SeaBIOS) enabled"
fi
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ Boot configured"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
}
# Configure Cloud-Init
configure_cloud_init() {
log_step "Configuring Cloud-Init"
# Add Cloud-Init drive
local cmd="qm set $VMID --ide2 ${STORAGE}:cloudinit"
# Enable serial console if requested
if [ "$ENABLE_SERIAL" = true ] || [ "$ENABLE_CLOUD_INIT" = true ]; then
cmd="$cmd --serial0 socket --vga serial0"
fi
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
# Configure Cloud-Init user
if [ -n "$CIUSER" ]; then
cmd="qm set $VMID --ciuser $CIUSER"
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
# Configure password (if provided, but not recommended)
if [ -n "$CIPASSWORD" ]; then
cmd="qm set $VMID --cipassword \"$CIPASSWORD\""
log_warn "Setting password via Cloud-Init (not recommended, use SSH keys instead)"
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
# Configure SSH key
if [ -n "$SSHKEY" ]; then
cmd="qm set $VMID --sshkey \"$SSHKEY\""
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ SSH key configured"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
# Configure IP
if [ -n "$IPCONFIG" ]; then
cmd="qm set $VMID --ipconfig0 $IPCONFIG"
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
# Configure DNS
if [ -n "$NAMESERVER" ]; then
cmd="qm set $VMID --nameserver \"$NAMESERVER\""
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
# Configure search domain
if [ -n "$SEARCHDOMAIN" ]; then
cmd="qm set $VMID --searchdomain \"$SEARCHDOMAIN\""
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
if [ "$DRY_RUN" = false ]; then
log_info "✓ Cloud-Init configured"
fi
}
# Configure memory balloon
configure_balloon() {
if [ "$BALLOON" -gt 0 ]; then
log_step "Configuring memory balloon: ${BALLOON}MB"
local cmd="qm set $VMID --balloon $BALLOON"
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ Memory balloon configured"
else
log_info "[DRY RUN] Would execute: $cmd"
fi
fi
}
# Start VM
start_vm() {
log_step "Starting VM"
local cmd="qm start $VMID"
log_info "Command: $cmd"
if [ "$DRY_RUN" = false ]; then
eval "$cmd"
log_info "✓ VM started"
# Show status
sleep 2
qm status $VMID
else
log_info "[DRY RUN] Would execute: $cmd"
fi
}
# Convert to template
convert_to_template() {
if [ "$ENABLE_TEMPLATE" = false ]; then
return
fi
log_step "Converting VM to template"
log_warn "VM must be shut down before converting to template"
if [ "$DRY_RUN" = false ]; then
# Check if VM is running
local status
status=$(qm status $VMID 2>&1 | grep "status:" | awk '{print $2}')
if [ "$status" = "running" ]; then
log_info "VM is running. Shutting down..."
qm shutdown $VMID
log_info "Waiting for shutdown (this may take a minute)..."
local max_wait=60
local waited=0
while [ $waited -lt $max_wait ]; do
status=$(qm status $VMID 2>&1 | grep "status:" | awk '{print $2}')
if [ "$status" != "running" ]; then
break
fi
sleep 2
waited=$((waited + 2))
echo -n "."
done
echo ""
fi
# Convert to template
qm template $VMID
log_info "✓ VM converted to template"
else
log_info "[DRY RUN] Would execute: qm shutdown $VMID && qm template $VMID"
fi
}
# Main function
main() {
echo "========================================="
echo "Create Proxmox VM from Image"
echo "========================================="
echo ""
parse_args "$@"
if [ "$DRY_RUN" = true ]; then
log_warn "DRY RUN MODE - No changes will be made"
echo ""
fi
validate_args
validate_image
echo ""
log_info "VM Configuration:"
log_info " VMID: $VMID"
log_info " Name: $VMNAME"
log_info " Image: $IMAGE"
log_info " Storage: $STORAGE"
log_info " Memory: ${MEMORY}MB"
log_info " Cores: $CORES"
log_info " Bridge: $BRIDGE"
[ -n "$VLAN_TAG" ] && log_info " VLAN: $VLAN_TAG"
[ "$ENABLE_CLOUD_INIT" = true ] && log_info " Cloud-Init: Enabled"
[ "$ENABLE_UEFI" = true ] && log_info " UEFI: Enabled"
[ "$ENABLE_TEMPLATE" = true ] && log_info " Convert to Template: Yes"
echo ""
if [ "$DRY_RUN" = false ]; then
read -p "Continue with VM creation? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Aborted by user"
exit 0
fi
echo ""
fi
create_vm_shell
import_disk
attach_disk
configure_boot
if [ "$ENABLE_CLOUD_INIT" = true ]; then
configure_cloud_init
fi
configure_balloon
if [ "$ENABLE_TEMPLATE" = false ]; then
start_vm
else
log_info "Skipping VM start (will be converted to template)"
fi
convert_to_template
echo ""
log_info "========================================="
log_info "VM Creation Complete!"
log_info "========================================="
if [ "$ENABLE_TEMPLATE" = false ] && [ "$DRY_RUN" = false ]; then
echo ""
log_info "VM Status:"
qm status $VMID
echo ""
log_info "View VM console: qm terminal $VMID"
log_info "View VM config: qm config $VMID"
fi
}
# Run main function
main "$@"