Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mkol5222/7868dce923b41b490e3f6aab472f82ef to your computer and use it in GitHub Desktop.

Select an option

Save mkol5222/7868dce923b41b490e3f6aab472f82ef to your computer and use it in GitHub Desktop.

Revisions

  1. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 8 additions and 5 deletions.
    13 changes: 8 additions & 5 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -236,20 +236,23 @@ declare -a images=(
    # 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 23.10 (Mantic Minotaur)
    "912|ubuntu-23.10-template|https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-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://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2"
    "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.18
    "940|alpine-3.18-template|https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-standard-3.18.0-x86_64.iso"
    # 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
  2. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 38 additions and 2 deletions.
    40 changes: 38 additions & 2 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -100,10 +100,46 @@ function create_template() {
    qm destroy "${vm_id}" --destroy-unreferenced-disks yes
    fi
    else
    echo "Template with VM ID ${vm_id} does not exist. Creating new template."
    echo "Template or checksum file does not exist. Proceeding to download and create/update template."
    fi

    # Proceed with template creation
    # 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
  3. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  4. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 101 additions and 79 deletions.
    180 changes: 101 additions & 79 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -16,26 +16,41 @@ function setup_ssh_keys() {
    local default_ssh_key_dir="${HOME}/ssh-keys"
    local ssh_key_dir="$default_ssh_key_dir"

    # 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)
    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

    if [[ -z "${ssh_keyfile}" ]]; then
    echo "No SSH public key found in ${ssh_key_dir}. Generating a new SSH key pair..."
    ssh-keygen -t rsa -b 4096 -f "${ssh_key_dir}/id_rsa" -N "" -q
    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}."
    else
    echo "Using existing SSH public key: ${ssh_keyfile}"
    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}"
    @@ -47,67 +62,48 @@ function setup_ssh_keys() {
    # Args:
    # $1: VM ID
    # $2: VM Name
    # $3: Image URL
    # $3: Image file name
    function create_template() {
    local vm_id="$1"
    local vm_name="$2"
    local image_url="$3"
    local image_file="$3"

    echo "Processing template ${vm_name} (${vm_id})"

    local checksum_file="checksums/${vm_id}.sha256"

    # Check if the template exists and checksum file exists
    if qm status "${vm_id}" &>/dev/null && [[ -f "${checksum_file}" ]]; then
    echo "Template with VM ID ${vm_id} exists."
    echo "Checksum file for VM ID ${vm_id} exists."
    echo "Skipping download and template creation."
    return
    else
    echo "Template or checksum file does not exist. Proceeding to download and create/update template."
    fi

    # Proceed to download and create/update template
    # Extract the filename from the URL
    local image_file="${image_url##*/}"

    # 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
    # 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"

    # Delete the existing template if it exists
    # Check if the template already exists
    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
    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 with VM ID ${vm_id} does not exist. Creating new template."
    fi

    echo "Creating template ${vm_name} (${vm_id})"
    # Proceed with template creation

    # Create new VM
    qm create "${vm_id}" --name "${vm_name}" --ostype l26
    @@ -165,8 +161,8 @@ function create_template() {
    check_utilities

    # User-configurable variables
    export username="your_username_here" # Replace with your desired username
    export storage="your_storage_name_here" # Replace with your Proxmox storage name
    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
    @@ -204,29 +200,55 @@ declare -a images=(
    # 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"
    # Ubuntu 23.10 (Mantic Minotaur)
    "912|ubuntu-23.10-template|https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-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"
    "920|fedora-39-template|https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.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"
    # Alpine Linux 3.18
    "940|alpine-3.18-template|https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-standard-3.18.0-x86_64.iso"
    )

    # 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_url}"
    create_template "${vm_id}" "${vm_name}" "${image_file}"
    done
  5. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  6. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  7. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  8. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  9. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  10. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 8 additions and 23 deletions.
    31 changes: 8 additions & 23 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -55,37 +55,22 @@ function create_template() {

    echo "Processing template ${vm_name} (${vm_id})"

    # Extract the filename from the URL
    local image_file="${image_url##*/}"

    local checksum_file="checksums/${vm_id}.sha256"

    # Check if the template exists and checksum file exists
    if qm status "${vm_id}" &>/dev/null && [[ -f "${checksum_file}" ]]; then
    echo "Template with VM ID ${vm_id} exists."
    echo "Checksum file for VM ID ${vm_id} exists."

    # Check if the image file exists
    if [[ -f "${image_file}" ]]; then
    # Compute the checksum of the existing image file
    local image_checksum
    image_checksum=$(sha256sum "${image_file}" | awk '{print $1}')
    local stored_checksum
    stored_checksum=$(cat "${checksum_file}")
    echo "Comparing current image checksum with stored checksum..."
    if [[ "${image_checksum}" == "${stored_checksum}" ]]; then
    echo "Checksums match. Skipping download and template update."
    return
    else
    echo "Checksums differ. Proceeding to download and update template."
    fi
    else
    echo "Image file ${image_file} not found. Proceeding to download."
    fi
    echo "Skipping download and template creation."
    return
    else
    echo "Template or checksum file does not exist. Proceeding to download and create/update template."
    fi

    # Proceed to download and create/update template
    # Extract the filename from the URL
    local image_file="${image_url##*/}"

    # Download the image
    echo "Downloading ${image_file}..."
    if ! wget -N "${image_url}"; then
    @@ -116,7 +101,7 @@ function create_template() {
    local image_checksum
    image_checksum=$(sha256sum "${image_file}" | awk '{print $1}')

    # Check if template exists and delete if necessary
    # Delete the existing template if it exists
    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
    @@ -173,7 +158,7 @@ function create_template() {
    echo "${image_checksum}" > "${checksum_file}"

    # Remove the image file when done
    # rm -f "${image_file}"
    rm -f "${image_file}"
    }

    # Check for required utilities
  11. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 37 additions and 16 deletions.
    53 changes: 37 additions & 16 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -55,22 +55,37 @@ function create_template() {

    echo "Processing template ${vm_name} (${vm_id})"

    # Extract the filename from the URL
    local image_file="${image_url##*/}"

    local checksum_file="checksums/${vm_id}.sha256"

    # Check if the template exists and checksum file exists
    if qm status "${vm_id}" &>/dev/null && [[ -f "${checksum_file}" ]]; then
    echo "Template with VM ID ${vm_id} exists."
    echo "Checksum file for VM ID ${vm_id} exists."
    echo "Skipping download and template creation."
    return

    # Check if the image file exists
    if [[ -f "${image_file}" ]]; then
    # Compute the checksum of the existing image file
    local image_checksum
    image_checksum=$(sha256sum "${image_file}" | awk '{print $1}')
    local stored_checksum
    stored_checksum=$(cat "${checksum_file}")
    echo "Comparing current image checksum with stored checksum..."
    if [[ "${image_checksum}" == "${stored_checksum}" ]]; then
    echo "Checksums match. Skipping download and template update."
    return
    else
    echo "Checksums differ. Proceeding to download and update template."
    fi
    else
    echo "Image file ${image_file} not found. Proceeding to download."
    fi
    else
    echo "Template or checksum file does not exist. Proceeding to download and create/update template."
    fi

    # Proceed to download and create/update template
    # Extract the filename from the URL
    local image_file="${image_url##*/}"

    # Download the image
    echo "Downloading ${image_file}..."
    if ! wget -N "${image_url}"; then
    @@ -101,7 +116,7 @@ function create_template() {
    local image_checksum
    image_checksum=$(sha256sum "${image_file}" | awk '{print $1}')

    # Delete the existing template if it exists
    # 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
    @@ -158,15 +173,15 @@ function create_template() {
    echo "${image_checksum}" > "${checksum_file}"

    # Remove the image file when done
    rm -f "${image_file}"
    # rm -f "${image_file}"
    }

    # Check for required utilities
    check_utilities

    # User-configurable variables
    export username="your_username_here" # Replace with your desired username
    export storage="your_storage_name_here" # Replace with your Proxmox storage name
    export storage="your_storage_name_here" # Replace with your Proxmox storage name

    # Validate variables
    if [[ -z "${username}" || "${username}" == "your_username_here" ]]; then
    @@ -192,29 +207,35 @@ declare -a images=(
    # Debian 12 (Bookworm)
    "902|debian-12-template|https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"

    # Debian Testing (Trixie)
    "903|debian-testing-template|https://cloud.debian.org/images/cloud/testing/latest/debian-testing-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 23.10 (Mantic Minotaur)
    "912|ubuntu-23.10-template|https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-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://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2"
    "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.18
    "940|alpine-3.18-template|https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-standard-3.18.0-x86_64.iso"
    # 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
  12. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 71 additions and 99 deletions.
    170 changes: 71 additions & 99 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -16,41 +16,26 @@ 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
    # 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
    # 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}. Generating a new SSH key pair..."
    ssh-keygen -t rsa -b 4096 -f "${ssh_key_dir}/id_rsa" -N "" -q
    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}."
    else
    echo "Using existing SSH public key: ${ssh_keyfile}"
    fi

    # Set permissions on the SSH key files and directory
    chmod 700 "${ssh_key_dir}"
    @@ -62,48 +47,67 @@ function setup_ssh_keys() {
    # Args:
    # $1: VM ID
    # $2: VM Name
    # $3: Image file name
    # $3: Image URL
    function create_template() {
    local vm_id="$1"
    local vm_name="$2"
    local image_file="$3"
    local image_url="$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
    # Check if the template exists and checksum file exists
    if qm status "${vm_id}" &>/dev/null && [[ -f "${checksum_file}" ]]; then
    echo "Template with VM ID ${vm_id} exists."
    echo "Checksum file for VM ID ${vm_id} exists."
    echo "Skipping download and template creation."
    return
    else
    echo "Template or checksum file does not exist. Proceeding to download and create/update template."
    fi

    # Proceed to download and create/update template
    # Extract the filename from the URL
    local image_file="${image_url##*/}"

    # 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 "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
    echo "Decompressed file ${decompressed_file} is up to date."
    image_file="${decompressed_file}"
    fi
    else
    echo "Template with VM ID ${vm_id} does not exist. Creating new template."
    fi

    # Proceed with template creation
    # 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}')

    # Delete the existing template if it exists
    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
    @@ -188,11 +192,8 @@ declare -a images=(
    # 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"
    # Debian Testing (Trixie)
    "903|debian-testing-template|https://cloud.debian.org/images/cloud/testing/latest/debian-testing-genericcloud-amd64.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"
    @@ -220,35 +221,6 @@ declare -a images=(
    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}"
    create_template "${vm_id}" "${vm_name}" "${image_url}"
    done
  13. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 83 additions and 71 deletions.
    154 changes: 83 additions & 71 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,61 @@
    # 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
    @@ -28,24 +83,27 @@ function create_template() {
    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 creation."
    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 has changed. Updating template..."
    # Destroy the existing template and its disks
    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 checksum file found for VM ID ${vm_id}. Recreating template."
    # Destroy the existing template and its disks
    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 with VM ID ${vm_id} does not exist. Creating new template."
    fi

    echo "Creating template ${vm_name} (${vm_id})"
    # Proceed with template creation

    # Create new VM
    qm create "${vm_id}" --name "${vm_name}" --ostype l26
    @@ -100,15 +158,11 @@ function create_template() {
    }

    # Check for required utilities
    command -v qm >/dev/null 2>&1 || { echo "qm command not found. Please ensure you are running this on a Proxmox VE host."; exit 1; }
    command -v wget >/dev/null 2>&1 || { echo "wget not found. Please install wget."; exit 1; }
    command -v xz >/dev/null 2>&1 || { echo "xz not found. Please install xz-utils."; exit 1; }
    command -v sha256sum >/dev/null 2>&1 || { echo "sha256sum not found. Please install coreutils."; exit 1; }
    command -v ssh-keygen >/dev/null 2>&1 || { echo "ssh-keygen not found. Please install OpenSSH client."; exit 1; }
    check_utilities

    # User-configurable variables
    export username="your_username_here" # Replace with your desired username
    export storage="your_storage_name_here" # Replace with your Proxmox storage name
    export storage="your_storage_name_here" # Replace with your Proxmox storage name

    # Validate variables
    if [[ -z "${username}" || "${username}" == "your_username_here" ]]; then
    @@ -121,56 +175,8 @@ if ! pvesm status | grep -q "^${storage}\s"; then
    exit 1
    fi

    # SSH keys directory
    default_ssh_key_dir="${HOME}/ssh-keys"

    # Function to find or generate SSH keys
    function setup_ssh_keys() {
    local ssh_key_dir="$1"
    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_keyfile}"
    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
    }

    # Call the function to set up SSH keys
    setup_ssh_keys "${default_ssh_key_dir}"
    # Set up SSH keys
    setup_ssh_keys

    # Array of images to download and create templates from
    declare -a images=(
    @@ -194,23 +200,20 @@ declare -a images=(
    # 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"
    # Ubuntu 23.10 (Mantic Minotaur)
    "912|ubuntu-23.10-template|https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-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"
    "920|fedora-39-template|https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.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"
    # Alpine Linux 3.18
    "940|alpine-3.18-template|https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-standard-3.18.0-x86_64.iso"
    )

    # Loop through the images array
    @@ -222,7 +225,10 @@ for entry in "${images[@]}"; do

    # Download the image with timestamping
    echo "Downloading ${image_file}..."
    wget -N "${image_url}"
    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
    @@ -237,6 +243,12 @@ for entry in "${images[@]}"; do
    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
  14. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  15. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  16. @Hackiri Hackiri revised this gist Oct 11, 2024. 1 changed file with 53 additions and 2 deletions.
    55 changes: 53 additions & 2 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -104,11 +104,11 @@ command -v qm >/dev/null 2>&1 || { echo "qm command not found. Please ensure you
    command -v wget >/dev/null 2>&1 || { echo "wget not found. Please install wget."; exit 1; }
    command -v xz >/dev/null 2>&1 || { echo "xz not found. Please install xz-utils."; exit 1; }
    command -v sha256sum >/dev/null 2>&1 || { echo "sha256sum not found. Please install coreutils."; exit 1; }
    command -v ssh-keygen >/dev/null 2>&1 || { echo "ssh-keygen not found. Please install OpenSSH client."; exit 1; }

    # User-configurable variables
    export ssh_keyfile="Path_to_your_id_rsa.pub_here" # Update this path if necessary
    export username="your_username_here" # Replace with your desired username
    export storage="ceph_local" # Replace with your Proxmox storage name
    export storage="your_storage_name_here" # Replace with your Proxmox storage name

    # Validate variables
    if [[ -z "${username}" || "${username}" == "your_username_here" ]]; then
    @@ -121,6 +121,57 @@ if ! pvesm status | grep -q "^${storage}\s"; then
    exit 1
    fi

    # SSH keys directory
    default_ssh_key_dir="${HOME}/ssh-keys"

    # Function to find or generate SSH keys
    function setup_ssh_keys() {
    local ssh_key_dir="$1"
    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_keyfile}"
    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
    }

    # Call the function to set up SSH keys
    setup_ssh_keys "${default_ssh_key_dir}"

    # Array of images to download and create templates from
    declare -a images=(
    # Format: "VM_ID|VM_NAME|IMAGE_URL"
  17. @Hackiri Hackiri revised this gist Oct 11, 2024. No changes.
  18. @Hackiri Hackiri revised this gist Oct 11, 2024. 2 changed files with 191 additions and 118 deletions.
    118 changes: 0 additions & 118 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -1,118 +0,0 @@
    root@pve02:~# cat install.sh
    #!/bin/bash

    #Create template
    #args:
    # vm_id
    # vm_name
    # file name in the current directory
    function create_template() {
    #Print all of the configuration
    echo "Creating template $2 ($1)"

    #Create new VM
    #Feel free to change any of these to your liking
    qm create $1 --name $2 --ostype l26
    #Set networking to default bridge
    qm set $1 --net0 virtio,bridge=vmbr0
    #Set display to serial
    qm set $1 --serial0 socket --vga serial0
    #Set memory, cpu, type defaults
    #If you are in a cluster, you might need to change cpu type
    qm set $1 --memory 1024 --cores 4 --cpu host
    #Set boot device to new file
    qm set $1 --scsi0 ${storage}:0,import-from="$(pwd)/$3",discard=on
    #Set scsi hardware as default boot disk using virtio scsi single
    qm set $1 --boot order=scsi0 --scsihw virtio-scsi-single
    #Enable Qemu guest agent in case the guest has it available
    qm set $1 --agent enabled=1,fstrim_cloned_disks=1
    #Add cloud-init device
    qm set $1 --ide2 ${storage}:cloudinit
    #Set CI ip config
    #IP6 = auto means SLAAC (a reliable default with no bad effects on non-IPv6 networks)
    #IP = DHCP means what it says, so leave that out entirely on non-IPv4 networks to avoid DHCP delays
    qm set $1 --ipconfig0 "ip6=auto,ip=dhcp"
    #Import the ssh keyfile
    qm set $1 --sshkeys ${ssh_keyfile}
    #If you want to do password-based auth instaed
    #Then use this option and comment out the line above
    #qm set $1 --cipassword password
    #Add the user
    qm set $1 --ciuser ${username}
    #Resize the disk to 8G, a reasonable minimum. You can expand it more later.
    #If the disk is already bigger than 8G, this will fail, and that is okay.
    qm disk resize $1 scsi0 8G
    #Make it a template
    qm template $1

    #Remove file when done
    rm $3
    }


    #Path to your ssh authorized_keys file
    #Alternatively, use /etc/pve/priv/authorized_keys if you are already authorized
    #on the Proxmox system
    export ssh_keyfile=/root/ssh-keys/id_rsa.pub
    #Username to create on VM template
    export username="YOUR USER NAME HERE"

    #Name of your storage
    export storage=ceph_local "CHANGE STORAGE LOCATION"

    #The images that I've found premade
    #Feel free to add your own

    ## Debian
    #Buster (10) (really old at this point)
    #wget "https://cloud.debian.org/images/cloud/buster/latest/debian-10-genericcloud-amd64.qcow2"
    #create_template 900 "temp-debian-10" "debian-10-genericcloud-amd64.qcow2"
    #Bullseye (11) (oldstable)
    wget "https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-genericcloud-amd64.qcow2"
    create_template 901 "temp-debian-11" "debian-11-genericcloud-amd64.qcow2"
    #Bookworm (12) (stable)
    wget "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
    create_template 902 "temp-debian-12" "debian-12-genericcloud-amd64.qcow2"
    #Trixie (13) (testing) dailies
    wget "https://cloud.debian.org/images/cloud/trixie/daily/latest/debian-13-genericcloud-amd64-daily.qcow2"
    create_template 903 "temp-debian-13-daily" "debian-13-genericcloud-amd64-daily.qcow2"
    #Sid (unstable)
    wget "https://cloud.debian.org/images/cloud/sid/daily/latest/debian-sid-genericcloud-amd64-daily.qcow2"
    create_template 909 "temp-debian-sid" "debian-sid-genericcloud-amd64-daily.qcow2"

    ## Ubuntu
    #20.04 (Focal Fossa) LTS
    wget "https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img"
    create_template 910 "temp-ubuntu-20-04" "ubuntu-20.04-server-cloudimg-amd64.img"
    #22.04 (Jammy Jellyfish) LTS
    wget "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"
    create_template 911 "temp-ubuntu-22-04" "ubuntu-22.04-server-cloudimg-amd64.img"
    #23.10 (Manic Minotaur)
    wget "https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-server-cloudimg-amd64.img"
    create_template 912 "temp-ubuntu-23-10" "ubuntu-23.10-server-cloudimg-amd64.img"
    #24.04 (Lunar Lobster)
    wget "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
    create_template 913 "temp-ubuntu-24-04" "ubuntu-24.04-server-cloudimg-amd64.img"

    ## Fedora 37
    #Image is compressed, so need to uncompress first
    wget https://download.fedoraproject.org/pub/fedora/linux/releases/37/Cloud/x86_64/images/Fedora-Cloud-Base-37-1.7.x86_64.raw.xz
    xz -d -v Fedora-Cloud-Base-37-1.7.x86_64.raw.xz
    create_template 920 "temp-fedora-37" "Fedora-Cloud-Base-37-1.7.x86_64.raw"
    ## Fedora 38
    wget "https://download.fedoraproject.org/pub/fedora/linux/releases/38/Cloud/x86_64/images/Fedora-Cloud-Base-38-1.6.x86_64.raw.xz"
    xz -d -v Fedora-Cloud-Base-38-1.6.x86_64.raw.xz
    create_template 921 "temp-fedora-38" "Fedora-Cloud-Base-38-1.6.x86_64.raw"

    ## Rocky Linux
    #Rocky 8 latest
    wget "http://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2"
    create_template 930 "temp-rocky-8" "Rocky-8-GenericCloud.latest.x86_64.qcow2"
    #Rocky 9 latest
    wget "http://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2"
    create_template 931 "temp-rocky-9" "Rocky-9-GenericCloud.latest.x86_64.qcow2"

    ## Alpine Linux
    #Alpine 3.19.1
    wget "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.1-x86_64-bios-cloudinit-r0.qcow2"
    create_template 940 "temp-alpine-3.19" "nocloud_alpine-3.19.1-x86_64-bios-cloudinit-r0.qcow2"
    191 changes: 191 additions & 0 deletions proxmox_template_builder.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,191 @@
    #!/bin/bash

    # Exit immediately if a command exits with a non-zero status.
    set -e

    # 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}")
    if [[ "${image_checksum}" == "${stored_checksum}" ]]; then
    echo "Template is up to date. Skipping creation."
    # Remove the image file if it was downloaded
    rm -f "${image_file}"
    return
    else
    echo "Image has changed. Updating template..."
    # Destroy the existing template and its disks
    qm destroy "${vm_id}" --destroy-unreferenced-disks yes
    fi
    else
    echo "No checksum file found for VM ID ${vm_id}. Recreating template."
    # Destroy the existing template and its disks
    qm destroy "${vm_id}" --destroy-unreferenced-disks yes
    fi
    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
    command -v qm >/dev/null 2>&1 || { echo "qm command not found. Please ensure you are running this on a Proxmox VE host."; exit 1; }
    command -v wget >/dev/null 2>&1 || { echo "wget not found. Please install wget."; exit 1; }
    command -v xz >/dev/null 2>&1 || { echo "xz not found. Please install xz-utils."; exit 1; }
    command -v sha256sum >/dev/null 2>&1 || { echo "sha256sum not found. Please install coreutils."; exit 1; }

    # User-configurable variables
    export ssh_keyfile="Path_to_your_id_rsa.pub_here" # Update this path if necessary
    export username="your_username_here" # 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

    # 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}..."
    wget -N "${image_url}"

    # 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

    # Create or update the template
    create_template "${vm_id}" "${vm_name}" "${image_file}"
    done
  19. @Hackiri Hackiri created this gist Oct 10, 2024.
    118 changes: 118 additions & 0 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    root@pve02:~# cat install.sh
    #!/bin/bash

    #Create template
    #args:
    # vm_id
    # vm_name
    # file name in the current directory
    function create_template() {
    #Print all of the configuration
    echo "Creating template $2 ($1)"

    #Create new VM
    #Feel free to change any of these to your liking
    qm create $1 --name $2 --ostype l26
    #Set networking to default bridge
    qm set $1 --net0 virtio,bridge=vmbr0
    #Set display to serial
    qm set $1 --serial0 socket --vga serial0
    #Set memory, cpu, type defaults
    #If you are in a cluster, you might need to change cpu type
    qm set $1 --memory 1024 --cores 4 --cpu host
    #Set boot device to new file
    qm set $1 --scsi0 ${storage}:0,import-from="$(pwd)/$3",discard=on
    #Set scsi hardware as default boot disk using virtio scsi single
    qm set $1 --boot order=scsi0 --scsihw virtio-scsi-single
    #Enable Qemu guest agent in case the guest has it available
    qm set $1 --agent enabled=1,fstrim_cloned_disks=1
    #Add cloud-init device
    qm set $1 --ide2 ${storage}:cloudinit
    #Set CI ip config
    #IP6 = auto means SLAAC (a reliable default with no bad effects on non-IPv6 networks)
    #IP = DHCP means what it says, so leave that out entirely on non-IPv4 networks to avoid DHCP delays
    qm set $1 --ipconfig0 "ip6=auto,ip=dhcp"
    #Import the ssh keyfile
    qm set $1 --sshkeys ${ssh_keyfile}
    #If you want to do password-based auth instaed
    #Then use this option and comment out the line above
    #qm set $1 --cipassword password
    #Add the user
    qm set $1 --ciuser ${username}
    #Resize the disk to 8G, a reasonable minimum. You can expand it more later.
    #If the disk is already bigger than 8G, this will fail, and that is okay.
    qm disk resize $1 scsi0 8G
    #Make it a template
    qm template $1

    #Remove file when done
    rm $3
    }


    #Path to your ssh authorized_keys file
    #Alternatively, use /etc/pve/priv/authorized_keys if you are already authorized
    #on the Proxmox system
    export ssh_keyfile=/root/ssh-keys/id_rsa.pub
    #Username to create on VM template
    export username="YOUR USER NAME HERE"

    #Name of your storage
    export storage=ceph_local "CHANGE STORAGE LOCATION"

    #The images that I've found premade
    #Feel free to add your own

    ## Debian
    #Buster (10) (really old at this point)
    #wget "https://cloud.debian.org/images/cloud/buster/latest/debian-10-genericcloud-amd64.qcow2"
    #create_template 900 "temp-debian-10" "debian-10-genericcloud-amd64.qcow2"
    #Bullseye (11) (oldstable)
    wget "https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-genericcloud-amd64.qcow2"
    create_template 901 "temp-debian-11" "debian-11-genericcloud-amd64.qcow2"
    #Bookworm (12) (stable)
    wget "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2"
    create_template 902 "temp-debian-12" "debian-12-genericcloud-amd64.qcow2"
    #Trixie (13) (testing) dailies
    wget "https://cloud.debian.org/images/cloud/trixie/daily/latest/debian-13-genericcloud-amd64-daily.qcow2"
    create_template 903 "temp-debian-13-daily" "debian-13-genericcloud-amd64-daily.qcow2"
    #Sid (unstable)
    wget "https://cloud.debian.org/images/cloud/sid/daily/latest/debian-sid-genericcloud-amd64-daily.qcow2"
    create_template 909 "temp-debian-sid" "debian-sid-genericcloud-amd64-daily.qcow2"

    ## Ubuntu
    #20.04 (Focal Fossa) LTS
    wget "https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img"
    create_template 910 "temp-ubuntu-20-04" "ubuntu-20.04-server-cloudimg-amd64.img"
    #22.04 (Jammy Jellyfish) LTS
    wget "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"
    create_template 911 "temp-ubuntu-22-04" "ubuntu-22.04-server-cloudimg-amd64.img"
    #23.10 (Manic Minotaur)
    wget "https://cloud-images.ubuntu.com/releases/23.10/release/ubuntu-23.10-server-cloudimg-amd64.img"
    create_template 912 "temp-ubuntu-23-10" "ubuntu-23.10-server-cloudimg-amd64.img"
    #24.04 (Lunar Lobster)
    wget "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
    create_template 913 "temp-ubuntu-24-04" "ubuntu-24.04-server-cloudimg-amd64.img"

    ## Fedora 37
    #Image is compressed, so need to uncompress first
    wget https://download.fedoraproject.org/pub/fedora/linux/releases/37/Cloud/x86_64/images/Fedora-Cloud-Base-37-1.7.x86_64.raw.xz
    xz -d -v Fedora-Cloud-Base-37-1.7.x86_64.raw.xz
    create_template 920 "temp-fedora-37" "Fedora-Cloud-Base-37-1.7.x86_64.raw"
    ## Fedora 38
    wget "https://download.fedoraproject.org/pub/fedora/linux/releases/38/Cloud/x86_64/images/Fedora-Cloud-Base-38-1.6.x86_64.raw.xz"
    xz -d -v Fedora-Cloud-Base-38-1.6.x86_64.raw.xz
    create_template 921 "temp-fedora-38" "Fedora-Cloud-Base-38-1.6.x86_64.raw"

    ## Rocky Linux
    #Rocky 8 latest
    wget "http://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2"
    create_template 930 "temp-rocky-8" "Rocky-8-GenericCloud.latest.x86_64.qcow2"
    #Rocky 9 latest
    wget "http://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2"
    create_template 931 "temp-rocky-9" "Rocky-9-GenericCloud.latest.x86_64.qcow2"

    ## Alpine Linux
    #Alpine 3.19.1
    wget "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.1-x86_64-bios-cloudinit-r0.qcow2"
    create_template 940 "temp-alpine-3.19" "nocloud_alpine-3.19.1-x86_64-bios-cloudinit-r0.qcow2"