Blog

Building Proxmox Templates via Script

By Jay

Jun 25, 2023 | 5 minutes read

Series: HomeLab

Tags: blog, tech, proxmox

As someone who frequently spins up and tears down virtual machines for both professional and personal projects, I’ve tried my fair share of virtualization platforms. From Joyent Triton and VMWare to Rancher Harvester and Virtualbox, I’ve explored a variety of options. However, I keep coming back to Proxmox Why? Because it’s the simplest way for me to manage my VMs without having to be a full-time administrator.

Proxmox, coupled with its integrated Ceph block storage allows me to focus on the tasks that need my attention rather than managing the underlying virtualization technology. It’s a powerful, open-source server virtualization management solution that allows you to virtualize both Linux and Windows application workloads.

One of the key benefits of Proxmox is its user-friendly web interface. You can easily start, stop, and manage VMs, or even check the console of a VM right from your web browser. But what if you need to create multiple VMs with similar configurations? That’s where the Proxmox CLI and API come in handy.

One of the more annoying tasks I found myself doing over and over was to build templates for the various Ubuntu LTS versions that I regularly use. So, to make my life a bit easier, I wrote a Bash script that automates the process of downloading Ubuntu cloud images and creating corresponding Proxmox templates. This script has opinions on what it’s doing - for example, I am setting the cpu, memory, network parameters, and disk size to what I use for testing - but you can edit the script directly to change those values. The script does prompt for username, password, and sshkey file (but there is no reason why you can’t modify it to accept other arguments).

Here’s the script:

#!/bin/bash

# Author: Jason Schmidt
# License: Apache 2.0

# This script downloads Ubuntu cloud images and creates corresponding
# Proxmox VMs with cloud-init enabled. It sets up DHCP for all network
# interfaces and sets a username, password, and SSH key for each VM.

# Display usage information
usage() {
    echo "Usage: $0 [-u username] [-p password] [-k sshkey]"
    echo
    echo "Options:"
    echo "  -u  Specify the username for the VM."
    echo "  -p  Specify the password for the VM."
    echo "  -k  Specify the path to the SSH public key."
    echo "  -h  Display this help message."
    exit 1
}

# Array of Ubuntu versions
versions=("bionic" "jammy" "kinetic" "lunar" "focal")

# Base URL for Ubuntu cloud images
base_url="https://cloud-images.ubuntu.com"

# VMID start value
vmid=9100

# Where do we store our volumes?
storage="ceph01"

# Parse command-line arguments for username, password, and SSH key path
while getopts u:p:k:h flag; do
    case "${flag}" in
        u) username=${OPTARG} ;;
        p) password=${OPTARG} ;;
        k) sshkey=${OPTARG} ;;
        h) usage ;;
        *) usage ;;
    esac
done

# If variables are not provided as command-line arguments, prompt the user for them
if [[ -z "$username" ]]; then
  read -r -p "Enter your username: " username
fi
if [[ -z "$password" ]]; then
  read -r -s -p "Enter your password: " password
  echo
fi
if [[ -z "$sshkey" ]]; then
  read -r -p "Enter the path to your SSH public key: " sshkey
fi

# Check if all required variables are provided
if [[ -z "$username" || -z "$password" || -z "$sshkey" ]]; then
  echo "Error: You must provide a username, password, and SSH key path."
  exit 1
fi

# Check if the SSH key file exists
if [[ ! -f "$sshkey" ]]; then
  echo "Error: The SSH key file does not exist."
  exit 1
fi

# VM specifications
memory=8196 # Memory in MB
cores=4     # Number of CPUs

# Loop over each Ubuntu version
for version in "${versions[@]}"; do
  # Check if a template with the VMID already exists and delete it if it does
  if qm status $vmid >/dev/null 2>&1; then
    qm stop $vmid
    qm destroy $vmid
  fi

  # Download the cloud image for the current Ubuntu version
  url="${base_url}/${version}/current/${version}-server-cloudimg-amd64.img"
  curl "{$url}" -O

  image="${version}-server-cloudimg-amd64.img"

  # Edit the image to autoinstall some packages for us.
  # Check if virt-customize is installed
  if ! command -v virt-customize &>/dev/null; then
    echo "virt-customize is not installed."
    echo "You can install it on Debian-based systems with the following command:"
    echo "sudo apt-get install libguestfs-tools"
  else
    virt-customize -a "$image" --firstboot-install build-essential,curl,jq,libbz2-dev,libffi-dev,liblzma-dev,libncursesw5-dev,libreadline-dev,libsqlite3-dev,libssl-dev,libxml2-dev,libxmlsec1-dev,llvm,make,tk-dev,wget,xz-utils,zlib1g-dev,unzip,qemu-guest-agent --firstboot-command 'systemctl enable qemu-guest-agent'
  fi

  # Create a new VM for the current Ubuntu version
  name="${version}-template"
  qm create $vmid --name "$name" --memory "$memory" --cores "$cores" --net0 virtio,bridge=vmbr0 --cipassword "$password" --sshkeys "$sshkey" --ipconfig0 ip=dhcp --agent enabled=1
  qm importdisk "$vmid" "$image" "$storage"
  qm set $vmid --scsihw virtio-scsi-pci --scsi0 "$storage":vm-${vmid}-disk-0
  qm set $vmid --ide2 "$storage":cloudinit
  qm set $vmid --boot c --bootdisk scsi0
  qm set $vmid --serial0 socket --vga serial0
  qm resize "$vmid" scsi0 +126G
  qm template "$vmid"

  # Remvove the intermediate file
  rm -f "$image"

  # Increment VMID for the next iteration
  ((vmid++))
done

This script starts by defining an array of Ubuntu versions and a base URL for Ubuntu cloud images. It then loops over each Ubuntu version, checks if a template with the VMID already exists and deletes it if it does, downloads the cloud image for the current Ubuntu version, and creates a new template.

The script also creates cloud-init user-data and network-config files, updates the cloud-init drive with these files, and increments the VMID for the next iteration. This is just one of the many scripts and tools I use to make my life easier; there will be more to follow.

By leveraging the power of Proxmox and the flexibility of Bash scripting, I’ve been able to significantly reduce the time and effort required to manage my VMs. If you’re in a similar situation where you need to frequently spin up and tear down VMs, I highly recommend giving Proxmox and this Bash script a try. Happy virtualizing!