Files
loc_az_hci/scripts/vm-management/create/create-vm-template.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

520 lines
13 KiB
Bash
Executable File

#!/bin/bash
source ~/.bashrc
# Create Proxmox Cloud-Init Template with Best Practices
#
# This script creates an optimized cloud-init template for Proxmox VE
# following best practices for template management and cloud-init configuration.
#
# Reference: https://pve.proxmox.com/pve-docs/qm.1.html
set -e
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m'
# 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=""
TEMPLATE_NAME=""
IMAGE=""
STORAGE="local-lvm"
MEMORY=2048
CORES=2
BRIDGE="vmbr0"
CIUSER="ubuntu"
SSHKEY=""
SSHKEY_FILE=""
IPCONFIG="ip=dhcp"
NAMESERVER=""
SEARCHDOMAIN=""
DESCRIPTION=""
TAGS="template,cloud-init"
NODE=""
SKIP_VERIFICATION=false
OPTIMIZE_TEMPLATE=true
# 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)
TEMPLATE_NAME="$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
;;
--ciuser)
CIUSER="$2"
shift 2
;;
--sshkey)
SSHKEY="$2"
shift 2
;;
--sshkey-file)
SSHKEY_FILE="$2"
shift 2
;;
--ipconfig)
IPCONFIG="$2"
shift 2
;;
--nameserver)
NAMESERVER="$2"
shift 2
;;
--searchdomain)
SEARCHDOMAIN="$2"
shift 2
;;
--description)
DESCRIPTION="$2"
shift 2
;;
--tags)
TAGS="$2"
shift 2
;;
--node)
NODE="$2"
shift 2
;;
--skip-verification)
SKIP_VERIFICATION=true
shift
;;
--no-optimize)
OPTIMIZE_TEMPLATE=false
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 Cloud-Init Template with Best Practices
Usage: $0 [OPTIONS]
Required Options:
--vmid ID VM ID (e.g., 9000)
--name NAME Template name (e.g., "ubuntu-24.04-cloudinit")
--image PATH Full path to cloud image file
Optional Options:
--storage STORAGE Storage pool (default: local-lvm)
--memory MB Memory in MB (default: 2048, minimal for template)
--cores NUM CPU cores (default: 2)
--bridge BRIDGE Network bridge (default: vmbr0)
Cloud-Init Configuration:
--ciuser USER Cloud-Init username (default: ubuntu)
--sshkey KEY SSH public key (or use --sshkey-file)
--sshkey-file FILE Read SSH key from file
--ipconfig CONFIG IP configuration (default: ip=dhcp)
--nameserver DNS DNS servers (space-separated)
--searchdomain DOMAIN Search domains
Template Options:
--description TEXT Template description
--tags TAGS Tags (comma-separated, default: "template,cloud-init")
--node NODE Target Proxmox node
--skip-verification Skip template verification after creation
--no-optimize Skip template optimization steps
Examples:
# Create template from Ubuntu cloud image
$0 --vmid 9000 --name "ubuntu-24.04-cloudinit" \\
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
--sshkey-file ~/.ssh/id_rsa.pub
# Create template with custom configuration
$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 2048 --cores 2 \\
--ciuser ubuntu --sshkey-file ~/.ssh/id_rsa.pub \\
--description "Ubuntu 24.04 LTS Cloud-Init Template"
EOF
}
# Validate required arguments
validate_args() {
if [ -z "$VMID" ]; then
log_error "VMID is required. Use --vmid option."
exit 1
fi
if [ -z "$TEMPLATE_NAME" ]; then
log_error "Template 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
# Load SSH key from file if specified
if [ -n "$SSHKEY_FILE" ]; then
if [ ! -f "$SSHKEY_FILE" ]; then
log_error "SSH key file not found: $SSHKEY_FILE"
exit 1
fi
SSHKEY="$(cat "$SSHKEY_FILE")"
log_info "Loaded SSH key from: $SSHKEY_FILE"
fi
# Validate SSH key format if provided
if [ -n "$SSHKEY" ]; then
if ! echo "$SSHKEY" | grep -qE "^ssh-(rsa|ed25519|ecdsa)"; then
log_warn "SSH key format may be invalid (should start with ssh-rsa, ssh-ed25519, or ecdsa)"
fi
fi
}
# Validate template after creation
verify_template() {
log_step "Verifying template configuration"
local config
config=$(qm config $VMID 2>&1)
if [ $? -ne 0 ]; then
log_error "Failed to read template configuration"
return 1
fi
local errors=0
# Check Cloud-Init is configured
if ! echo "$config" | grep -q "ide2.*cloudinit"; then
log_warn "Cloud-Init drive not found in template"
errors=$((errors + 1))
fi
# Check serial console is enabled
if ! echo "$config" | grep -q "serial0.*socket"; then
log_warn "Serial console not enabled (recommended for cloud-init)"
errors=$((errors + 1))
fi
# Check SSH key is configured
if [ -n "$SSHKEY" ]; then
if ! echo "$config" | grep -q "sshkey"; then
log_warn "SSH key not found in template configuration"
errors=$((errors + 1))
fi
fi
# Check UEFI is enabled
if ! echo "$config" | grep -q "bios.*ovmf"; then
log_warn "UEFI not enabled (recommended for modern images)"
fi
if [ $errors -eq 0 ]; then
log_info "✓ Template configuration verified"
return 0
else
log_warn "Template has $errors configuration warnings"
return 1
fi
}
# Clone template for testing
test_template_clone() {
if [ "$SKIP_VERIFICATION" = true ]; then
return
fi
log_step "Testing template by creating a temporary clone"
local test_vmid=$((VMID + 1000)) # Use a different VMID range
local test_name="${TEMPLATE_NAME}-test-$$"
# Find available VMID
while qm list | grep -q "^\s*$test_vmid\s"; do
test_vmid=$((test_vmid + 1))
done
log_info "Creating test clone: VMID $test_vmid"
# Create linked clone
if ! qm clone $VMID $test_vmid --name "$test_name" > /dev/null 2>&1; then
log_error "Failed to create test clone"
return 1
fi
log_info "✓ Test clone created successfully (VMID: $test_vmid)"
# Clean up test clone
read -p "Delete test clone $test_vmid? (Y/n): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
log_info "Deleting test clone..."
qm destroy $test_vmid --purge
log_info "✓ Test clone deleted"
else
log_info "Test clone preserved. Manual cleanup required: qm destroy $test_vmid --purge"
fi
return 0
}
# Create template using the main script
create_template() {
log_step "Creating template using create-vm-from-image.sh"
# Build command
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local create_script="${script_dir}/create-vm-from-image.sh"
if [ ! -f "$create_script" ]; then
log_error "create-vm-from-image.sh not found at: $create_script"
exit 1
fi
local cmd="$create_script"
cmd="$cmd --vmid $VMID"
cmd="$cmd --name \"$TEMPLATE_NAME\""
cmd="$cmd --image \"$IMAGE\""
cmd="$cmd --storage $STORAGE"
cmd="$cmd --memory $MEMORY"
cmd="$cmd --cores $CORES"
cmd="$cmd --bridge $BRIDGE"
cmd="$cmd --cloud-init"
cmd="$cmd --uefi"
cmd="$cmd --serial"
cmd="$cmd --template"
cmd="$cmd --cpu host"
cmd="$cmd --cache none"
cmd="$cmd --discard"
# Add node if specified
if [ -n "$NODE" ]; then
cmd="$cmd --node $NODE"
fi
# Add Cloud-Init configuration
if [ -n "$CIUSER" ]; then
cmd="$cmd --ciuser $CIUSER"
fi
if [ -n "$SSHKEY" ]; then
cmd="$cmd --sshkey \"$SSHKEY\""
fi
if [ -n "$IPCONFIG" ]; then
cmd="$cmd --ipconfig \"$IPCONFIG\""
fi
if [ -n "$NAMESERVER" ]; then
cmd="$cmd --nameserver \"$NAMESERVER\""
fi
if [ -n "$SEARCHDOMAIN" ]; then
cmd="$cmd --searchdomain \"$SEARCHDOMAIN\""
fi
if [ -n "$DESCRIPTION" ]; then
cmd="$cmd --description \"$DESCRIPTION\""
fi
if [ -n "$TAGS" ]; then
cmd="$cmd --tags \"$TAGS\""
fi
log_info "Executing: $cmd"
echo ""
# Execute the main script
eval "$cmd"
if [ $? -ne 0 ]; then
log_error "Failed to create template"
exit 1
fi
}
# Add template metadata
add_template_metadata() {
log_step "Adding template metadata"
local metadata_desc
if [ -n "$DESCRIPTION" ]; then
metadata_desc="$DESCRIPTION"
else
metadata_desc="Cloud-Init Template - Created $(date +%Y-%m-%d)"
fi
# Update description
qm set $VMID --description "$metadata_desc"
# Ensure tags include template
if [[ ! "$TAGS" =~ template ]]; then
TAGS="template,$TAGS"
fi
# Update tags
qm set $VMID --tags "$TAGS"
log_info "✓ Template metadata added"
}
# Main function
main() {
echo "========================================="
echo "Create Proxmox Cloud-Init Template"
echo "========================================="
echo ""
parse_args "$@"
validate_args
echo ""
log_info "Template Configuration:"
log_info " VMID: $VMID"
log_info " Name: $TEMPLATE_NAME"
log_info " Image: $IMAGE"
log_info " Storage: $STORAGE"
log_info " Memory: ${MEMORY}MB (template minimal)"
log_info " Cores: $CORES"
log_info " Bridge: $BRIDGE"
log_info " Cloud-Init User: $CIUSER"
[ -n "$SSHKEY" ] && log_info " SSH Key: Configured"
[ -n "$DESCRIPTION" ] && log_info " Description: $DESCRIPTION"
[ -n "$TAGS" ] && log_info " Tags: $TAGS"
echo ""
read -p "Continue with template creation? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Aborted by user"
exit 0
fi
echo ""
# Create template
create_template
# Add metadata
add_template_metadata
# Verify template
if [ "$SKIP_VERIFICATION" = false ]; then
echo ""
verify_template
fi
# Test clone if verification enabled
if [ "$SKIP_VERIFICATION" = false ]; then
echo ""
read -p "Test template by creating a temporary clone? (Y/n): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
test_template_clone
fi
fi
echo ""
log_info "========================================="
log_info "Template Creation Complete!"
log_info "========================================="
echo ""
log_info "Template Details:"
qm config $VMID | head -20
echo ""
log_info "Clone template with:"
log_info " qm clone $VMID <new-vmid> --name \"<name>\""
echo ""
log_info "Full clone:"
log_info " qm clone $VMID <new-vmid> --full --name \"<name>\""
echo ""
log_info "After cloning, configure Cloud-Init:"
log_info " qm set <new-vmid> --ciuser $CIUSER"
log_info " qm set <new-vmid> --sshkey \"<ssh-key>\""
log_info " qm set <new-vmid> --ipconfig0 ip=<ip>/24,gw=<gateway>"
}
# Run main function
main "$@"