diff --git a/pve-toolkit.sh b/pve-toolkit.sh new file mode 100755 index 0000000..a52d9ae --- /dev/null +++ b/pve-toolkit.sh @@ -0,0 +1,229 @@ +#!/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 + + "$@" >"$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 + + whiptail --title "$title" --textbox "$output" 24 85 +} + +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