From 3971eb49c7a641d1c9653fac89dae3e3d180ee50 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:54:10 +0100 Subject: [PATCH] Indent shell functions for consistency Updated all function bodies in debian-13-vm.sh and vm-manager.sh to use consistent indentation. This improves readability and maintainability of the scripts without changing their logic or behavior. --- vm/debian-13-vm.sh | 652 ++++++++++++++-------------- vm/vm-manager.sh | 1028 ++++++++++++++++++++++---------------------- 2 files changed, 848 insertions(+), 832 deletions(-) diff --git a/vm/debian-13-vm.sh b/vm/debian-13-vm.sh index 7a2272972..97ca4de15 100644 --- a/vm/debian-13-vm.sh +++ b/vm/debian-13-vm.sh @@ -7,8 +7,8 @@ source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) function header_info { - clear - cat <<"EOF" + clear + cat <<"EOF" ____ __ _ ________ / __ \___ / /_ (_)___ _____ < /__ / / / / / _ \/ __ \/ / __ `/ __ \ / / /_ < @@ -66,374 +66,374 @@ trap cleanup EXIT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM function error_handler() { - local exit_code="$?" - local line_number="$1" - local command="$2" - local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" - post_update_to_api "failed" "${command}" - echo -e "\n$error_message\n" - cleanup_vmid + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + post_update_to_api "failed" "${command}" + echo -e "\n$error_message\n" + cleanup_vmid } function get_valid_nextid() { - local try_id - try_id=$(pvesh get /cluster/nextid) - while true; do - if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then - try_id=$((try_id + 1)) - continue - fi - if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then - try_id=$((try_id + 1)) - continue - fi - break - done - echo "$try_id" + local try_id + try_id=$(pvesh get /cluster/nextid) + while true; do + if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then + try_id=$((try_id + 1)) + continue + fi + if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then + try_id=$((try_id + 1)) + continue + fi + break + done + echo "$try_id" } function cleanup_vmid() { - if qm status $VMID &>/dev/null; then - qm stop $VMID &>/dev/null - qm destroy $VMID &>/dev/null - fi + if qm status $VMID &>/dev/null; then + qm stop $VMID &>/dev/null + qm destroy $VMID &>/dev/null + fi } function cleanup() { - popd >/dev/null - post_update_to_api "done" "none" - rm -rf $TEMP_DIR + popd >/dev/null + post_update_to_api "done" "none" + rm -rf $TEMP_DIR } TEMP_DIR=$(mktemp -d) pushd $TEMP_DIR >/dev/null if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Debian 13 VM" --yesno "This will create a New Debian 13 VM. Proceed?" 10 58; then - : + : else - header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit + header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit fi function msg_info() { - local msg="$1" - echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" + local msg="$1" + echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" } function msg_ok() { - local msg="$1" - echo -e "${BFR}${CM}${GN}${msg}${CL}" + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" } function msg_error() { - local msg="$1" - echo -e "${BFR}${CROSS}${RD}${msg}${CL}" + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" } function check_root() { - if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then - clear - msg_error "Please run this script as root." - echo -e "\nExiting..." - sleep 2 - exit - fi + if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then + clear + msg_error "Please run this script as root." + echo -e "\nExiting..." + sleep 2 + exit + fi } # This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. # Supported: Proxmox VE 8.0.x – 8.9.x and 9.0 – 9.1 pve_check() { - local PVE_VER - PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" - # Check for Proxmox VE 8.x: allow 8.0–8.9 - if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then - local MINOR="${BASH_REMATCH[1]}" - if ((MINOR < 0 || MINOR > 9)); then - msg_error "This version of Proxmox VE is not supported." - msg_error "Supported: Proxmox VE version 8.0 – 8.9" - exit 1 - fi - return 0 + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 1 fi + return 0 + fi - # Check for Proxmox VE 9.x: allow 9.0–9.1 - if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then - local MINOR="${BASH_REMATCH[1]}" - if ((MINOR < 0 || MINOR > 1)); then - msg_error "This version of Proxmox VE is not yet supported." - msg_error "Supported: Proxmox VE version 9.0 – 9.1" - exit 1 - fi - return 0 + # Check for Proxmox VE 9.x: allow 9.0–9.1 + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 1)); then + msg_error "This version of Proxmox VE is not yet supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.1" + exit 1 fi + return 0 + fi - # All other unsupported versions - msg_error "This version of Proxmox VE is not supported." - msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1" - exit 1 + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.1" + exit 1 } function arch_check() { - if [ "$(dpkg --print-architecture)" != "amd64" ]; then - echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" - echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" - echo -e "Exiting..." - sleep 2 - exit - fi + if [ "$(dpkg --print-architecture)" != "amd64" ]; then + echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" + echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" + echo -e "Exiting..." + sleep 2 + exit + fi } function ssh_check() { - if command -v pveversion >/dev/null 2>&1; then - if [ -n "${SSH_CLIENT:+x}" ]; then - if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then - echo "you've been warned" - else - clear - exit - fi - fi + if command -v pveversion >/dev/null 2>&1; then + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then + echo "you've been warned" + else + clear + exit + fi fi + fi } function exit-script() { - clear - echo -e "\n${CROSS}${RD}User exited script${CL}\n" - exit + clear + echo -e "\n${CROSS}${RD}User exited script${CL}\n" + exit } function default_settings() { - VMID=$(get_valid_nextid) - FORMAT="" - MACHINE="q35" - DISK_SIZE="30G" - HN="debian" - CPU_TYPE="" - CORE_COUNT="2" - RAM_SIZE="4096" - BRG="vmbr0" - MAC="$GEN_MAC" - VLAN="" - MTU="" - START_VM="yes" - METHOD="default" - echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}" - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" - echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" - echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" - echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}" - echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" - echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" - echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above default settings${CL}" + VMID=$(get_valid_nextid) + FORMAT="" + MACHINE="q35" + DISK_SIZE="30G" + HN="debian" + CPU_TYPE="" + CORE_COUNT="2" + RAM_SIZE="4096" + BRG="vmbr0" + MAC="$GEN_MAC" + VLAN="" + MTU="" + START_VM="yes" + METHOD="default" + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above default settings${CL}" } function advanced_settings() { - METHOD="advanced" - [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) - while true; do - if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z "$VMID" ]; then - VMID=$(get_valid_nextid) - fi - if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then - echo -e "${CROSS}${RD} ID $VMID is already in use${CL}" - sleep 2 - continue - fi - echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" - break - else - exit-script - fi - done - - if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Machine Type" 10 58 2 \ - "q35" "Modern (PCIe, UEFI, default)" ON \ - "i440fx" "Legacy (older compatibility)" OFF \ - 3>&1 1>&2 2>&3); then - if [ "$MACH" = "q35" ]; then - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}" - FORMAT="" - MACHINE=" -machine q35" - else - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" - FORMAT=",efitype=4m" - MACHINE="" - fi + METHOD="advanced" + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID=$(get_valid_nextid) + fi + if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then + echo -e "${CROSS}${RD} ID $VMID is already in use${CL}" + sleep 2 + continue + fi + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break else - exit-script + exit-script fi + done - if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') - if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then - DISK_SIZE="${DISK_SIZE}G" - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" - elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" - else - echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" - exit-script - fi + if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Machine Type" 10 58 2 \ + "q35" "Modern (PCIe, UEFI, default)" ON \ + "i440fx" "Legacy (older compatibility)" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$MACH" = "q35" ]; then + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}" + FORMAT="" + MACHINE=" -machine q35" else - exit-script + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" + FORMAT=",efitype=4m" + MACHINE="" fi + else + exit-script + fi - if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ - "0" "None" OFF \ - "1" "Write Through (Default)" ON \ - 3>&1 1>&2 2>&3); then - if [ $DISK_CACHE = "1" ]; then - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" - DISK_CACHE="cache=writethrough," - else - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" - DISK_CACHE="" - fi + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') + if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then + DISK_SIZE="${DISK_SIZE}G" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" else - exit-script + echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" + exit-script fi + else + exit-script + fi - if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 unifi-os-server --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $VM_NAME ]; then - HN="unifi-os-server" - echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" - else - HN=$(echo ${VM_NAME,,} | tr -d ' ') - echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" - fi + if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "None" OFF \ + "1" "Write Through (Default)" ON \ + 3>&1 1>&2 2>&3); then + if [ $DISK_CACHE = "1" ]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" + DISK_CACHE="cache=writethrough," else - exit-script + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + DISK_CACHE="" fi + else + exit-script + fi - if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose CPU Model" --cancel-button Exit-Script 10 58 2 \ - "KVM64" "Default – safe for migration/compatibility" ON \ - "Host" "Use host CPU features (faster, no migration)" OFF \ - 3>&1 1>&2 2>&3); then - case "$CPU_TYPE1" in - Host) - echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" - CPU_TYPE=" -cpu host" - ;; - *) - echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" - CPU_TYPE="" - ;; - esac + if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 unifi-os-server --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VM_NAME ]; then + HN="unifi-os-server" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" else - exit-script + HN=$(echo ${VM_NAME,,} | tr -d ' ') + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" fi + else + exit-script + fi - if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $CORE_COUNT ]; then - CORE_COUNT="2" - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" - else - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" - fi - else - exit-script - fi + if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose CPU Model" --cancel-button Exit-Script 10 58 2 \ + "KVM64" "Default – safe for migration/compatibility" ON \ + "Host" "Use host CPU features (faster, no migration)" OFF \ + 3>&1 1>&2 2>&3); then + case "$CPU_TYPE1" in + Host) + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" + CPU_TYPE=" -cpu host" + ;; + *) + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + CPU_TYPE="" + ;; + esac + else + exit-script + fi - if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $RAM_SIZE ]; then - RAM_SIZE="2048" - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" - else - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" - fi + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $CORE_COUNT ]; then + CORE_COUNT="2" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" else - exit-script + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" fi + else + exit-script + fi - if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $BRG ]; then - BRG="vmbr0" - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - else - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - fi + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $RAM_SIZE ]; then + RAM_SIZE="2048" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" else - exit-script + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" fi + else + exit-script + fi - if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $MAC1 ]; then - MAC="$GEN_MAC" - echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" - else - MAC="$MAC1" - echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" - fi + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $BRG ]; then + BRG="vmbr0" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" else - exit-script + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" fi + else + exit-script + fi - if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $VLAN1 ]; then - VLAN1="Default" - VLAN="" - echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" - else - VLAN=",tag=$VLAN1" - echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" - fi + if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $MAC1 ]; then + MAC="$GEN_MAC" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" else - exit-script + MAC="$MAC1" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" fi + else + exit-script + fi - if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $MTU1 ]; then - MTU1="Default" - MTU="" - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" - else - MTU=",mtu=$MTU1" - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" - fi + if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VLAN1 ]; then + VLAN1="Default" + VLAN="" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" else - exit-script + VLAN=",tag=$VLAN1" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" fi + else + exit-script + fi - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" --yesno "Configure the VM with Cloud-init?" --defaultno 10 58); then - echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}yes${CL}" - CLOUD_INIT="yes" + if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $MTU1 ]; then + MTU1="Default" + MTU="" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" else - echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}" - CLOUD_INIT="no" + MTU=",mtu=$MTU1" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" fi + else + exit-script + fi - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then - echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" - START_VM="yes" - else - echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}" - START_VM="no" - fi + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" --yesno "Configure the VM with Cloud-init?" --defaultno 10 58); then + echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}yes${CL}" + CLOUD_INIT="yes" + else + echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}" + CLOUD_INIT="no" + fi - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Debian 13 VM?" --no-button Do-Over 10 58); then - echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above advanced settings${CL}" - else - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" - advanced_settings - fi + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + START_VM="yes" + else + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}" + START_VM="no" + fi + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Debian 13 VM?" --no-button Do-Over 10 58); then + echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above advanced settings${CL}" + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi } function start_script() { - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}" - default_settings - else - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" - advanced_settings - fi + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi } check_root arch_check @@ -444,40 +444,40 @@ post_to_api_vm msg_info "Validating Storage" while read -r line; do - TAG=$(echo $line | awk '{print $1}') - TYPE=$(echo $line | awk '{printf "%-10s", $2}') - FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}') - ITEM=" Type: $TYPE Free: $FREE " - OFFSET=2 - if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then - MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) - fi - STORAGE_MENU+=("$TAG" "$ITEM" "OFF") + TAG=$(echo $line | awk '{print $1}') + TYPE=$(echo $line | awk '{printf "%-10s", $2}') + FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}') + ITEM=" Type: $TYPE Free: $FREE " + OFFSET=2 + if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then + MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) + fi + STORAGE_MENU+=("$TAG" "$ITEM" "OFF") done < <(pvesm status -content images | awk 'NR>1') VALID=$(pvesm status -content images | awk 'NR>1') if [ -z "$VALID" ]; then - msg_error "Unable to detect a valid storage location." - exit + msg_error "Unable to detect a valid storage location." + exit elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then - STORAGE=${STORAGE_MENU[0]} + STORAGE=${STORAGE_MENU[0]} else - while [ -z "${STORAGE:+x}" ]; do - if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi - printf "\e[?25h" - STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ - "Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \ - 16 $(($MSG_MAX_LENGTH + 23)) 6 \ - "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) - done + while [ -z "${STORAGE:+x}" ]; do + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi + printf "\e[?25h" + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + done fi msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." msg_info "Retrieving the URL for the Debian 13 Qcow2 Disk Image" if [ "$CLOUD_INIT" == "yes" ]; then - URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2 + URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2 else - URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2 + URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2 fi CACHE_DIR="/var/lib/vz/template/cache" CACHE_FILE="$CACHE_DIR/$(basename "$URL")" @@ -486,49 +486,49 @@ mkdir -p "$CACHE_DIR" "$(dirname "$FILE_IMG")" msg_ok "${CL}${BL}${URL}${CL}" if [[ ! -s "$CACHE_FILE" ]]; then - curl -f#SL -o "$CACHE_FILE" "$URL" - msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}" + curl -f#SL -o "$CACHE_FILE" "$URL" + msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}" else - msg_ok "Using cached image ${CL}${BL}$(basename "$CACHE_FILE")${CL}" + msg_ok "Using cached image ${CL}${BL}$(basename "$CACHE_FILE")${CL}" fi set -o pipefail msg_info "Creating Debian 13 VM shell" qm create "$VMID" -machine q35 -bios ovmf -agent 1 -tablet 0 -localtime 1 ${CPU_TYPE} \ - -cores "$CORE_COUNT" -memory "$RAM_SIZE" -name "$HN" -tags community-script \ - -net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null + -cores "$CORE_COUNT" -memory "$RAM_SIZE" -name "$HN" -tags community-script \ + -net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null msg_ok "Created VM shell" msg_info "Importing disk into storage ($STORAGE)" if qm disk import --help >/dev/null 2>&1; then - IMPORT_CMD=(qm disk import) + IMPORT_CMD=(qm disk import) else - IMPORT_CMD=(qm importdisk) + IMPORT_CMD=(qm importdisk) fi IMPORT_OUT="$("${IMPORT_CMD[@]}" "$VMID" "$CACHE_FILE" "$STORAGE" --format raw 2>&1 || true)" DISK_REF="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully imported disk '\([^']\+\)'.*/\1/p" | tr -d "\r\"'")" [[ -z "$DISK_REF" ]] && DISK_REF="$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $1":"$5}' | sort | tail -n1)" [[ -z "$DISK_REF" ]] && { - msg_error "Unable to determine imported disk reference." - echo "$IMPORT_OUT" - exit 1 + msg_error "Unable to determine imported disk reference." + echo "$IMPORT_OUT" + exit 1 } msg_ok "Imported disk (${CL}${BL}${DISK_REF}${CL})" msg_info "Attaching EFI and root disk" if [ "$CLOUD_INIT" == "yes" ]; then - qm set "$VMID" \ - --efidisk0 "${STORAGE}:0,efitype=4m" \ - --scsi0 "${DISK_REF},ssd=1,discard=on" \ - --scsi1 "${STORAGE}:cloudinit" \ - --boot order=scsi0 \ - --serial0 socket >/dev/null + qm set "$VMID" \ + --efidisk0 "${STORAGE}:0,efitype=4m" \ + --scsi0 "${DISK_REF},ssd=1,discard=on" \ + --scsi1 "${STORAGE}:cloudinit" \ + --boot order=scsi0 \ + --serial0 socket >/dev/null else - qm set "$VMID" \ - --efidisk0 "${STORAGE}:0,efitype=4m" \ - --scsi0 "${DISK_REF},ssd=1,discard=on" \ - --boot order=scsi0 \ - --serial0 socket >/dev/null + qm set "$VMID" \ + --efidisk0 "${STORAGE}:0,efitype=4m" \ + --scsi0 "${DISK_REF},ssd=1,discard=on" \ + --boot order=scsi0 \ + --serial0 socket >/dev/null fi qm set "$VMID" --agent enabled=1 >/dev/null msg_ok "Attached EFI and root disk" @@ -538,7 +538,7 @@ qm resize "$VMID" scsi0 "${DISK_SIZE}" >/dev/null msg_ok "Resized disk" DESCRIPTION=$( - cat < Logo @@ -571,9 +571,9 @@ qm set "$VMID" -description "$DESCRIPTION" >/dev/null msg_ok "Created a Debian 13 VM ${CL}${BL}(${HN})" if [ "$START_VM" == "yes" ]; then - msg_info "Starting Debian 13 VM" - qm start $VMID - msg_ok "Started Debian 13 VM" + msg_info "Starting Debian 13 VM" + qm start $VMID + msg_ok "Started Debian 13 VM" fi msg_info "Installing resize tools in VM" diff --git a/vm/vm-manager.sh b/vm/vm-manager.sh index 69c803b10..bffd8695e 100644 --- a/vm/vm-manager.sh +++ b/vm/vm-manager.sh @@ -11,39 +11,39 @@ set -euo pipefail # ============================================================================ declare -A OS_IMAGES=( - # Debian - Cloud-Init enabled - ["debian-13"]="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2" - ["debian-12"]="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2" + # Debian - Cloud-Init enabled + ["debian-13"]="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2" + ["debian-12"]="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2" - # Debian - NoCloud variants (without cloud-init pre-installed) - ["debian-13-nocloud"]="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2" - ["debian-12-nocloud"]="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.qcow2" + # Debian - NoCloud variants (without cloud-init pre-installed) + ["debian-13-nocloud"]="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2" + ["debian-12-nocloud"]="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.qcow2" - # Ubuntu - Cloud-Init enabled - ["ubuntu-24.04"]="https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img" - ["ubuntu-22.04"]="https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" - ["ubuntu-20.04"]="https://cloud-images.ubuntu.com/releases/20.04/release/ubuntu-20.04-server-cloudimg-amd64.img" + # Ubuntu - Cloud-Init enabled + ["ubuntu-24.04"]="https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img" + ["ubuntu-22.04"]="https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" + ["ubuntu-20.04"]="https://cloud-images.ubuntu.com/releases/20.04/release/ubuntu-20.04-server-cloudimg-amd64.img" - # AlmaLinux - ["alma-9"]="https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2" - ["alma-8"]="https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2" + # AlmaLinux + ["alma-9"]="https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2" + ["alma-8"]="https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2" - # Rocky Linux - ["rocky-9"]="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2" - ["rocky-8"]="https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2" + # Rocky Linux + ["rocky-9"]="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2" + ["rocky-8"]="https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2" - # Fedora - ["fedora-41"]="https://download.fedoraproject.org/pub/fedora/linux/releases/41/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-41-1.4.x86_64.qcow2" - ["fedora-40"]="https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-40-1.14.x86_64.qcow2" + # Fedora + ["fedora-41"]="https://download.fedoraproject.org/pub/fedora/linux/releases/41/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-41-1.4.x86_64.qcow2" + ["fedora-40"]="https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-40-1.14.x86_64.qcow2" - # Arch Linux - ["arch"]="https://geo.mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg.qcow2" + # Arch Linux + ["arch"]="https://geo.mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg.qcow2" - # CentOS Stream - ["centos-9"]="https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2" + # CentOS Stream + ["centos-9"]="https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2" - # Alpine Linux - ["alpine-3.20"]="https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/generic_alpine-3.20.0-x86_64-bios-cloudinit-r0.qcow2" + # Alpine Linux + ["alpine-3.20"]="https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/generic_alpine-3.20.0-x86_64-bios-cloudinit-r0.qcow2" ) # ============================================================================ @@ -95,47 +95,47 @@ NC='\033[0m' info() { echo -e "${BLUE}ℹ${NC} $*"; } ok() { echo -e "${GREEN}✓${NC} $*"; } error() { - echo -e "${RED}✗${NC} $*" >&2 - exit 1 + echo -e "${RED}✗${NC} $*" >&2 + exit 1 } warn() { echo -e "${YELLOW}⚠${NC} $*"; } get_next_vmid() { - local start_id=${1:-100} - local id=$start_id - while [ -f "/etc/pve/qemu-server/${id}.conf" ] || [ -f "/etc/pve/lxc/${id}.conf" ]; do - id=$((id + 1)) - done - echo "$id" + local start_id=${1:-100} + local id=$start_id + while [ -f "/etc/pve/qemu-server/${id}.conf" ] || [ -f "/etc/pve/lxc/${id}.conf" ]; do + id=$((id + 1)) + done + echo "$id" } get_default_storage() { - pvesm status -content images 2>/dev/null | awk 'NR==2 {print $1}' || echo "local-lvm" + pvesm status -content images 2>/dev/null | awk 'NR==2 {print $1}' || echo "local-lvm" } list_storage_pools() { - pvesm status -content images 2>/dev/null | awk 'NR>1 {print $1}' + pvesm status -content images 2>/dev/null | awk 'NR>1 {print $1}' } get_snippet_storage() { - pvesm status -content snippets 2>/dev/null | awk 'NR==2 {print $1}' || echo "local" + pvesm status -content snippets 2>/dev/null | awk 'NR==2 {print $1}' || echo "local" } find_template_by_name() { - local name=$1 - qm list 2>/dev/null | awk -v n="$name" '$2 == n {print $1; exit}' + local name=$1 + qm list 2>/dev/null | awk -v n="$name" '$2 == n {print $1; exit}' } is_template() { - local vmid=$1 - qm config "$vmid" 2>/dev/null | grep -q "^template: 1" + local vmid=$1 + qm config "$vmid" 2>/dev/null | grep -q "^template: 1" } cleanup_vm() { - if [ -n "${VMID:-}" ] && qm status "$VMID" &>/dev/null; then - warn "Cleanup: Removing VM $VMID" - qm destroy "$VMID" &>/dev/null || true - fi + if [ -n "${VMID:-}" ] && qm status "$VMID" &>/dev/null; then + warn "Cleanup: Removing VM $VMID" + qm destroy "$VMID" &>/dev/null || true + fi } # ============================================================================ @@ -143,213 +143,229 @@ cleanup_vm() { # ============================================================================ interactive_main_menu() { - local choice - choice=$(whiptail --title "VM Manager" --menu "Choose action:" 20 70 10 \ - "1" "Create VM Template" \ - "2" "Deploy VM from Template" \ - "3" "List Templates" \ - "4" "List Available OS Images" \ - "5" "Exit" \ - 3>&1 1>&2 2>&3) - - case $choice in + local choice + choice=$(whiptail --title "VM Manager" --menu "Choose action:" 20 70 10 \ + "1" "Create VM Template" \ + "2" "Deploy VM from Template" \ + "3" "List Templates" \ + "4" "List Available OS Images" \ + "5" "Exit" \ + 3>&1 1>&2 2>&3) + + case $choice in 1) interactive_create_template ;; 2) interactive_deploy_vm ;; - 3) list_templates; read -p "Press Enter to continue..."; interactive_main_menu ;; - 4) list_os_options; read -p "Press Enter to continue..."; interactive_main_menu ;; + 3) + list_templates + read -p "Press Enter to continue..." + interactive_main_menu + ;; + 4) + list_os_options + read -p "Press Enter to continue..." + interactive_main_menu + ;; 5) exit 0 ;; *) exit 0 ;; - esac + esac } interactive_select_os() { - local menu_items=() - local i=1 - - for key in $(echo "${!OS_IMAGES[@]}" | tr ' ' '\n' | sort); do - menu_items+=("$key" "${OS_IMAGES[$key]}") - done - - OS_KEY=$(whiptail --title "Select OS" --menu "Choose operating system:" 25 80 15 \ - "${menu_items[@]}" 3>&1 1>&2 2>&3) - - [ -z "$OS_KEY" ] && return 1 - return 0 + local menu_items=() + local i=1 + + for key in $(echo "${!OS_IMAGES[@]}" | tr ' ' '\n' | sort); do + menu_items+=("$key" "${OS_IMAGES[$key]}") + done + + OS_KEY=$(whiptail --title "Select OS" --menu "Choose operating system:" 25 80 15 \ + "${menu_items[@]}" 3>&1 1>&2 2>&3) + + [ -z "$OS_KEY" ] && return 1 + return 0 } interactive_create_template() { - clear - info "Creating VM Template - Interactive Mode" - echo "" - - # Select OS - interactive_select_os || { warn "No OS selected"; interactive_main_menu; return; } - - # VM ID - local input_vmid - input_vmid=$(whiptail --title "VM ID" --inputbox "Enter VM ID (leave empty for auto):" 10 60 "" 3>&1 1>&2 2>&3) - [ -n "$input_vmid" ] && VMID="$input_vmid" - - # CPU Cores - CORES=$(whiptail --title "CPU Cores" --inputbox "Number of CPU cores:" 10 60 "$CORES" 3>&1 1>&2 2>&3) || CORES=2 - - # Memory - MEMORY=$(whiptail --title "Memory" --inputbox "Memory in MB:" 10 60 "$MEMORY" 3>&1 1>&2 2>&3) || MEMORY=2048 - - # Disk Size - DISK_SIZE=$(whiptail --title "Disk Size" --inputbox "Disk size in GB:" 10 60 "$DISK_SIZE" 3>&1 1>&2 2>&3) || DISK_SIZE=30 - - # Storage - local storage_list=() - while IFS= read -r stor; do - storage_list+=("$stor" "") - done < <(list_storage_pools) - - if [ ${#storage_list[@]} -gt 0 ]; then - STORAGE=$(whiptail --title "Storage Pool" --menu "Select storage:" 20 70 10 \ - "${storage_list[@]}" 3>&1 1>&2 2>&3) || STORAGE="" - fi - - # Cloud-Init - if whiptail --title "Cloud-Init" --yesno "Enable Cloud-Init?" 10 60; then - ENABLE_CLOUDINIT="yes" - - # Optional credentials - if whiptail --title "Cloud-Init Credentials" --yesno "Configure Cloud-Init credentials now?" 10 60; then - CI_USER=$(whiptail --title "Cloud-Init User" --inputbox "Username:" 10 60 "root" 3>&1 1>&2 2>&3) || CI_USER="" - CI_PASSWORD=$(whiptail --title "Cloud-Init Password" --passwordbox "Password (optional):" 10 60 3>&1 1>&2 2>&3) || CI_PASSWORD="" - CI_SSH_KEY=$(whiptail --title "SSH Public Key" --inputbox "SSH Public Key (optional):" 10 60 "" 3>&1 1>&2 2>&3) || CI_SSH_KEY="" - fi - else - ENABLE_CLOUDINIT="no" - fi - - # Confirm - if whiptail --title "Confirm" --yesno "Create template with these settings?\n\nOS: $OS_KEY\nCores: $CORES\nMemory: ${MEMORY}MB\nDisk: ${DISK_SIZE}GB\nCloud-Init: $ENABLE_CLOUDINIT" 15 60; then clear - create_template + info "Creating VM Template - Interactive Mode" echo "" - read -p "Press Enter to continue..." - fi - - interactive_main_menu + + # Select OS + interactive_select_os || { + warn "No OS selected" + interactive_main_menu + return + } + + # VM ID + local input_vmid + input_vmid=$(whiptail --title "VM ID" --inputbox "Enter VM ID (leave empty for auto):" 10 60 "" 3>&1 1>&2 2>&3) + [ -n "$input_vmid" ] && VMID="$input_vmid" + + # CPU Cores + CORES=$(whiptail --title "CPU Cores" --inputbox "Number of CPU cores:" 10 60 "$CORES" 3>&1 1>&2 2>&3) || CORES=2 + + # Memory + MEMORY=$(whiptail --title "Memory" --inputbox "Memory in MB:" 10 60 "$MEMORY" 3>&1 1>&2 2>&3) || MEMORY=2048 + + # Disk Size + DISK_SIZE=$(whiptail --title "Disk Size" --inputbox "Disk size in GB:" 10 60 "$DISK_SIZE" 3>&1 1>&2 2>&3) || DISK_SIZE=30 + + # Storage + local storage_list=() + while IFS= read -r stor; do + storage_list+=("$stor" "") + done < <(list_storage_pools) + + if [ ${#storage_list[@]} -gt 0 ]; then + STORAGE=$(whiptail --title "Storage Pool" --menu "Select storage:" 20 70 10 \ + "${storage_list[@]}" 3>&1 1>&2 2>&3) || STORAGE="" + fi + + # Cloud-Init + if whiptail --title "Cloud-Init" --yesno "Enable Cloud-Init?" 10 60; then + ENABLE_CLOUDINIT="yes" + + # Optional credentials + if whiptail --title "Cloud-Init Credentials" --yesno "Configure Cloud-Init credentials now?" 10 60; then + CI_USER=$(whiptail --title "Cloud-Init User" --inputbox "Username:" 10 60 "root" 3>&1 1>&2 2>&3) || CI_USER="" + CI_PASSWORD=$(whiptail --title "Cloud-Init Password" --passwordbox "Password (optional):" 10 60 3>&1 1>&2 2>&3) || CI_PASSWORD="" + CI_SSH_KEY=$(whiptail --title "SSH Public Key" --inputbox "SSH Public Key (optional):" 10 60 "" 3>&1 1>&2 2>&3) || CI_SSH_KEY="" + fi + else + ENABLE_CLOUDINIT="no" + fi + + # Confirm + if whiptail --title "Confirm" --yesno "Create template with these settings?\n\nOS: $OS_KEY\nCores: $CORES\nMemory: ${MEMORY}MB\nDisk: ${DISK_SIZE}GB\nCloud-Init: $ENABLE_CLOUDINIT" 15 60; then + clear + create_template + echo "" + read -p "Press Enter to continue..." + fi + + interactive_main_menu } interactive_deploy_vm() { - clear - info "Deploy VM from Template - Interactive Mode" - echo "" - - # Select OS (template must exist) - interactive_select_os || { warn "No OS selected"; interactive_main_menu; return; } - - # Check if template exists - local template_name="${TEMPLATE_PREFIX}-${OS_KEY}" - local template_id=$(find_template_by_name "$template_name") - - if [ -z "$template_id" ]; then - whiptail --title "Error" --msgbox "Template '$template_name' not found!\n\nCreate it first." 10 60 - interactive_main_menu - return - fi - - # Hostname - HOSTNAME=$(whiptail --title "Hostname" --inputbox "Enter hostname:" 10 60 "${OS_KEY}-vm" 3>&1 1>&2 2>&3) - [ -z "$HOSTNAME" ] && HOSTNAME="${OS_KEY}-vm" - - # VM ID - local input_vmid - input_vmid=$(whiptail --title "VM ID" --inputbox "Enter VM ID (leave empty for auto):" 10 60 "" 3>&1 1>&2 2>&3) - [ -n "$input_vmid" ] && VMID="$input_vmid" - - # Disk Size - local template_size=$(qm config "$template_id" | grep "scsi0:" | grep -oP '\d+G' | head -1 | sed 's/G//') - DISK_SIZE=$(whiptail --title "Disk Size" --inputbox "Disk size in GB:" 10 60 "${template_size:-30}" 3>&1 1>&2 2>&3) || DISK_SIZE=30 - - # Post-Install - local post_choice - post_choice=$(whiptail --title "Post-Install" --menu "Install additional software?" 20 70 10 \ - "none" "No additional software" \ - "docker" "Install Docker CE" \ - "podman" "Install Podman" \ - "portainer" "Install Docker + Portainer" \ - 3>&1 1>&2 2>&3) - - POST_INSTALL="${post_choice:-none}" - - # Start VM - if whiptail --title "Start VM" --yesno "Start VM after deployment?" 10 60; then - START_VM="yes" - else - START_VM="no" - fi - - # Confirm - local confirm_msg="Deploy VM with these settings?\n\nTemplate: $template_name\nHostname: $HOSTNAME\nDisk: ${DISK_SIZE}GB" - [ "$POST_INSTALL" != "none" ] && confirm_msg+="\nPost-Install: $POST_INSTALL" - [ "$START_VM" = "yes" ] && confirm_msg+="\nAuto-start: Yes" - - if whiptail --title "Confirm" --yesno "$confirm_msg" 18 60; then clear - deploy_from_template + info "Deploy VM from Template - Interactive Mode" echo "" - read -p "Press Enter to continue..." - fi - - interactive_main_menu + + # Select OS (template must exist) + interactive_select_os || { + warn "No OS selected" + interactive_main_menu + return + } + + # Check if template exists + local template_name="${TEMPLATE_PREFIX}-${OS_KEY}" + local template_id=$(find_template_by_name "$template_name") + + if [ -z "$template_id" ]; then + whiptail --title "Error" --msgbox "Template '$template_name' not found!\n\nCreate it first." 10 60 + interactive_main_menu + return + fi + + # Hostname + HOSTNAME=$(whiptail --title "Hostname" --inputbox "Enter hostname:" 10 60 "${OS_KEY}-vm" 3>&1 1>&2 2>&3) + [ -z "$HOSTNAME" ] && HOSTNAME="${OS_KEY}-vm" + + # VM ID + local input_vmid + input_vmid=$(whiptail --title "VM ID" --inputbox "Enter VM ID (leave empty for auto):" 10 60 "" 3>&1 1>&2 2>&3) + [ -n "$input_vmid" ] && VMID="$input_vmid" + + # Disk Size + local template_size=$(qm config "$template_id" | grep "scsi0:" | grep -oP '\d+G' | head -1 | sed 's/G//') + DISK_SIZE=$(whiptail --title "Disk Size" --inputbox "Disk size in GB:" 10 60 "${template_size:-30}" 3>&1 1>&2 2>&3) || DISK_SIZE=30 + + # Post-Install + local post_choice + post_choice=$(whiptail --title "Post-Install" --menu "Install additional software?" 20 70 10 \ + "none" "No additional software" \ + "docker" "Install Docker CE" \ + "podman" "Install Podman" \ + "portainer" "Install Docker + Portainer" \ + 3>&1 1>&2 2>&3) + + POST_INSTALL="${post_choice:-none}" + + # Start VM + if whiptail --title "Start VM" --yesno "Start VM after deployment?" 10 60; then + START_VM="yes" + else + START_VM="no" + fi + + # Confirm + local confirm_msg="Deploy VM with these settings?\n\nTemplate: $template_name\nHostname: $HOSTNAME\nDisk: ${DISK_SIZE}GB" + [ "$POST_INSTALL" != "none" ] && confirm_msg+="\nPost-Install: $POST_INSTALL" + [ "$START_VM" = "yes" ] && confirm_msg+="\nAuto-start: Yes" + + if whiptail --title "Confirm" --yesno "$confirm_msg" 18 60; then + clear + deploy_from_template + echo "" + read -p "Press Enter to continue..." + fi + + interactive_main_menu } wait_for_vm_ready() { - local vmid=$1 - local timeout=${2:-120} - local elapsed=0 + local vmid=$1 + local timeout=${2:-120} + local elapsed=0 - info "Waiting for VM $vmid to be ready..." + info "Waiting for VM $vmid to be ready..." - while [ $elapsed -lt $timeout ]; do - if qm guest exec $vmid -- test -f /usr/bin/systemctl &>/dev/null; then - ok "VM is ready" - return 0 - fi - sleep 5 - elapsed=$((elapsed + 5)) - done + while [ $elapsed -lt $timeout ]; do + if qm guest exec $vmid -- test -f /usr/bin/systemctl &>/dev/null; then + ok "VM is ready" + return 0 + fi + sleep 5 + elapsed=$((elapsed + 5)) + done - warn "VM readiness timeout after ${timeout}s" - return 1 + warn "VM readiness timeout after ${timeout}s" + return 1 } run_post_install() { - local vmid=$1 - local script_type=$2 + local vmid=$1 + local script_type=$2 - [ -z "$script_type" ] || [ "$script_type" = "none" ] && return 0 + [ -z "$script_type" ] || [ "$script_type" = "none" ] && return 0 - info "Running post-install: $script_type" + info "Running post-install: $script_type" - # Wait for VM to be ready - wait_for_vm_ready "$vmid" 180 || { - warn "VM not ready, skipping post-install" - return 1 - } + # Wait for VM to be ready + wait_for_vm_ready "$vmid" 180 || { + warn "VM not ready, skipping post-install" + return 1 + } - case "$script_type" in - docker) - info "Installing Docker..." - qm guest exec "$vmid" -- bash -c ' + case "$script_type" in + docker) + info "Installing Docker..." + qm guest exec "$vmid" -- bash -c ' curl -fsSL https://get.docker.com | sh && \ systemctl enable --now docker && \ usermod -aG docker $(whoami) 2>/dev/null || true ' || { - warn "Docker installation failed" - return 1 - } - ok "Docker installed successfully" - ;; + warn "Docker installation failed" + return 1 + } + ok "Docker installed successfully" + ;; - podman) - info "Installing Podman..." - qm guest exec "$vmid" -- bash -c ' + podman) + info "Installing Podman..." + qm guest exec "$vmid" -- bash -c ' if command -v apt-get &>/dev/null; then apt-get update && apt-get install -y podman elif command -v dnf &>/dev/null; then @@ -362,19 +378,19 @@ run_post_install() { fi && \ systemctl enable --now podman || true ' || { - warn "Podman installation failed" - return 1 - } - ok "Podman installed successfully" - ;; + warn "Podman installation failed" + return 1 + } + ok "Podman installed successfully" + ;; - portainer) - info "Installing Portainer (requires Docker)..." - # First install Docker - run_post_install "$vmid" "docker" || return 1 + portainer) + info "Installing Portainer (requires Docker)..." + # First install Docker + run_post_install "$vmid" "docker" || return 1 - info "Deploying Portainer container..." - qm guest exec "$vmid" -- bash -c ' + info "Deploying Portainer container..." + qm guest exec "$vmid" -- bash -c ' docker volume create portainer_data && \ docker run -d \ -p 8000:8000 \ @@ -385,20 +401,20 @@ run_post_install() { -v portainer_data:/data \ portainer/portainer-ce:latest ' || { - warn "Portainer deployment failed" - return 1 - } - ok "Portainer deployed successfully" - info "Access Portainer at: https://:9443" - ;; + warn "Portainer deployment failed" + return 1 + } + ok "Portainer deployed successfully" + info "Access Portainer at: https://:9443" + ;; - *) - warn "Unknown post-install script: $script_type" - return 1 - ;; - esac + *) + warn "Unknown post-install script: $script_type" + return 1 + ;; + esac - return 0 + return 0 } # ============================================================================ @@ -406,226 +422,226 @@ run_post_install() { # ============================================================================ list_templates() { - echo -e "\n${BOLD}${CYAN}Available VM Templates:${NC}\n" - echo "┌──────┬────────────────────────────┬─────────┬────────┬──────────┬────────────┐" - echo "│ VMID │ Name │ Cores │ Memory │ Disk │ Cloud-Init │" - echo "├──────┼────────────────────────────┼─────────┼────────┼──────────┼────────────┤" + echo -e "\n${BOLD}${CYAN}Available VM Templates:${NC}\n" + echo "┌──────┬────────────────────────────┬─────────┬────────┬──────────┬────────────┐" + echo "│ VMID │ Name │ Cores │ Memory │ Disk │ Cloud-Init │" + echo "├──────┼────────────────────────────┼─────────┼────────┼──────────┼────────────┤" - local found=0 - while IFS= read -r line; do - local vmid=$(echo "$line" | awk '{print $1}') - local name=$(echo "$line" | awk '{print $2}') + local found=0 + while IFS= read -r line; do + local vmid=$(echo "$line" | awk '{print $1}') + local name=$(echo "$line" | awk '{print $2}') - if is_template "$vmid"; then - local config=$(qm config "$vmid" 2>/dev/null) - local cores=$(echo "$config" | grep "^cores:" | awk '{print $2}') - local memory=$(echo "$config" | grep "^memory:" | awk '{print $2}') - local disk=$(echo "$config" | grep "scsi0:" | grep -oP '\d+G' | head -1) - local has_ci=$(echo "$config" | grep -q "ide2:.*cloudinit" && echo "Yes" || echo "No") + if is_template "$vmid"; then + local config=$(qm config "$vmid" 2>/dev/null) + local cores=$(echo "$config" | grep "^cores:" | awk '{print $2}') + local memory=$(echo "$config" | grep "^memory:" | awk '{print $2}') + local disk=$(echo "$config" | grep "scsi0:" | grep -oP '\d+G' | head -1) + local has_ci=$(echo "$config" | grep -q "ide2:.*cloudinit" && echo "Yes" || echo "No") - printf "│ %-4s │ %-26s │ %-7s │ %-6s │ %-8s │ %-10s │\n" \ - "$vmid" "$name" "${cores:-N/A}" "${memory:-N/A}MB" "${disk:-N/A}" "$has_ci" - found=$((found + 1)) + printf "│ %-4s │ %-26s │ %-7s │ %-6s │ %-8s │ %-10s │\n" \ + "$vmid" "$name" "${cores:-N/A}" "${memory:-N/A}MB" "${disk:-N/A}" "$has_ci" + found=$((found + 1)) + fi + done < <(qm list 2>/dev/null | tail -n +2) + + echo "└──────┴────────────────────────────┴─────────┴────────┴──────────┴────────────┘" + + if [ $found -eq 0 ]; then + echo -e "\n${YELLOW}No templates found.${NC}" + echo -e "Create one with: $0 create --os \n" + else + echo -e "\n${GREEN}Total: $found template(s)${NC}\n" fi - done < <(qm list 2>/dev/null | tail -n +2) - - echo "└──────┴────────────────────────────┴─────────┴────────┴──────────┴────────────┘" - - if [ $found -eq 0 ]; then - echo -e "\n${YELLOW}No templates found.${NC}" - echo -e "Create one with: $0 create --os \n" - else - echo -e "\n${GREEN}Total: $found template(s)${NC}\n" - fi } list_os_options() { - echo -e "\n${BOLD}${CYAN}Available OS Images:${NC}\n" - local i=1 - for key in $(echo "${!OS_IMAGES[@]}" | tr ' ' '\n' | sort); do - printf "%2d) %-20s %s\n" $i "$key" "${OS_IMAGES[$key]}" - i=$((i + 1)) - done - echo "" + echo -e "\n${BOLD}${CYAN}Available OS Images:${NC}\n" + local i=1 + for key in $(echo "${!OS_IMAGES[@]}" | tr ' ' '\n' | sort); do + printf "%2d) %-20s %s\n" $i "$key" "${OS_IMAGES[$key]}" + i=$((i + 1)) + done + echo "" } create_template() { - # Validate OS - [ -z "$OS_KEY" ] && error "OS not specified. Use --os " - [ -z "${OS_IMAGES[$OS_KEY]:-}" ] && error "Unknown OS: $OS_KEY (use --list-os)" + # Validate OS + [ -z "$OS_KEY" ] && error "OS not specified. Use --os " + [ -z "${OS_IMAGES[$OS_KEY]:-}" ] && error "Unknown OS: $OS_KEY (use --list-os)" - local image_url="${OS_IMAGES[$OS_KEY]}" - local template_name="${TEMPLATE_PREFIX}-${OS_KEY}" + local image_url="${OS_IMAGES[$OS_KEY]}" + local template_name="${TEMPLATE_PREFIX}-${OS_KEY}" - # Check if template already exists - local existing_id=$(find_template_by_name "$template_name") - if [ -n "$existing_id" ]; then - warn "Template '$template_name' already exists (ID: $existing_id)" - read -p "Overwrite? (y/N): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - info "Aborted" - exit 0 + # Check if template already exists + local existing_id=$(find_template_by_name "$template_name") + if [ -n "$existing_id" ]; then + warn "Template '$template_name' already exists (ID: $existing_id)" + read -p "Overwrite? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + info "Aborted" + exit 0 + fi + qm destroy "$existing_id" &>/dev/null || true fi - qm destroy "$existing_id" &>/dev/null || true - fi - # Get VM ID - [ -z "$VMID" ] && VMID=$(get_next_vmid $TEMPLATE_ID_START) - [ -z "$STORAGE" ] && STORAGE=$(get_default_storage) + # Get VM ID + [ -z "$VMID" ] && VMID=$(get_next_vmid $TEMPLATE_ID_START) + [ -z "$STORAGE" ] && STORAGE=$(get_default_storage) - info "Creating template: $template_name (ID: $VMID)" - [ "$ENABLE_CLOUDINIT" = "yes" ] && info "Cloud-Init: Enabled" || info "Cloud-Init: Disabled" + info "Creating template: $template_name (ID: $VMID)" + [ "$ENABLE_CLOUDINIT" = "yes" ] && info "Cloud-Init: Enabled" || info "Cloud-Init: Disabled" - # Download/cache image - local cache_dir="/var/lib/vz/template/cache" - local image_file="$cache_dir/$(basename "$image_url")" - mkdir -p "$cache_dir" + # Download/cache image + local cache_dir="/var/lib/vz/template/cache" + local image_file="$cache_dir/$(basename "$image_url")" + mkdir -p "$cache_dir" - if [ ! -f "$image_file" ]; then - info "Downloading image..." - curl -fL --progress-bar -o "$image_file" "$image_url" || error "Download failed" - ok "Image downloaded" - else - ok "Using cached image" - fi - - # Create VM - info "Creating VM shell" - qm create "$VMID" \ - --name "$template_name" \ - --machine "$MACHINE_TYPE" \ - --bios ovmf \ - --cores "$CORES" \ - --memory "$MEMORY" \ - --net0 "virtio,bridge=$BRIDGE" \ - --scsihw virtio-scsi-single \ - --ostype l26 \ - --agent enabled=1 \ - >/dev/null || error "VM creation failed" - - ok "VM shell created" - - # Import disk - info "Importing disk" - local import_out - if command -v "qm" &>/dev/null && qm disk import --help &>/dev/null 2>&1; then - import_out=$(qm disk import "$VMID" "$image_file" "$STORAGE" --format qcow2 2>&1 || true) - else - import_out=$(qm importdisk "$VMID" "$image_file" "$STORAGE" 2>&1 || true) - fi - - local disk_ref=$(echo "$import_out" | grep -oP "vm-$VMID-disk-\d+" | head -1) - [ -z "$disk_ref" ] && disk_ref=$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $5}' | sort | tail -n1) - [ -z "$disk_ref" ] && error "Disk import failed" - - ok "Disk imported: $disk_ref" - - # Configure disks - info "Configuring disks" - if [ "$ENABLE_CLOUDINIT" = "yes" ]; then - qm set "$VMID" \ - --scsi0 "${STORAGE}:${disk_ref},discard=on" \ - --boot order=scsi0 \ - --ide2 "${STORAGE}:cloudinit" \ - >/dev/null || error "Disk configuration failed" - else - qm set "$VMID" \ - --scsi0 "${STORAGE}:${disk_ref},discard=on" \ - --boot order=scsi0 \ - >/dev/null || error "Disk configuration failed" - fi - - # Resize disk - qm resize "$VMID" scsi0 "${DISK_SIZE}G" >/dev/null 2>&1 || warn "Disk resize failed" - ok "Disk configured (${DISK_SIZE}G)" - - # Cloud-Init configuration - if [ "$ENABLE_CLOUDINIT" = "yes" ]; then - if [ -n "$CI_USER" ] && [ -n "$CI_SSH_KEY" ]; then - info "Configuring Cloud-Init credentials" - qm set "$VMID" --ciuser "$CI_USER" >/dev/null - qm set "$VMID" --sshkeys <(echo "$CI_SSH_KEY") >/dev/null - [ -n "$CI_PASSWORD" ] && qm set "$VMID" --cipassword "$CI_PASSWORD" >/dev/null - ok "Cloud-Init configured" + if [ ! -f "$image_file" ]; then + info "Downloading image..." + curl -fL --progress-bar -o "$image_file" "$image_url" || error "Download failed" + ok "Image downloaded" else - info "Cloud-Init drive created (configure after deployment)" + ok "Using cached image" fi - fi - # Convert to template - info "Converting to template" - qm template "$VMID" >/dev/null || error "Template conversion failed" + # Create VM + info "Creating VM shell" + qm create "$VMID" \ + --name "$template_name" \ + --machine "$MACHINE_TYPE" \ + --bios ovmf \ + --cores "$CORES" \ + --memory "$MEMORY" \ + --net0 "virtio,bridge=$BRIDGE" \ + --scsihw virtio-scsi-single \ + --ostype l26 \ + --agent enabled=1 \ + >/dev/null || error "VM creation failed" - ok "Template created successfully!" - echo "" - echo " Template ID: $VMID" - echo " Template Name: $template_name" - echo " OS: $OS_KEY" - echo " Cloud-Init: $ENABLE_CLOUDINIT" - echo "" + ok "VM shell created" + + # Import disk + info "Importing disk" + local import_out + if command -v "qm" &>/dev/null && qm disk import --help &>/dev/null 2>&1; then + import_out=$(qm disk import "$VMID" "$image_file" "$STORAGE" --format qcow2 2>&1 || true) + else + import_out=$(qm importdisk "$VMID" "$image_file" "$STORAGE" 2>&1 || true) + fi + + local disk_ref=$(echo "$import_out" | grep -oP "vm-$VMID-disk-\d+" | head -1) + [ -z "$disk_ref" ] && disk_ref=$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $5}' | sort | tail -n1) + [ -z "$disk_ref" ] && error "Disk import failed" + + ok "Disk imported: $disk_ref" + + # Configure disks + info "Configuring disks" + if [ "$ENABLE_CLOUDINIT" = "yes" ]; then + qm set "$VMID" \ + --scsi0 "${STORAGE}:${disk_ref},discard=on" \ + --boot order=scsi0 \ + --ide2 "${STORAGE}:cloudinit" \ + >/dev/null || error "Disk configuration failed" + else + qm set "$VMID" \ + --scsi0 "${STORAGE}:${disk_ref},discard=on" \ + --boot order=scsi0 \ + >/dev/null || error "Disk configuration failed" + fi + + # Resize disk + qm resize "$VMID" scsi0 "${DISK_SIZE}G" >/dev/null 2>&1 || warn "Disk resize failed" + ok "Disk configured (${DISK_SIZE}G)" + + # Cloud-Init configuration + if [ "$ENABLE_CLOUDINIT" = "yes" ]; then + if [ -n "$CI_USER" ] && [ -n "$CI_SSH_KEY" ]; then + info "Configuring Cloud-Init credentials" + qm set "$VMID" --ciuser "$CI_USER" >/dev/null + qm set "$VMID" --sshkeys <(echo "$CI_SSH_KEY") >/dev/null + [ -n "$CI_PASSWORD" ] && qm set "$VMID" --cipassword "$CI_PASSWORD" >/dev/null + ok "Cloud-Init configured" + else + info "Cloud-Init drive created (configure after deployment)" + fi + fi + + # Convert to template + info "Converting to template" + qm template "$VMID" >/dev/null || error "Template conversion failed" + + ok "Template created successfully!" + echo "" + echo " Template ID: $VMID" + echo " Template Name: $template_name" + echo " OS: $OS_KEY" + echo " Cloud-Init: $ENABLE_CLOUDINIT" + echo "" } deploy_from_template() { - local template_name="${TEMPLATE_PREFIX}-${OS_KEY}" - local template_id=$(find_template_by_name "$template_name") + local template_name="${TEMPLATE_PREFIX}-${OS_KEY}" + local template_id=$(find_template_by_name "$template_name") - [ -z "$template_id" ] && error "Template '$template_name' not found" + [ -z "$template_id" ] && error "Template '$template_name' not found" - if ! is_template "$template_id"; then - error "VM $template_id is not a template" - fi - - [ -z "$VMID" ] && VMID=$(get_next_vmid) - [ -z "$HOSTNAME" ] && HOSTNAME="${OS_KEY}-vm-${VMID}" - - info "Cloning template $template_id -> VM $VMID ($HOSTNAME)" - - # Full clone - qm clone "$template_id" "$VMID" --name "$HOSTNAME" --full 1 >/dev/null || error "Clone failed" - ok "VM cloned" - - # Reconfigure network (remove MAC to get new one) - qm set "$VMID" --delete net0 >/dev/null - qm set "$VMID" --net0 "virtio,bridge=$BRIDGE" >/dev/null - ok "Network reconfigured" - - # Resize if different from template - local template_size=$(qm config "$template_id" | grep "scsi0:" | grep -oP '\d+G' | head -1) - template_size=${template_size%G} - if [ "$DISK_SIZE" -gt "$template_size" ]; then - local diff=$((DISK_SIZE - template_size)) - info "Expanding disk by ${diff}G" - qm resize "$VMID" scsi0 "+${diff}G" >/dev/null 2>&1 || warn "Resize failed" - fi - - # Start VM if requested or if post-install is needed - local need_start="no" - [ "$START_VM" = "yes" ] && need_start="yes" - [ -n "$POST_INSTALL" ] && [ "$POST_INSTALL" != "none" ] && need_start="yes" - - if [ "$need_start" = "yes" ]; then - info "Starting VM" - qm start "$VMID" || { warn "Start failed"; } - ok "VM started" - fi - - # Execute post-install scripts if specified - if [ -n "$POST_INSTALL" ] && [ "$POST_INSTALL" != "none" ]; then - if run_post_install "$VMID" "$POST_INSTALL"; then - ok "Post-install completed: $POST_INSTALL" - else - warn "Post-install had issues, but VM is deployed" + if ! is_template "$template_id"; then + error "VM $template_id is not a template" fi - fi - ok "VM deployed successfully!" - echo "" - echo " VM ID: $VMID" - echo " Hostname: $HOSTNAME" - echo " Template: $template_name (ID: $template_id)" - [ -n "$POST_INSTALL" ] && [ "$POST_INSTALL" != "none" ] && echo " Post-Install: $POST_INSTALL" - echo "" + [ -z "$VMID" ] && VMID=$(get_next_vmid) + [ -z "$HOSTNAME" ] && HOSTNAME="${OS_KEY}-vm-${VMID}" + + info "Cloning template $template_id -> VM $VMID ($HOSTNAME)" + + # Full clone + qm clone "$template_id" "$VMID" --name "$HOSTNAME" --full 1 >/dev/null || error "Clone failed" + ok "VM cloned" + + # Reconfigure network (remove MAC to get new one) + qm set "$VMID" --delete net0 >/dev/null + qm set "$VMID" --net0 "virtio,bridge=$BRIDGE" >/dev/null + ok "Network reconfigured" + + # Resize if different from template + local template_size=$(qm config "$template_id" | grep "scsi0:" | grep -oP '\d+G' | head -1) + template_size=${template_size%G} + if [ "$DISK_SIZE" -gt "$template_size" ]; then + local diff=$((DISK_SIZE - template_size)) + info "Expanding disk by ${diff}G" + qm resize "$VMID" scsi0 "+${diff}G" >/dev/null 2>&1 || warn "Resize failed" + fi + + # Start VM if requested or if post-install is needed + local need_start="no" + [ "$START_VM" = "yes" ] && need_start="yes" + [ -n "$POST_INSTALL" ] && [ "$POST_INSTALL" != "none" ] && need_start="yes" + + if [ "$need_start" = "yes" ]; then + info "Starting VM" + qm start "$VMID" || { warn "Start failed"; } + ok "VM started" + fi + + # Execute post-install scripts if specified + if [ -n "$POST_INSTALL" ] && [ "$POST_INSTALL" != "none" ]; then + if run_post_install "$VMID" "$POST_INSTALL"; then + ok "Post-install completed: $POST_INSTALL" + else + warn "Post-install had issues, but VM is deployed" + fi + fi + + ok "VM deployed successfully!" + echo "" + echo " VM ID: $VMID" + echo " Hostname: $HOSTNAME" + echo " Template: $template_name (ID: $template_id)" + [ -n "$POST_INSTALL" ] && [ "$POST_INSTALL" != "none" ] && echo " Post-Install: $POST_INSTALL" + echo "" } # ============================================================================ @@ -633,7 +649,7 @@ deploy_from_template() { # ============================================================================ usage() { - cat </dev/null 2>&1 || error "whiptail not found. Install it or use CLI mode." - interactive_main_menu - exit 0 + command -v whiptail >/dev/null 2>&1 || error "whiptail not found. Install it or use CLI mode." + interactive_main_menu + exit 0 fi MODE="$1" shift while [ $# -gt 0 ]; do - case "$1" in - --os) - OS_KEY="$2" - shift 2 - ;; - --vmid) - VMID="$2" - shift 2 - ;; - --hostname) - HOSTNAME="$2" - shift 2 - ;; - --cores) - CORES="$2" - shift 2 - ;; - --memory) - MEMORY="$2" - shift 2 - ;; - --disk) - DISK_SIZE="$2" - shift 2 - ;; - --storage) - STORAGE="$2" - shift 2 - ;; - --bridge) - BRIDGE="$2" - shift 2 - ;; - --start) - START_VM="yes" - shift - ;; - --no-cloudinit) - ENABLE_CLOUDINIT="no" - shift - ;; - --ci-user) - CI_USER="$2" - shift 2 - ;; - --ci-password) - CI_PASSWORD="$2" - shift 2 - ;; - --ci-ssh-key) - CI_SSH_KEY="$2" - shift 2 - ;; - --post-install) - POST_INSTALL="$2" - shift 2 - ;; - -h | --help) usage ;; - *) error "Unknown option: $1 (use --help)" ;; - esac + case "$1" in + --os) + OS_KEY="$2" + shift 2 + ;; + --vmid) + VMID="$2" + shift 2 + ;; + --hostname) + HOSTNAME="$2" + shift 2 + ;; + --cores) + CORES="$2" + shift 2 + ;; + --memory) + MEMORY="$2" + shift 2 + ;; + --disk) + DISK_SIZE="$2" + shift 2 + ;; + --storage) + STORAGE="$2" + shift 2 + ;; + --bridge) + BRIDGE="$2" + shift 2 + ;; + --start) + START_VM="yes" + shift + ;; + --no-cloudinit) + ENABLE_CLOUDINIT="no" + shift + ;; + --ci-user) + CI_USER="$2" + shift 2 + ;; + --ci-password) + CI_PASSWORD="$2" + shift 2 + ;; + --ci-ssh-key) + CI_SSH_KEY="$2" + shift 2 + ;; + --post-install) + POST_INSTALL="$2" + shift 2 + ;; + -h | --help) usage ;; + *) error "Unknown option: $1 (use --help)" ;; + esac done # ============================================================================ @@ -794,18 +810,18 @@ trap cleanup_vm EXIT case "$MODE" in create) - create_template - ;; + create_template + ;; deploy) - deploy_from_template - ;; + deploy_from_template + ;; list) - list_templates - ;; + list_templates + ;; list-os) - list_os_options - ;; + list_os_options + ;; *) - error "Unknown command: $MODE (use --help)" - ;; + error "Unknown command: $MODE (use --help)" + ;; esac