create-pve-template.sh
· 4.8 KiB · Bash
Brut
#!/bin/bash
set -e
TEMPLATE_URL="https://cloud-images.ubuntu.com/plucky/current/plucky-server-cloudimg-amd64.img"
VMID=9001
TEMPLATE_NAME="ubuntu-25.04-cloudimg-temp"
STORAGE="local-lvm"
SKIP_DISK_SETUP=false
VMID_CUSTOM=false # Track if VMID was explicitly set
# Parser avec getopt
OPTS=$(getopt -o "" --long vmid:,template:,storage:,skip-disk -n 'script' -- "$@")
eval set -- "$OPTS"
while true; do
case "$1" in
--vmid) VMID="$2"; VMID_CUSTOM=true; shift 2 ;;
--template) TEMPLATE_NAME="$2"; shift 2 ;;
--storage) STORAGE="$2"; shift 2 ;;
--skip-disk) SKIP_DISK_SETUP=true; shift ;;
--) shift; break ;;
*) break ;;
esac
done
# Detect if we need sudo (non-root user)
if [ "$(id -u)" -ne 0 ]; then
echo "Running as non-root user, will use sudo for qm commands"
SUDO="sudo"
else
SUDO=""
fi
# Check if VMID already exists
ORIGINAL_VMID=$VMID
echo "Checking if VMID $VMID is available..."
if $SUDO qm status "$VMID" &>/dev/null; then
# VMID exists, check if it's already a template
IS_TEMPLATE=$($SUDO qm config "$VMID" | grep -q "^template:" && echo "yes" || echo "no")
if [ "$IS_TEMPLATE" = "yes" ]; then
echo "✓ VMID $VMID already exists and is a template (idempotent)"
echo ""
echo "========================================="
echo "Template already exists!"
echo " VMID: $VMID"
echo "========================================="
echo ""
echo "Skipping template creation (already exists)."
exit 0
else
echo "⚠ VMID $VMID already exists but is NOT a template!"
# If VMID is the default (9001) and was not explicitly set, error out
# This ensures Packer can find the expected template
if [ "$VMID_CUSTOM" = "false" ]; then
echo ""
echo "========================================="
echo "ERROR: Default VMID $VMID is occupied!"
echo "========================================="
echo ""
echo "VMID $VMID exists but is not a template."
echo ""
echo "Solutions:"
echo " 1. Delete the VM: qm destroy $VMID"
echo " 2. Convert it to template: qm template $VMID"
echo " 3. Use a different VMID: $0 --vmid XXXX"
echo ""
exit 1
fi
# If VMID was explicitly set, auto-increment to next available
echo "Finding next available VMID..."
MAX_VMID=$($SUDO qm list | awk 'NR>1 {print $1}' | sort -n | tail -1)
if [ -z "$MAX_VMID" ]; then
# No VMs exist, use the requested VMID
echo "No existing VMs found, using VMID $VMID"
else
# Use max VMID + 1
VMID=$((MAX_VMID + 1))
echo "✓ Using next available VMID: $VMID (max existing: $MAX_VMID)"
fi
fi
else
echo "✓ VMID $VMID is available"
fi
# Récupérer les clés SSH
SSH_KEYS=$(curl -s https://gist.vorpax.dev/vorpax/ssh/raw/HEAD/ssh-keys.pub | sed 's/%20/ /g; s/%0A/\n/g; s/%3D/=/g; s/%2F/\//g; s/%40/@/g; s/%3A/:/g')
cd /tmp
if [ "$SKIP_DISK_SETUP" = false ]; then
echo "Downloading cloud image..."
wget -O "${TEMPLATE_NAME}_disk" "$TEMPLATE_URL"
# Créer la VM avec une config minimale
$SUDO qm create "$VMID" \
--name "$TEMPLATE_NAME" \
--memory 3072 \
--cores 3 \
--scsihw virtio-scsi-pci \
--serial0 socket \
--vga serial0 \
--net0 virtio,bridge=vmbr0
# Importer le disque
echo "Importing disk..."
$SUDO qm importdisk "$VMID" "${TEMPLATE_NAME}_disk" "$STORAGE"
# Extraire le chemin complet du disque unused (la partie APRÈS "unused0: ")
DISK_PATH=$($SUDO qm config "$VMID" | grep "^unused0:" | awk '{print $2}')
if [ -z "$DISK_PATH" ]; then
echo "ERROR: Could not find unused disk"
exit 1
fi
echo "Attaching disk: $DISK_PATH"
$SUDO qm set "$VMID" --scsi0 "$DISK_PATH"
# Cleanup temporary downloaded file
rm -f "${TEMPLATE_NAME}_disk"
else
echo "Skipping disk setup (--skip-disk flag)"
fi
# Créer le dossier des clés SSH
SSH_KEYS_DIR="ssh_keys_${VMID}"
mkdir -p "$SSH_KEYS_DIR"
# Écrire toutes les clés dans un seul fichier
SSH_KEY_FILE="${SSH_KEYS_DIR}/authorized_keys"
echo "$SSH_KEYS" > "$SSH_KEY_FILE"
echo "SSH keys written to $SSH_KEY_FILE"
# Configurer cloud-init et le reste
$SUDO qm set "$VMID" \
--sshkey "$SSH_KEY_FILE" \
--ide2 "${STORAGE}:cloudinit" \
--boot order=scsi0 \
--ciuser packer \
--searchdomain .lab.internal \
--ipconfig0 ip=dhcp
--bios ovmf
# Convertir en template
$SUDO qm template "$VMID"
echo ""
echo "========================================="
echo "✓ Template created successfully!"
echo " VMID: $VMID"
echo " Name: $TEMPLATE_NAME"
echo "========================================="
echo ""
echo "Template is ready for Packer to clone and provision."
| 1 | #!/bin/bash |
| 2 | set -e |
| 3 | |
| 4 | TEMPLATE_URL="https://cloud-images.ubuntu.com/plucky/current/plucky-server-cloudimg-amd64.img" |
| 5 | VMID=9001 |
| 6 | TEMPLATE_NAME="ubuntu-25.04-cloudimg-temp" |
| 7 | STORAGE="local-lvm" |
| 8 | SKIP_DISK_SETUP=false |
| 9 | VMID_CUSTOM=false # Track if VMID was explicitly set |
| 10 | |
| 11 | # Parser avec getopt |
| 12 | OPTS=$(getopt -o "" --long vmid:,template:,storage:,skip-disk -n 'script' -- "$@") |
| 13 | eval set -- "$OPTS" |
| 14 | |
| 15 | while true; do |
| 16 | case "$1" in |
| 17 | --vmid) VMID="$2"; VMID_CUSTOM=true; shift 2 ;; |
| 18 | --template) TEMPLATE_NAME="$2"; shift 2 ;; |
| 19 | --storage) STORAGE="$2"; shift 2 ;; |
| 20 | --skip-disk) SKIP_DISK_SETUP=true; shift ;; |
| 21 | --) shift; break ;; |
| 22 | *) break ;; |
| 23 | esac |
| 24 | done |
| 25 | |
| 26 | # Detect if we need sudo (non-root user) |
| 27 | if [ "$(id -u)" -ne 0 ]; then |
| 28 | echo "Running as non-root user, will use sudo for qm commands" |
| 29 | SUDO="sudo" |
| 30 | else |
| 31 | SUDO="" |
| 32 | fi |
| 33 | |
| 34 | # Check if VMID already exists |
| 35 | ORIGINAL_VMID=$VMID |
| 36 | echo "Checking if VMID $VMID is available..." |
| 37 | if $SUDO qm status "$VMID" &>/dev/null; then |
| 38 | # VMID exists, check if it's already a template |
| 39 | IS_TEMPLATE=$($SUDO qm config "$VMID" | grep -q "^template:" && echo "yes" || echo "no") |
| 40 | |
| 41 | if [ "$IS_TEMPLATE" = "yes" ]; then |
| 42 | echo "✓ VMID $VMID already exists and is a template (idempotent)" |
| 43 | echo "" |
| 44 | echo "=========================================" |
| 45 | echo "Template already exists!" |
| 46 | echo " VMID: $VMID" |
| 47 | echo "=========================================" |
| 48 | echo "" |
| 49 | echo "Skipping template creation (already exists)." |
| 50 | exit 0 |
| 51 | else |
| 52 | echo "⚠ VMID $VMID already exists but is NOT a template!" |
| 53 | |
| 54 | # If VMID is the default (9001) and was not explicitly set, error out |
| 55 | # This ensures Packer can find the expected template |
| 56 | if [ "$VMID_CUSTOM" = "false" ]; then |
| 57 | echo "" |
| 58 | echo "=========================================" |
| 59 | echo "ERROR: Default VMID $VMID is occupied!" |
| 60 | echo "=========================================" |
| 61 | echo "" |
| 62 | echo "VMID $VMID exists but is not a template." |
| 63 | echo "" |
| 64 | echo "Solutions:" |
| 65 | echo " 1. Delete the VM: qm destroy $VMID" |
| 66 | echo " 2. Convert it to template: qm template $VMID" |
| 67 | echo " 3. Use a different VMID: $0 --vmid XXXX" |
| 68 | echo "" |
| 69 | exit 1 |
| 70 | fi |
| 71 | |
| 72 | # If VMID was explicitly set, auto-increment to next available |
| 73 | echo "Finding next available VMID..." |
| 74 | MAX_VMID=$($SUDO qm list | awk 'NR>1 {print $1}' | sort -n | tail -1) |
| 75 | |
| 76 | if [ -z "$MAX_VMID" ]; then |
| 77 | # No VMs exist, use the requested VMID |
| 78 | echo "No existing VMs found, using VMID $VMID" |
| 79 | else |
| 80 | # Use max VMID + 1 |
| 81 | VMID=$((MAX_VMID + 1)) |
| 82 | echo "✓ Using next available VMID: $VMID (max existing: $MAX_VMID)" |
| 83 | fi |
| 84 | fi |
| 85 | else |
| 86 | echo "✓ VMID $VMID is available" |
| 87 | fi |
| 88 | |
| 89 | # Récupérer les clés SSH |
| 90 | SSH_KEYS=$(curl -s https://gist.vorpax.dev/vorpax/ssh/raw/HEAD/ssh-keys.pub | sed 's/%20/ /g; s/%0A/\n/g; s/%3D/=/g; s/%2F/\//g; s/%40/@/g; s/%3A/:/g') |
| 91 | |
| 92 | cd /tmp |
| 93 | |
| 94 | if [ "$SKIP_DISK_SETUP" = false ]; then |
| 95 | echo "Downloading cloud image..." |
| 96 | wget -O "${TEMPLATE_NAME}_disk" "$TEMPLATE_URL" |
| 97 | |
| 98 | # Créer la VM avec une config minimale |
| 99 | $SUDO qm create "$VMID" \ |
| 100 | --name "$TEMPLATE_NAME" \ |
| 101 | --memory 3072 \ |
| 102 | --cores 3 \ |
| 103 | --scsihw virtio-scsi-pci \ |
| 104 | --serial0 socket \ |
| 105 | --vga serial0 \ |
| 106 | --net0 virtio,bridge=vmbr0 |
| 107 | |
| 108 | # Importer le disque |
| 109 | echo "Importing disk..." |
| 110 | $SUDO qm importdisk "$VMID" "${TEMPLATE_NAME}_disk" "$STORAGE" |
| 111 | |
| 112 | # Extraire le chemin complet du disque unused (la partie APRÈS "unused0: ") |
| 113 | DISK_PATH=$($SUDO qm config "$VMID" | grep "^unused0:" | awk '{print $2}') |
| 114 | |
| 115 | if [ -z "$DISK_PATH" ]; then |
| 116 | echo "ERROR: Could not find unused disk" |
| 117 | exit 1 |
| 118 | fi |
| 119 | |
| 120 | echo "Attaching disk: $DISK_PATH" |
| 121 | $SUDO qm set "$VMID" --scsi0 "$DISK_PATH" |
| 122 | |
| 123 | # Cleanup temporary downloaded file |
| 124 | rm -f "${TEMPLATE_NAME}_disk" |
| 125 | else |
| 126 | echo "Skipping disk setup (--skip-disk flag)" |
| 127 | fi |
| 128 | |
| 129 | # Créer le dossier des clés SSH |
| 130 | SSH_KEYS_DIR="ssh_keys_${VMID}" |
| 131 | mkdir -p "$SSH_KEYS_DIR" |
| 132 | |
| 133 | # Écrire toutes les clés dans un seul fichier |
| 134 | SSH_KEY_FILE="${SSH_KEYS_DIR}/authorized_keys" |
| 135 | echo "$SSH_KEYS" > "$SSH_KEY_FILE" |
| 136 | echo "SSH keys written to $SSH_KEY_FILE" |
| 137 | |
| 138 | # Configurer cloud-init et le reste |
| 139 | $SUDO qm set "$VMID" \ |
| 140 | --sshkey "$SSH_KEY_FILE" \ |
| 141 | --ide2 "${STORAGE}:cloudinit" \ |
| 142 | --boot order=scsi0 \ |
| 143 | --ciuser packer \ |
| 144 | --searchdomain .lab.internal \ |
| 145 | --ipconfig0 ip=dhcp |
| 146 | --bios ovmf |
| 147 | |
| 148 | # Convertir en template |
| 149 | $SUDO qm template "$VMID" |
| 150 | |
| 151 | echo "" |
| 152 | echo "=========================================" |
| 153 | echo "✓ Template created successfully!" |
| 154 | echo " VMID: $VMID" |
| 155 | echo " Name: $TEMPLATE_NAME" |
| 156 | echo "=========================================" |
| 157 | echo "" |
| 158 | echo "Template is ready for Packer to clone and provision." |