Initial commit: loc_az_hci (smom-dbis-138 excluded via .gitignore)
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
519
scripts/vm-management/create/create-vm-template.sh
Executable file
519
scripts/vm-management/create/create-vm-template.sh
Executable file
@@ -0,0 +1,519 @@
|
||||
#!/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 "$@"
|
||||
|
||||
Reference in New Issue
Block a user