mirror of
https://github.com/ergosteur/ergo-proxmox-scripts.git
synced 2026-10-28 13:00:37 -04:00
Add hardened Proxmox Debian cloud-init template script
Adds error handling (set -euo pipefail), VM ID/storage validation, dependency checks, configurable storage/bridge/memory/cores via env vars, image download caching, and auto-detection of the imported disk instead of hardcoding filesystem-specific paths.
This commit is contained in:
@@ -1,38 +1,95 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# makes a cloud-init template vm from URL
|
# Build a Proxmox cloud-init template VM from a cloud image URL.
|
||||||
# call like ./make-debian-template.sh 9000
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./make-debian-cloudinit-template.sh <VMID> [IMAGE_URL]
|
||||||
|
#
|
||||||
|
# Optional overrides (env vars):
|
||||||
|
# STORAGE Proxmox storage pool for disks (default: local-zfs)
|
||||||
|
# BRIDGE Network bridge (default: vmbr0)
|
||||||
|
# MEMORY RAM in MB (default: 2048)
|
||||||
|
# CORES CPU cores (default: 4)
|
||||||
|
# NAME Template name in Proxmox (default: debian-cloudinit-<VMID>)
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# STORAGE=local-lvm BRIDGE=vmbr1 ./make-debian-cloudinit-template.sh 9000
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
grep '^#' "$0" | sed -e 's/^#//' -e 's/^ //' | head -n 12
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Check for root priviliges
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
printf "Please run as root:\nsudo %s\n" "${0}"
|
printf "Please run as root:\nsudo %s\n" "$0"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for bin in wget qm pvesm; do
|
||||||
|
if ! command -v "$bin" >/dev/null; then
|
||||||
|
echo "Required command '$bin' not found. Is this running on a Proxmox host?" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
ID=${1:-}
|
||||||
|
if [[ -z "$ID" || ! "$ID" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Error: a numeric VM ID is required." >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if qm status "$ID" &>/dev/null; then
|
||||||
|
echo "Error: VM ID $ID already exists." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ID=$1
|
|
||||||
URL=${2:-"https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"}
|
URL=${2:-"https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2"}
|
||||||
FS=btrfs
|
STORAGE=${STORAGE:-local-zfs}
|
||||||
# decide between lvm, zfs or btrfs
|
BRIDGE=${BRIDGE:-vmbr0}
|
||||||
|
MEMORY=${MEMORY:-2048}
|
||||||
|
CORES=${CORES:-4}
|
||||||
|
NAME=${NAME:-"debian-cloudinit-$ID"}
|
||||||
|
|
||||||
wget $URL -O cloud-init.qcow2
|
if ! pvesm status -storage "$STORAGE" &>/dev/null; then
|
||||||
qm create $ID --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci --memory 2048 --cores 4 --machine q35
|
echo "Error: storage pool '$STORAGE' not found (check STORAGE env var)." >&2
|
||||||
command -v virt-customize
|
exit 1
|
||||||
# install libguestfs-tools to have virt-customize
|
|
||||||
|
|
||||||
if [[ $? -eq 0 ]]; then
|
|
||||||
echo "installing qemu-guest-agent into guest"
|
|
||||||
virt-customize -a cloud-init.qcow2 --install qemu-guest-agent
|
|
||||||
qm set $ID --agent 1
|
|
||||||
else
|
|
||||||
echo "will not install qemu-guest-agent into guest"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
qm importdisk $ID ./cloud-init.qcow2 local-$FS
|
trap 'echo "Failed while building VM $ID. It may be left in a partial state — check with: qm config $ID (clean up with: qm destroy $ID)" >&2' ERR
|
||||||
[ "$FS" == "btrfs" ] && qm set $ID --scsi0 local-$FS:$ID/vm-$ID-disk-0.raw
|
|
||||||
[ "$FS" == "lvm" ] && qm set $ID --scsi0 local-$FS:vm-$ID-disk-0
|
CACHE_DIR=/var/tmp/pve-cloudinit-images
|
||||||
[ "$FS" == "zfs" ] && qm set $ID --scsi0 local-$FS:vm-$ID-disk-0
|
mkdir -p "$CACHE_DIR"
|
||||||
qm set $ID --scsi1 local-$FS:cloudinit
|
IMAGE_FILE="$CACHE_DIR/$(basename "$URL")"
|
||||||
qm set $ID --serial0 socket --vga serial0
|
|
||||||
qm set $ID --name cloud-init-template
|
echo "Fetching cloud image (cached under $CACHE_DIR, re-used if unchanged)..."
|
||||||
qm set $ID --boot order=scsi0
|
wget -N -P "$CACHE_DIR" "$URL"
|
||||||
qm template $ID
|
|
||||||
|
qm create "$ID" --net0 "virtio,bridge=$BRIDGE" --scsihw virtio-scsi-pci --memory "$MEMORY" --cores "$CORES" --machine q35
|
||||||
|
|
||||||
|
if command -v virt-customize >/dev/null; then
|
||||||
|
echo "Installing qemu-guest-agent into guest image..."
|
||||||
|
virt-customize -a "$IMAGE_FILE" --install qemu-guest-agent
|
||||||
|
qm set "$ID" --agent 1
|
||||||
|
else
|
||||||
|
echo "virt-customize not found (install libguestfs-tools for this); skipping in-guest qemu-guest-agent install."
|
||||||
|
fi
|
||||||
|
|
||||||
|
qm importdisk "$ID" "$IMAGE_FILE" "$STORAGE"
|
||||||
|
DISK=$(qm config "$ID" | awk -F': ' '/^unused0:/ {print $2}')
|
||||||
|
if [[ -z "$DISK" ]]; then
|
||||||
|
echo "Error: could not find imported disk on VM $ID." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
qm set "$ID" --scsi0 "$DISK"
|
||||||
|
qm set "$ID" --scsi1 "$STORAGE:cloudinit"
|
||||||
|
qm set "$ID" --name "$NAME" --boot order=scsi0 --serial0 socket --vga serial0
|
||||||
|
qm template "$ID"
|
||||||
|
|
||||||
|
echo "Template $ID ($NAME) created from $URL using storage '$STORAGE'."
|
||||||
|
|||||||
Reference in New Issue
Block a user