Last active
December 3, 2024 03:46
-
-
Save Hackiri/f3709f3173a818c04518e604617e5c5a to your computer and use it in GitHub Desktop.
create cloud init template proxmox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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
Functionality,Description
Utility Checks,Verifies that required utilities (qm, wget, xz, sha256sum, ssh-keygen) are installed.
User Configuration,Allows users to set variables like username and storage for the VM templates.
SSH Key Management,Checks for an existing SSH public key or prompts the user to generate one; allows specifying a directory; sets appropriate permissions on keys and directory.
Cloud Image Handling,Downloads specified cloud images for various Linux distributions; uses timestamping to avoid unnecessary downloads; decompresses images if necessary.
Template Creation,Checks if a VM template with the specified ID exists; updates or skips creation based on image checksum; creates new VM with specified configurations and converts it into a template.
Checksum Management,Computes and stores checksums of images to detect changes; saves checksums in a checksums/ directory.
Cleanup,Removes downloaded image files after processing to conserve disk space.
User Interaction,Provides prompts to guide the user in SSH key setup; outputs informative messages about progress and any issues encountered.
Note,Intended to run on a Proxmox VE host; requires appropriate permissions; users should set username and storage; script handles errors and exits gracefully if misconfigured.