Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mkol5222/7868dce923b41b490e3f6aab472f82ef to your computer and use it in GitHub Desktop.
Save mkol5222/7868dce923b41b490e3f6aab472f82ef to your computer and use it in GitHub Desktop.
create cloud init template proxmox
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Function to check for required utilities
function check_utilities() {
local utilities=("qm" "wget" "xz" "sha256sum" "ssh-keygen")
for util in "${utilities[@]}"; do
command -v "$util" >/dev/null 2>&1 || { echo "$util not found. Please install it."; exit 1; }
done
}
# Function to set up SSH keys
function setup_ssh_keys() {
local default_ssh_key_dir="${HOME}/ssh-keys"
local ssh_key_dir="$default_ssh_key_dir"
while true; do
# Ensure the SSH key directory exists
if [[ ! -d "${ssh_key_dir}" ]]; then
echo "SSH key directory not found at ${ssh_key_dir}. Creating directory..."
mkdir -p "${ssh_key_dir}"
chmod 700 "${ssh_key_dir}"
fi
# Determine the SSH public key file
ssh_keyfile=$(ls "${ssh_key_dir}"/*.pub 2>/dev/null | head -n 1)
if [[ -z "${ssh_keyfile}" ]]; then
echo "No SSH public key found in ${ssh_key_dir}."
read -p "Do you want to generate a new SSH key pair in this directory? (yes/no): " generate_key
if [[ "${generate_key}" == "yes" ]]; then
ssh-keygen -t rsa -b 4096 -f "${ssh_key_dir}/id_rsa" -N ""
ssh_keyfile="${ssh_key_dir}/id_rsa.pub"
chmod 600 "${ssh_key_dir}/id_rsa"
chmod 644 "${ssh_key_dir}/id_rsa.pub"
echo "SSH key pair generated at ${ssh_key_dir}/id_rsa and ${ssh_keyfile}."
break
else
read -p "Do you want to specify a different directory for your SSH keys? (yes/no): " change_dir
if [[ "${change_dir}" == "yes" ]]; then
read -p "Please enter the full path to your SSH key directory: " ssh_key_dir
else
echo "Cannot proceed without an SSH public key. Exiting."
exit 1
fi
fi
else
echo "Using existing SSH public key: ${ssh_keyfile}"
break
fi
done
# Set permissions on the SSH key files and directory
chmod 700 "${ssh_key_dir}"
chmod 600 "${ssh_key_dir}"/id_* 2>/dev/null || true
chmod 644 "${ssh_key_dir}"/*.pub 2>/dev/null || true
}
# Function to create or update a template
# Args:
# $1: VM ID
# $2: VM Name
# $3: Image file name
function create_template() {
local vm_id="$1"
local vm_name="$2"
local image_file="$3"
echo "Processing template ${vm_name} (${vm_id})"
# Compute the checksum of the image file
local image_checksum
image_checksum=$(sha256sum "${image_file}" | awk '{print $1}')
local checksum_file="checksums/${vm_id}.sha256"
# Check if the template already exists
if qm status "${vm_id}" &>/dev/null; then
echo "Template with VM ID ${vm_id} already exists."
# Check if a checksum file exists
if [[ -f "${checksum_file}" ]]; then
local stored_checksum
stored_checksum=$(cat "${checksum_file}")
echo "Comparing current image checksum with stored checksum..."
if [[ "${image_checksum}" == "${stored_checksum}" ]]; then
echo "Template is up to date. Skipping template creation."
# Remove the image file if it was downloaded
rm -f "${image_file}"
return
else
echo "Image checksum has changed. Deleting and updating template..."
# Delete the existing template and its disks
qm destroy "${vm_id}" --destroy-unreferenced-disks yes
fi
else
echo "No stored checksum found for VM ID ${vm_id}. Deleting and updating template..."
# Delete the existing template and its disks
qm destroy "${vm_id}" --destroy-unreferenced-disks yes
fi
else
echo "Template or checksum file does not exist. Proceeding to download and create/update template."
fi
# Download the image
echo "Downloading ${image_file}..."
if ! wget -N "${image_url}"; then
echo "Failed to download ${image_url}"
exit 1
fi
# If the image is compressed, decompress it
if [[ "${image_file}" == *.xz ]]; then
local decompressed_file="${image_file%.xz}"
if [[ ! -f "${decompressed_file}" || "${image_file}" -nt "${decompressed_file}" ]]; then
echo "Decompressing ${image_file}..."
xz -d -v -f "${image_file}"
image_file="${decompressed_file}"
else
echo "Decompressed file ${decompressed_file} is up to date."
image_file="${decompressed_file}"
fi
fi
# Ensure the image file exists and is not empty
if [[ ! -s "${image_file}" ]]; then
echo "Downloaded image file ${image_file} is missing or empty."
exit 1
fi
# Compute the checksum of the new image file
local image_checksum
image_checksum=$(sha256sum "${image_file}" | awk '{print $1}')
# Check if template exists and delete if necessary
if qm status "${vm_id}" &>/dev/null; then
echo "Deleting existing template with VM ID ${vm_id}..."
qm destroy "${vm_id}" --destroy-unreferenced-disks yes
fi
echo "Creating template ${vm_name} (${vm_id})"
# Create new VM
qm create "${vm_id}" --name "${vm_name}" --ostype l26
# Set networking to default bridge
qm set "${vm_id}" --net0 virtio,bridge=vmbr0
# Set display to serial
qm set "${vm_id}" --serial0 socket --vga serial0
# Set memory, CPU, and type defaults
qm set "${vm_id}" --memory 1024 --cores 4 --cpu host
# Import the disk
qm set "${vm_id}" --scsi0 "${storage}:0,import-from=${PWD}/${image_file},discard=on"
# Set SCSI hardware as default boot disk using virtio SCSI single
qm set "${vm_id}" --boot order=scsi0 --scsihw virtio-scsi-single
# Enable QEMU guest agent
qm set "${vm_id}" --agent enabled=1,fstrim_cloned_disks=1
# Add cloud-init device
qm set "${vm_id}" --ide2 "${storage}:cloudinit"
# Set cloud-init network configuration
qm set "${vm_id}" --ipconfig0 "ip=dhcp,ip6=auto"
# Import the SSH keyfile
if [[ -f "${ssh_keyfile}" ]]; then
qm set "${vm_id}" --sshkeys "${ssh_keyfile}"
else
echo "SSH key file not found at ${ssh_keyfile}"
exit 1
fi
# Add the user
qm set "${vm_id}" --ciuser "${username}"
# Resize the disk to 8G
qm disk resize "${vm_id}" scsi0 8G || true
# Convert the VM into a template
qm template "${vm_id}"
# Save the checksum
mkdir -p checksums
echo "${image_checksum}" > "${checksum_file}"
# Remove the image file when done
rm -f "${image_file}"
}
# Check for required utilities
check_utilities
# User-configurable variables
export username="hackiri" # Replace with your desired username
export storage="ceph_local" # Replace with your Proxmox storage name
# Validate variables
if [[ -z "${username}" || "${username}" == "your_username_here" ]]; then
echo "Please set a valid username in the script."
exit 1
fi
if ! pvesm status | grep -q "^${storage}\s"; then
echo "Storage '${storage}' not found. Please check your Proxmox storage configuration."
exit 1
fi
# Set up SSH keys
setup_ssh_keys
# Array of images to download and create templates from
declare -a images=(
# Format: "VM_ID|VM_NAME|IMAGE_URL"
# Debian 11 (Bullseye)
"901|debian-11-template|https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-genericcloud-amd64.qcow2"
# Debian 12 (Bookworm)
"902|debian-12-template|https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
# Debian 13 (Trixie, daily)
"903|debian-13-template|https://cloud.debian.org/images/cloud/trixie/daily/latest/debian-13-genericcloud-amd64-daily.qcow2"
# Debian Sid (unstable)
"909|debian-sid-template|https://cloud.debian.org/images/cloud/sid/daily/latest/debian-sid-genericcloud-amd64-daily.qcow2"
# Ubuntu 20.04 LTS (Focal Fossa)
"910|ubuntu-20.04-template|https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img"
# Ubuntu 22.04 LTS (Jammy Jellyfish)
"911|ubuntu-22.04-template|https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"
# Ubuntu 24.04 (Lunar Lobster)
"912|ubuntu-24.04-template|https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
# Fedora 39
"920|fedora-39-template|https://fedora.mirror.constant.com/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2"
# Fedora 40
"921|fedora-40-template|https://fedora.mirror.constant.com/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Generic.x86_64-40-1.14.qcow2"
# Rocky Linux 8
"930|rocky-8-template|https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2"
# Rocky Linux 9
"931|rocky-9-template|https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2"
# Alpine Linux 3.19.1
"940|alpine-3.19-template|https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.1-x86_64-bios-cloudinit-r0.qcow2"
)
# Loop through the images array
for entry in "${images[@]}"; do
IFS='|' read -r vm_id vm_name image_url <<< "${entry}"
# Extract the filename from the URL
image_file="${image_url##*/}"
# Download the image with timestamping
echo "Downloading ${image_file}..."
if ! wget -N "${image_url}"; then
echo "Failed to download ${image_url}"
exit 1
fi
# If the image is compressed, decompress it
if [[ "${image_file}" == *.xz ]]; then
decompressed_file="${image_file%.xz}"
if [[ ! -f "${decompressed_file}" || "${image_file}" -nt "${decompressed_file}" ]]; then
echo "Decompressing ${image_file}..."
xz -d -v -f "${image_file}"
image_file="${decompressed_file}"
else
echo "Decompressed file ${decompressed_file} is up to date."
image_file="${decompressed_file}"
fi
fi
# Ensure the image file exists and is not empty
if [[ ! -s "${image_file}" ]]; then
echo "Downloaded image file ${image_file} is missing or empty."
exit 1
fi
# Create or update the template
create_template "${vm_id}" "${vm_name}" "${image_file}"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment