diff --git a/misc/vm-core.func b/misc/vm-core.func index 607a93b76..8ad2dfc37 100644 --- a/misc/vm-core.func +++ b/misc/vm-core.func @@ -1,5 +1,5 @@ # Copyright (c) 2021-2026 community-scripts ORG -# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/LICENSE +# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE set -euo pipefail SPINNER_PID="" @@ -14,9 +14,18 @@ declare -A MSG_INFO_SHOWN [[ -n "${_CORE_FUNC_LOADED:-}" ]] && return _CORE_FUNC_LOADED=1 +COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main}" + +load_api_functions() { + if ! declare -f post_to_api_vm >/dev/null 2>&1; then + source /dev/stdin <<<$(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/api.func") + fi +} + load_functions() { [[ -n "${__FUNCTIONS_LOADED:-}" ]] && return __FUNCTIONS_LOADED=1 + load_api_functions color formatting icons @@ -31,18 +40,24 @@ load_functions() { arch_check } +load_cloud_init_functions() { + if ! declare -f setup_cloud_init >/dev/null 2>&1; then + source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/cloud-init.func") 2>/dev/null || true + fi +} + # Function to download & save header files get_header() { local app_name=$(echo "${APP,,}" | tr ' ' '-') local app_type=${APP_TYPE:-vm} - local header_url="https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/${app_type}/headers/${app_name}" + local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}" local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}" mkdir -p "$(dirname "$local_header_path")" if [ ! -s "$local_header_path" ]; then if ! curl -fsSL "$header_url" -o "$local_header_path"; then - return 250 + return 1 fi fi @@ -98,6 +113,7 @@ icons() { DNSOK="✔️ " DNSFAIL="${TAB}✖️${TAB}" INFO="${TAB}💡${TAB}${CL}" + CLOUD="${TAB}☁️${TAB}${CL}" OS="${TAB}🖥️${TAB}${CL}" OSVERSION="${TAB}🌟${TAB}${CL}" CONTAINERTYPE="${TAB}📦${TAB}${CL}" @@ -188,18 +204,32 @@ silent() { trap 'error_handler' ERR if [[ $rc -ne 0 ]]; then - # Return instead of exit so that callers can use `$STD cmd || true` - # When no || is used, set -e + ERR trap catches it via error_handler() - export _SILENT_FAILED_RC="$rc" - export _SILENT_FAILED_CMD="$cmd" - export _SILENT_FAILED_LINE="$caller_line" - export _SILENT_FAILED_LOG="$logfile" + # Source explain_exit_code if needed + if ! declare -f explain_exit_code >/dev/null 2>&1; then + source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/error_handler.func") 2>/dev/null || true + fi - return "$rc" + local explanation="" + if declare -f explain_exit_code >/dev/null 2>&1; then + explanation="$(explain_exit_code "$rc")" + fi + + printf "\e[?25h" + if [[ -n "$explanation" ]]; then + msg_error "in line ${caller_line}: exit code ${rc} (${explanation})" + else + msg_error "in line ${caller_line}: exit code ${rc}" + fi + msg_custom "→" "${YWB}" "${cmd}" + + if [[ -s "$logfile" ]]; then + echo -e "\n${TAB}--- Last 20 lines of log ---" + tail -n 20 "$logfile" + echo -e "${TAB}----------------------------\n" + fi + + exit "$rc" fi - - # Clear stale flags on success - unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE _SILENT_FAILED_LOG 2>/dev/null || true } # ------------------------------------------------------------------------------ @@ -230,7 +260,7 @@ curl_handler() { if [[ -z "$url" ]]; then msg_error "no valid url or option entered for curl_handler" - exit 64 + exit 1 fi $STD msg_info "Fetching: $url" @@ -259,7 +289,7 @@ curl_handler() { rm -f /tmp/curl_error.log fi __curl_err_handler "$exit_code" "$url" "$curl_stderr" - exit "$exit_code" + exit 1 # hard exit if exit_code is not 0 fi $STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2 @@ -302,7 +332,7 @@ __curl_err_handler() { esac [[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2 - exit "$exit_code" + exit 1 } # ------------------------------------------------------------------------------ @@ -317,7 +347,7 @@ shell_check() { msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell." echo -e "\nExiting..." sleep 2 - exit 103 + exit fi } @@ -338,11 +368,11 @@ clear_line() { # # - Determines if script should run in verbose mode # - Checks VERBOSE and var_verbose variables -# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info() +# - Also returns true if not running in TTY (pipe/redirect scenario) # ------------------------------------------------------------------------------ is_verbose_mode() { local verbose="${VERBOSE:-${var_verbose:-no}}" - [[ "$verbose" != "no" ]] + [[ "$verbose" != "no" || ! -t 2 ]] } ### dev spinner ### @@ -481,6 +511,20 @@ msg_debug() { fi } +error_handler() { + local exit_code="$?" + local line_number="${1:-unknown}" + local command="${2:-unknown}" + + if declare -f post_update_to_api >/dev/null 2>&1; then + post_update_to_api "failed" "$exit_code" + fi + + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + echo -e "\n$error_message\n" + cleanup_vmid +} + # Displays error message and immediately terminates script fatal() { msg_error "$1" @@ -516,9 +560,13 @@ cleanup_vmid() { cleanup() { local exit_code=$? + stop_spinner if [[ "$(dirs -p | wc -l)" -gt 1 ]]; then popd >/dev/null || true fi + if [[ -n "${TEMP_DIR:-}" && -d "$TEMP_DIR" ]]; then + rm -rf "$TEMP_DIR" + fi # Report final telemetry status if post_to_api_vm was called but no update was sent if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then if declare -f post_update_to_api >/dev/null 2>&1; then @@ -538,18 +586,37 @@ check_root() { msg_error "Please run this script as root." echo -e "\nExiting..." sleep 2 - exit 104 + exit fi } pve_check() { - if ! pveversion | grep -Eq "pve-manager/(8\.[1-4]|9\.[0-2])(\.[0-9]+)*"; then - msg_error "This version of Proxmox Virtual Environment is not supported" - echo -e "Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.2." - echo -e "Exiting..." - sleep 2 - exit 105 + local pve_ver + pve_ver="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + 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 105 + fi + return 0 fi + + if [[ "$pve_ver" =~ ^9\.([0-9]+) ]]; then + local minor="${BASH_REMATCH[1]}" + if ((minor < 0 || minor > 2)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.2" + exit 105 + fi + return 0 + fi + + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.9 or 9.0 – 9.2" + exit 105 } arch_check() { @@ -558,50 +625,487 @@ arch_check() { echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" echo -e "Exiting..." sleep 2 - exit 106 + exit + fi +} + +ssh_check() { + if command -v pveversion >/dev/null 2>&1 && [ -n "${SSH_CLIENT:-}" ]; 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 + : + else + clear + exit + fi fi } exit_script() { clear echo -e "\n${CROSS}${RD}User exited script${CL}\n" - exit 0 + exit +} + +sanitize_vm_hostname() { + local hostname="${1,,}" + hostname=$(echo "$hostname" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//') + echo "${hostname:0:63}" +} + +vm_confirm_new_vm() { + local title="$1" + local message="$2" + local height="${3:-10}" + local width="${4:-58}" + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" --yesno "$message" "$height" "$width" +} + +vm_choose_settings_mode() { + local message="${1:-Use Default Settings?}" + local height="${2:-10}" + local width="${3:-58}" + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "$message" --no-button Advanced "$height" "$width" +} + +vm_confirm_advanced_settings() { + local message="$1" + local height="${2:-10}" + local width="${3:-58}" + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "$message" --no-button Do-Over "$height" "$width" +} + +vm_prompt_vmid() { + local default_vmid="${1:-$(get_valid_nextid)}" + + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 "$default_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 +} + +vm_apply_machine_type() { + local machine_type="${1:-i440fx}" + + if [ "$machine_type" = "q35" ]; then + MACHINE_TYPE="q35" + FORMAT="" + MACHINE=" -machine q35" + else + MACHINE_TYPE="i440fx" + FORMAT=",efitype=4m" + MACHINE="" + fi +} + +vm_machine_type_label() { + case "${1:-i440fx}" in + q35) + echo "Q35 (Modern)" + ;; + *) + echo "i440fx" + ;; + esac +} + +vm_prompt_machine_type() { + local default_machine="${1:-i440fx}" + local i440fx_default="ON" + local q35_default="OFF" + local machine_choice + + if [ "$default_machine" = "q35" ]; then + i440fx_default="OFF" + q35_default="ON" + fi + + if machine_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ + "i440fx" "Machine i440fx" "$i440fx_default" \ + "q35" "Machine q35" "$q35_default" \ + 3>&1 1>&2 2>&3); then + vm_apply_machine_type "$machine_choice" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$(vm_machine_type_label "$MACHINE_TYPE")${CL}" + else + exit_script + fi +} + +vm_prompt_cloud_init() { + local default_user="${1:-root}" + + USE_CLOUD_INIT="no" + load_cloud_init_functions + + if ! declare -f configure_cloud_init_interactive >/dev/null 2>&1; then + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}unavailable${CL}" + return 1 + fi + + configure_cloud_init_interactive "$default_user" || true + USE_CLOUD_INIT="${CLOUDINIT_ENABLE:-no}" + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}${USE_CLOUD_INIT}${CL}" + + if [ "$USE_CLOUD_INIT" = "yes" ] && declare -f configure_cloudinit_ssh_keys >/dev/null 2>&1; then + configure_cloudinit_ssh_keys || true + fi + + return 0 +} + +vm_prompt_disk_size() { + local default_size="${1:-8G}" + local prompt_message="${2:-Set Disk Size in GiB (e.g., 10, 20)}" + + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "$prompt_message" 8 58 "$default_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 + else + exit_script + fi +} + +vm_prompt_disk_cache() { + local default_cache="${1:-none}" + local none_default="ON" + local write_default="OFF" + local cache_choice + + if [ "$default_cache" = "writethrough" ]; then + none_default="OFF" + write_default="ON" + fi + + if cache_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "None (Default)" "$none_default" \ + "1" "Write Through" "$write_default" \ + 3>&1 1>&2 2>&3); then + if [ "$cache_choice" = "1" ]; then + DISK_CACHE="cache=writethrough," + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" + else + DISK_CACHE="" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + fi + else + exit_script + fi +} + +vm_prompt_hostname() { + local default_hostname="${1:-vm}" + local adjusted_hostname + local input_hostname + + if input_hostname=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$default_hostname" --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$input_hostname" ]; then + HN="$default_hostname" + else + adjusted_hostname=$(sanitize_vm_hostname "$input_hostname") + HN="${adjusted_hostname:-$default_hostname}" + if [ "$HN" != "${input_hostname,,}" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58 + fi + fi + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + else + exit_script + fi +} + +vm_prompt_cpu_model() { + local default_model="${1:-kvm64}" + local kvm_default="ON" + local host_default="OFF" + local cpu_choice + + if [ "$default_model" = "host" ]; then + kvm_default="OFF" + host_default="ON" + fi + + if cpu_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "KVM64 (Default)" "$kvm_default" \ + "1" "Host" "$host_default" \ + 3>&1 1>&2 2>&3); then + if [ "$cpu_choice" = "1" ]; then + CPU_TYPE=" -cpu host" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" + else + CPU_TYPE="" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + fi + else + exit_script + fi +} + +vm_prompt_cpu_cores() { + local default_cores="${1:-2}" + + while true; do + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 "$default_cores" --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$CORE_COUNT" ]; then + CORE_COUNT="$default_cores" + fi + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58 + else + exit_script + fi + done +} + +vm_prompt_ram() { + local default_ram="${1:-2048}" + + while true; do + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 "$default_ram" --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$RAM_SIZE" ]; then + RAM_SIZE="$default_ram" + fi + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58 + else + exit_script + fi + done +} + +vm_prompt_bridge() { + local default_bridge="${1:-vmbr0}" + + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 "$default_bridge" --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$BRG" ]; then + BRG="$default_bridge" + fi + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + else + exit_script + fi +} + +vm_prompt_mac() { + local default_mac="${1:-$GEN_MAC}" + local input_mac + + while true; do + if input_mac=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 "$default_mac" --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$input_mac" ]; then + MAC="$default_mac" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" + break + fi + if [[ "$input_mac" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then + MAC="$input_mac" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58 + else + exit_script + fi + done +} + +vm_prompt_vlan() { + local default_vlan="${1:-}" + local input_vlan + + while true; do + if input_vlan=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan (leave blank for default)" 8 58 "$default_vlan" --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$input_vlan" ]; then + VLAN="" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" + break + fi + if [[ "$input_vlan" =~ ^[0-9]+$ ]] && [ "$input_vlan" -ge 1 ] && [ "$input_vlan" -le 4094 ]; then + VLAN=",tag=$input_vlan" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$input_vlan${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit_script + fi + done +} + +vm_prompt_mtu() { + local default_mtu="${1:-}" + local input_mtu + + while true; do + if input_mtu=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 "$default_mtu" --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$input_mtu" ]; then + MTU="" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" + break + fi + if [[ "$input_mtu" =~ ^[0-9]+$ ]] && [ "$input_mtu" -ge 576 ] && [ "$input_mtu" -le 65520 ]; then + MTU=",mtu=$input_mtu" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$input_mtu${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58 + else + exit_script + fi + done +} + +vm_prompt_start_vm() { + local default_start="${1:-yes}" + local default_flag=() + + if [ "$default_start" = "no" ]; then + default_flag=(--defaultno) + fi + + if whiptail --backtitle "Proxmox VE Helper Scripts" "${default_flag[@]}" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58; then + START_VM="yes" + else + START_VM="no" + fi + + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}" +} + +vm_apply_storage_layout() { + local storage_type="$1" + + case $storage_type in + nfs | dir | cifs) + DISK_EXT=".qcow2" + DISK_REF="$VMID/" + DISK_IMPORT_FORMAT="qcow2" + THIN="" + ;; + btrfs) + DISK_EXT=".raw" + DISK_REF="$VMID/" + DISK_IMPORT_FORMAT="raw" + FORMAT=",efitype=4m" + THIN="" + ;; + *) + DISK_EXT="" + DISK_REF="" + DISK_IMPORT_FORMAT="raw" + ;; + esac +} + +vm_select_storage() { + local hostname="${1:-${HN:-vm}}" + local storage_menu=() + local msg_max_length=0 + local line tag type free item + local offset=2 + local valid_storage + + 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 " + 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_storage=$(pvesm status -content images | awk 'NR>1') + if [ -z "$valid_storage" ]; then + msg_error "Unable to detect a valid storage location." + exit + elif [ $((${#storage_menu[@]} / 3)) -eq 1 ]; then + STORAGE=${storage_menu[0]} + else + if [ -n "${SPINNER_PID:-}" ] && ps -p "$SPINNER_PID" >/dev/null 2>&1; then + kill "$SPINNER_PID" >/dev/null 2>&1 || true + SPINNER_ACTIVE=0 + printf "\r\e[2K" >&2 + fi + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for ${hostname}?\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}." + + STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}') + vm_apply_storage_layout "$STORAGE_TYPE" +} + +vm_define_disk_references() { + local disk_count="${1:-2}" + local i disk_name + + for ((i = 0; i < disk_count; i++)); do + disk_name="vm-${VMID}-disk-${i}${DISK_EXT:-}" + printf -v "DISK${i}" '%s' "$disk_name" + printf -v "DISK${i}_REF" '%s' "${STORAGE}:${DISK_REF:-}${disk_name}" + done } check_hostname_conflict() { local hostname="$1" if qm list | awk '{print $2}' | grep -qx "$hostname"; then msg_error "Hostname $hostname already in use by another VM." - exit 206 + exit 1 fi } set_description() { - local app_name script_slug script_url donate_url - app_name=$(echo "${APP,,}" | tr ' ' '-') - script_slug="${SCRIPT_SLUG:-${app_name}}" - script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')" - script_url="https://community-scripts.org/scripts/${script_slug}" - donate_url="https://community-scripts.org/donate" + local description_title="${APP:-${NSAPP} VM}" DESCRIPTION=$( cat < - + Logo -

${NSAPP} VM

+

${description_title}

- - Sponsoring and donations - -

- -

- - Open script page + + spend Coffee