#!/usr/bin/env bash set -uo pipefail # Simple interactive toolkit for a Proxmox VE node. # Run as root. Uses whiptail for the GUI and does not execute arbitrary input. if [[ $EUID -ne 0 ]]; then printf 'Run this script as root, for example: sudo bash %s\n' "$0" >&2 exit 1 fi if ! command -v whiptail >/dev/null 2>&1; then printf 'whiptail is required. Install it with: apt install whiptail\n' >&2 exit 1 fi TMPDIR_TOOLKIT=$(mktemp -d) trap 'rm -rf "$TMPDIR_TOOLKIT"' EXIT require_command() { if ! command -v "$1" >/dev/null 2>&1; then whiptail --title "Command unavailable" \ --msgbox "The command '$1' is not installed or not available on this node." 8 70 return 1 fi } valid_id() { [[ "$1" =~ ^[0-9]+$ ]] } ask_id() { local prompt=$1 local value value=$(whiptail --title "Enter ID" --inputbox "$prompt" 9 60 3>&1 1>&2 2>&3) || return 1 if ! valid_id "$value"; then whiptail --title "Invalid ID" --msgbox "Enter a numeric VM or container ID." 8 55 return 1 fi printf '%s' "$value" } show_output() { local title=$1 shift local output="$TMPDIR_TOOLKIT/output.txt" local status=0 local lines cols height width "$@" >"$output" 2>&1 || status=$? if [[ ! -s "$output" ]]; then printf 'Command completed with exit status %s; no output.\n' "$status" >"$output" elif (( status != 0 )); then printf '\nCommand exited with status %s.\n' "$status" >>"$output" fi # Leave room around the dialog; use sensible fallback sizes if tput fails. lines=$(tput lines 2>/dev/null || printf '24') cols=$(tput cols 2>/dev/null || printf '80') height=$((lines - 4)) width=$((cols - 4)) # Keep dimensions within whiptail's practical limits. (( height < 12 )) && height=12 (( width < 50 )) && width=50 whiptail --title "$title" --textbox "$output" "$height" "$width" } confirm_action() { whiptail --title "Confirm" --yesno "$1" 10 70 } vm_menu() { local choice id while true; do choice=$(whiptail --title "Virtual Machines (QEMU)" --menu "Choose an action:" \ 16 72 8 \ "list" "List VMs (qm list)" \ "config" "Show VM configuration" \ "memory" "Show VM memory setting" \ "unlock" "Unlock a VM" \ "back" "Back" 3>&1 1>&2 2>&3) || return case "$choice" in list) require_command qm && show_output "VM list" qm list ;; config|memory|unlock) require_command qm || continue id=$(ask_id "Enter the VMID:") || continue case "$choice" in config) show_output "VM $id configuration" qm config "$id" ;; memory) show_output "VM $id memory" bash -c 'qm config "$1" | grep -E "^memory:" || true' _ "$id" ;; unlock) if confirm_action "Unlock VM $id?\n\nOnly do this if you have confirmed that no task is still running for this VM."; then show_output "Unlock VM $id" qm unlock "$id" fi ;; esac ;; back) return ;; esac done } container_menu() { local choice id while true; do choice=$(whiptail --title "Containers (LXC)" --menu "Choose an action:" \ 14 72 6 \ "list" "List containers (pct list)" \ "config" "Show container configuration" \ "unlock" "Unlock a container" \ "back" "Back" 3>&1 1>&2 2>&3) || return case "$choice" in list) require_command pct && show_output "Container list" pct list ;; config|unlock) require_command pct || continue id=$(ask_id "Enter the container ID:") || continue if [[ "$choice" == "config" ]]; then show_output "Container $id configuration" pct config "$id" elif confirm_action "Unlock container $id?\n\nOnly do this if you have confirmed that no task is still running for this container."; then show_output "Unlock container $id" pct unlock "$id" fi ;; back) return ;; esac done } backup_menu() { local id require_command vzdump || return id=$(ask_id "Enter the VM or container ID to back up:") || return if confirm_action "Start a backup for ID $id using vzdump defaults?\n\nReview your node's backup configuration and storage capacity first."; then show_output "Backup ID $id" vzdump "$id" fi } restore_menu() { local choice id archive choice=$(whiptail --title "Restore" --menu "Choose what to restore:" \ 12 72 4 \ "vm" "Restore a QEMU VM (qmrestore)" \ "ct" "Restore a container (pct restore)" \ "back" "Back" 3>&1 1>&2 2>&3) || return [[ "$choice" == "back" ]] && return id=$(ask_id "Enter the new VMID or container ID:") || return archive=$(whiptail --title "Backup archive" --inputbox \ "Enter the full path to the vzdump archive:" 9 72 3>&1 1>&2 2>&3) || return if [[ -z "$archive" || ! -f "$archive" ]]; then whiptail --title "Archive not found" --msgbox "That file does not exist or is not a regular file." 8 65 return fi if ! confirm_action "Restore '$archive' as ID $id?\n\nRestoring can create or overwrite resources. Confirm the target ID and archive carefully."; then return fi if [[ "$choice" == "vm" ]]; then require_command qmrestore && show_output "Restore VM $id" qmrestore "$archive" "$id" else require_command pct && show_output "Restore container $id" pct restore "$id" "$archive" fi } storage_menu() { local choice while true; do choice=$(whiptail --title "Storage / LVM" --menu "Choose a read-only report:" \ 14 72 6 \ "pvs" "Physical volumes (pvs)" \ "vgs" "Volume groups (vgs)" \ "lvs" "Logical volumes (lvs)" \ "back" "Back" 3>&1 1>&2 2>&3) || return case "$choice" in pvs) require_command pvs && show_output "Physical volumes" pvs ;; vgs) require_command vgs && show_output "Volume groups" vgs ;; lvs) require_command lvs && show_output "Logical volumes" lvs ;; back) return ;; esac done } while true; do choice=$(whiptail --title "Proxmox VE Toolkit" --menu "Select a task:" \ 21 76 13 \ "vms" "Virtual machines (QEMU)" \ "containers" "Containers (LXC)" \ "backup" "Back up a VM or container (vzdump)" \ "restore" "Restore a VM or container" \ "cluster" "Cluster status (pvecm status)" \ "version" "Proxmox version (pveversion -v)" \ "storage" "Storage / LVM reports" \ "perf" "Host performance test (pveperf)" \ "subscription" "Subscription status" \ "dab" "Debian Appliance Builder help" \ "quit" "Exit" 3>&1 1>&2 2>&3) || break case "$choice" in vms) vm_menu ;; containers) container_menu ;; backup) backup_menu ;; restore) restore_menu ;; cluster) require_command pvecm && show_output "Cluster status" pvecm status ;; version) require_command pveversion && show_output "Proxmox VE version" pveversion -v ;; storage) storage_menu ;; perf) if require_command pveperf && confirm_action "Run pveperf?\n\nThis can cause temporary disk activity."; then show_output "Host performance" pveperf fi ;; subscription) require_command pvesubscription && show_output "Subscription status" pvesubscription get ;; dab) if require_command dab; then show_output "Debian Appliance Builder help" dab help fi ;; quit) break ;; esac done