#!/usr/bin/env bash set -Eeuo pipefail ######################################## # Logging / Errors ######################################## _ts() { date '+%F %T'; } log() { echo "[$(_ts)] $*"; } info() { log "INFO: $*"; } warn() { log "WARN: $*"; } die() { log "ERROR: $*"; exit 1; } on_error() { die "Failed at line $1: $2 (exit=$3)" } setup_traps() { trap 'on_error $LINENO "$BASH_COMMAND" "$?"' ERR } ######################################## # Preconditions ######################################## need_cmd() { for c in "$@"; do command -v "$c" >/dev/null 2>&1 || die "Missing command: $c" done } ######################################## # Proxmox helpers ######################################## pve_bridge_exists() { [[ -d "/sys/class/net/$1/bridge" ]] } pve_storage_exists() { pvesm status --storage "$1" >/dev/null 2>&1 } ######################################## # Template handling ######################################## pve_template_ensure_debian12() { local tpl="debian-12-standard_12.12-1_amd64.tar.zst" local store="$1" if ! pveam list "$store" >/dev/null 2>&1; then warn "pveam storage '$store' not available for templates; falling back to 'local'" store="local" fi if ! pveam list "$store" | awk '{print $2}' | grep -qx "$tpl"; then info "Downloading CT template to $store: $tpl" pveam update pveam download "$store" "$tpl" fi echo "$store:vztmpl/$tpl" } ######################################## # Cluster-wide CTID ######################################## pve_next_free_ctid() { local used used="$(pvesh get /cluster/resources --type vm | awk 'NR>1 {print $1}' | sort -n)" for id in $(seq 100 9999); do if ! echo "$used" | grep -qx "$id"; then echo "$id" return fi done die "No free CTID found" } ######################################## # Networking ######################################## pve_build_net0() { local bridge="$1" local ip="$2" if [[ "$ip" == "dhcp" ]]; then echo "name=eth0,bridge=$bridge,ip=dhcp" else echo "name=eth0,bridge=$bridge,ip=$ip" fi }