Some checks failed
Test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
520 lines
13 KiB
Bash
Executable File
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 "$@"
|
|
|