From 260b30452ff45f181093c37223270b5791380a87 Mon Sep 17 00:00:00 2001 From: itiligent <94789708+itiligent@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:53:16 +1000 Subject: [PATCH 1/7] ghettoVCB-Express.sh - simplified backup & restore --- ghettoVCB-Express.sh | 313 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 ghettoVCB-Express.sh diff --git a/ghettoVCB-Express.sh b/ghettoVCB-Express.sh new file mode 100644 index 0000000..7363eb7 --- /dev/null +++ b/ghettoVCB-Express.sh @@ -0,0 +1,313 @@ +#!/bin/sh +# ============================================================================= +# ghettoVCB Backup & Restore Wrapper +# +# David Harrop +# August 2025 +# +# Description: +# Wrapper around ghettoVCB to simplify backing up and restoring ESXi VMs. +# Features include: +# - Backup & restore with exclusions +# - Backup & restore individual vms or all +# - Handles VM names with spaces +# - Dry-run mode for preview without execution +# - Prompt to rename vm(s) and edit the restore file prior to restore +# - Cleans up orphan vmkfstools processes or /tmp/ghetto* files after script interruption +# +# Usage: +# ./ghettoVCB-Express.sh --all # Back up all VMs except excluded +# ./ghettoVCB-Express.sh --name vmname or "vm name" # Back up a specific VM +# ./ghettoVCB-Express.sh --restore --all | --name vmname or "vm name" # Restore all VMs except excluded +# ./ghettoVCB-Express.sh --dry-run --all | --name vmname or "vm name" # Preview which VMs will be backed up +# ./ghettoVCB-Express.sh --restore --dry-run --all | --name vmname or "vm name" # Preview restore targets +# ./ghettoVCB-Express.sh --help # Show these options +# +# Requirements: +# - ghettoVCB.sh, ghettoVCB-restore.sh, and ghettoVCB.conf placed in the same directory +# - Must run on an ESXi host with vim-cmd available +# - Must only run one instance of this script at a time +# ============================================================================= + +set -eu + +clear + +# Excluded VMs (exact names, one per line) +EXCLUDE_VMS=" +VMAAA1111 +VMBBB2222 +" + +# Get various script variables +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +VCB_CONF="$SCRIPT_DIR/ghettoVCB.conf" +BACKUPLIST="$SCRIPT_DIR/backuplist.txt" +RESTORELIST="$SCRIPT_DIR/restorelist.txt" +VM_BACKUP_VOLUME=$( + grep -E '^VM_BACKUP_VOLUME=' "$VCB_CONF" \ + | cut -d'=' -f2- \ + | sed 's/^"[[:space:]]*//; s/[[:space:]]*"//; s/[[:space:]]*$//' +) + +# Ensure no trailing slash in path +VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME%/}" +RECOVERY_DATASTORE=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ {print $2; exit}') + +#RECOVERY_DATASTORE= "Manually enter restoration datastore name" +RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/" +RESTORE_DISK_FORMAT="3" # 1 = zeroedthick, 2 = 2gbsparse, 3 = thin, 4 = eagerzeroedthick + +usage() { + echo "Usage: $0 [--restore] [--dry-run] --all | --name " + echo + echo "Examples:" + echo " $0 --all # Back up all VMs except excluded" + echo " $0 --name # Back up a specific VM" + echo " $0 --restore --all # Restore all VMs except excluded" + echo " $0 --restore --name # Restore a specific VM" + echo " $0 --dry-run --all | --name # Preview which VMs would be backed up" + echo " $0 --restore --dry-run --all | --name # Preview restore targets" + echo " $0 --help # Show this help message" + echo + exit 1 +} + +# Gather any exlcuded VMs +is_excluded() { + vm="$1" + while IFS= read -r ex; do + [ -z "$ex" ] && continue + [ "$vm" = "$ex" ] && return 0 + done </dev/null + +cleanup_vmkfstools() { + echo "Checking for leftover vmkfstools processes..." + while true; do + # get PIDs safely + pids=$(ps | grep vmkfstools | grep -v grep | awk '{print $1}' || true) + [ -z "$pids" ] && break + for pid in $pids; do + echo " Killing PID $pid" + kill -9 "$pid" 2>/dev/null || true # ignore errors + sleep 0.2 + done + sleep 0.5 + done +} + +trap 'echo "Script interrupted!"; cleanup_vmkfstools; exit 1' INT TERM + +cleanup_vmkfstools || true + +echo +echo "Excluded VMs:" +echo "$EXCLUDE_VMS" | sed '/^$/d' | while IFS= read -r ex; do + echo " - $ex" +done +echo "--------------------------------------------------" +echo + +# Backup list generator +generate_backuplist() { + > "$BACKUPLIST" + + if [ "$RESTORE_MODE" -eq 1 ]; then + # In restore mode, build list from backup storage + echo "Building backup list from backup repository: $VM_BACKUP_VOLUME" + find "$VM_BACKUP_VOLUME" -maxdepth 1 -mindepth 1 -type d | while IFS= read -r vm_dir; do + vm="$(basename "$vm_dir")" + if ! is_excluded "$vm"; then + echo "$vm" >> "$BACKUPLIST" + fi + done + else + # In backup mode, build list from registered VMs + vim-cmd vmsvc/getallvms | awk ' + NR>1 && $1 ~ /^[0-9]+$/ { + name="" + for(i=2;i<=NF;i++){ + if($i ~ /^\[/) break + name = (name=="" ? $i : name " " $i) + } + if(name != "") print name + }' | while IFS= read -r vm; do + vm="$(echo "$vm" | sed "s/^[[:space:]]*//;s/[[:space:]]*$//")" + if ! is_excluded "$vm"; then + echo "$vm" >> "$BACKUPLIST" + fi + done + fi +} + +if [ "$ARG_MODE" = "all" ]; then + generate_backuplist +else + echo "$ARG_VM" > "$BACKUPLIST" +fi + +# Handle Non-Persistent NFS Restore Mounts. Only runs if enabled in ghettoVCB.conf (ignores comments in ghettoVCB.conf) +ENABLE_NON_PERSISTENT_NFS=$(grep -E '^ENABLE_NON_PERSISTENT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + +if [ "$ENABLE_NON_PERSISTENT_NFS" = "1" ]; then + echo "Non-persistent NFS restore enabled. Preparing to mount NFS datastore..." + + # Read NFS settings from config, ignoring comments and spaces + UNMOUNT_NFS=$(grep -E '^UNMOUNT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + NFS_SERVER=$(grep -E '^NFS_SERVER=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + NFS_MOUNT=$(grep -E '^NFS_MOUNT=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + NFS_LOCAL_NAME=$(grep -E '^NFS_LOCAL_NAME=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + NFS_VM_BACKUP_DIR=$(grep -E '^NFS_VM_BACKUP_DIR=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + + # Check if already mounted + if esxcli storage nfs list | awk '{print $1}' | grep -qw "$NFS_LOCAL_NAME"; then + echo "NFS datastore $NFS_LOCAL_NAME is already mounted. Skipping mount." + else + # Mount NFS datastore + echo "Mounting NFS $NFS_SERVER:$NFS_MOUNT as $NFS_LOCAL_NAME..." + esxcli storage nfs add --host="$NFS_SERVER" --share="$NFS_MOUNT" --volume-name="$NFS_LOCAL_NAME" + + # Verify mount succeeded + if ! esxcli storage nfs list | grep -qw "$NFS_LOCAL_NAME"; then + echo "Error: Failed to mount NFS datastore $NFS_LOCAL_NAME" + exit 1 + fi + fi + + # Update VM_BACKUP_VOLUME to point to the NFS backup path + VM_BACKUP_VOLUME="/vmfs/volumes/$NFS_LOCAL_NAME/$NFS_VM_BACKUP_DIR" + echo "VM_BACKUP_VOLUME set to: $VM_BACKUP_VOLUME" + + # Optional: trap cleanup if UNMOUNT_NFS=1 + if [ "$UNMOUNT_NFS" = "1" ]; then + trap 'echo "Unmounting NFS datastore $NFS_LOCAL_NAME..."; esxcli storage nfs remove --volume-name="$NFS_LOCAL_NAME"' EXIT + fi +fi + +# Restore list generator +generate_restorelist() { + : > "$RESTORELIST" + + if [ ! -f "$BACKUPLIST" ]; then + echo "Error: BACKUPLIST '$BACKUPLIST' not found" >&2 + return 1 + fi + + while IFS= read -r vm || [ -n "$vm" ]; do + [ -z "$vm" ] && continue + + vm_dir=$(find "$VM_BACKUP_VOLUME" -maxdepth 1 -type d -name "${vm}*" 2>/dev/null | sort | tail -n1) + if [ -z "$vm_dir" ]; then + echo "Warning: No backup directory found for '$vm', skipping" >&2 + continue + fi + + latest_gz=$(find "$vm_dir" -maxdepth 1 -type f \( -name "${vm}*.gz" -o -name "${vm}*.tgz" \) 2>/dev/null | sort | tail -n1) + if [ -z "$latest_gz" ]; then + echo "Warning: No matching .gz in '$vm_dir' for '$vm', skipping" >&2 + continue + fi + + # Prompt for rename + read -rp "Restore '$vm' as (press Enter to keep original name): " new_vm > "$RESTORELIST" + + done < "$BACKUPLIST" + + echo + echo "restorelist.txt generated with $(wc -l < "$RESTORELIST") entries" + + # Manual edit option + read -rp "Do you want to manually edit restorelist before continuing? (:wq to save) (y/N): " edit_choice Date: Wed, 27 Aug 2025 15:23:37 +1000 Subject: [PATCH 2/7] fix typo --- ghettoVCB-Express.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghettoVCB-Express.sh b/ghettoVCB-Express.sh index 7363eb7..9ab5550 100644 --- a/ghettoVCB-Express.sh +++ b/ghettoVCB-Express.sh @@ -85,7 +85,7 @@ EOF return 1 } -# Check for availabiity of script dependencies +# Check for availabilty of script dependencies [ -z "$VM_BACKUP_VOLUME" ] && { echo "Error: VM_BACKUP_VOLUME not set in $VCB_CONF"; exit 1; } [ -z "$RECOVERY_DATASTORE" ] && { echo "Error: RECOVERY_DATASTORE not set"; exit 1; } [ ! -f "$VCB_CONF" ] && { echo "Error: ghettoVCB.conf not found"; exit 1; } From 84808161892ce55f157d8357be5ea486e758c88d Mon Sep 17 00:00:00 2001 From: itiligent <94789708+itiligent@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:08:01 +1000 Subject: [PATCH 3/7] remove NFS unmount message --- ghettoVCB-Express.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghettoVCB-Express.sh b/ghettoVCB-Express.sh index 9ab5550..d848395 100644 --- a/ghettoVCB-Express.sh +++ b/ghettoVCB-Express.sh @@ -231,7 +231,7 @@ if [ "$ENABLE_NON_PERSISTENT_NFS" = "1" ]; then # Optional: trap cleanup if UNMOUNT_NFS=1 if [ "$UNMOUNT_NFS" = "1" ]; then - trap 'echo "Unmounting NFS datastore $NFS_LOCAL_NAME..."; esxcli storage nfs remove --volume-name="$NFS_LOCAL_NAME"' EXIT + trap 'esxcli storage nfs remove --volume-name="$NFS_LOCAL_NAME"' EXIT fi fi From ec5df368d002daab7a7d2e6b7dbb9ddf007917d9 Mon Sep 17 00:00:00 2001 From: itiligent <94789708+itiligent@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:32:12 +1000 Subject: [PATCH 4/7] improve NFS settings import --- ghettoVCB-Express.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ghettoVCB-Express.sh b/ghettoVCB-Express.sh index d848395..3cc53a6 100644 --- a/ghettoVCB-Express.sh +++ b/ghettoVCB-Express.sh @@ -197,18 +197,18 @@ else echo "$ARG_VM" > "$BACKUPLIST" fi -# Handle Non-Persistent NFS Restore Mounts. Only runs if enabled in ghettoVCB.conf (ignores comments in ghettoVCB.conf) -ENABLE_NON_PERSISTENT_NFS=$(grep -E '^ENABLE_NON_PERSISTENT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') +# Handle Non-Persistent NFS Mounts. Only runs if NFS is enabled in ghettoVCB.conf (also ignores comments on these lines in ghettoVCB.conf) +ENABLE_NON_PERSISTENT_NFS=$(grep -E '^ENABLE_NON_PERSISTENT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') if [ "$ENABLE_NON_PERSISTENT_NFS" = "1" ]; then echo "Non-persistent NFS restore enabled. Preparing to mount NFS datastore..." # Read NFS settings from config, ignoring comments and spaces - UNMOUNT_NFS=$(grep -E '^UNMOUNT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') - NFS_SERVER=$(grep -E '^NFS_SERVER=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') - NFS_MOUNT=$(grep -E '^NFS_MOUNT=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') - NFS_LOCAL_NAME=$(grep -E '^NFS_LOCAL_NAME=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') - NFS_VM_BACKUP_DIR=$(grep -E '^NFS_VM_BACKUP_DIR=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//;s/^"//;s/"$//;s/^[[:space:]]*//;s/[[:space:]]*$//') + UNMOUNT_NFS=$(grep -E '^UNMOUNT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') + NFS_SERVER=$(grep -E '^NFS_SERVER=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') + NFS_MOUNT=$(grep -E '^NFS_MOUNT=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') + NFS_LOCAL_NAME=$(grep -E '^NFS_LOCAL_NAME=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') + NFS_VM_BACKUP_DIR=$(grep -E '^NFS_VM_BACKUP_DIR=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') # Check if already mounted if esxcli storage nfs list | awk '{print $1}' | grep -qw "$NFS_LOCAL_NAME"; then @@ -272,7 +272,7 @@ generate_restorelist() { echo "restorelist.txt generated with $(wc -l < "$RESTORELIST") entries" # Manual edit option - read -rp "Do you want to manually edit restorelist before continuing? (:wq to save) (y/N): " edit_choice Date: Thu, 11 Sep 2025 14:09:58 +1000 Subject: [PATCH 5/7] honor vmdk names and sanitize paths for slashes --- ghettoVCB-restore.sh | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/ghettoVCB-restore.sh b/ghettoVCB-restore.sh index 5b67d35..b427536 100755 --- a/ghettoVCB-restore.sh +++ b/ghettoVCB-restore.sh @@ -150,6 +150,10 @@ ghettoVCBrestore() { DATASTORE_TO_RESTORE_TO=$(echo "${LINE}" | awk -F ';' '{print $2}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') RESTORE_DISK_FORMAT=$(echo "${LINE}" | awk -F ';' '{print $3}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') RESTORE_VM_NAME=$(echo "${LINE}" | awk -F ';' '{print $4}' | sed 's/"//g' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') + # --- Patched Sanitize restore directory --- + DATASTORE_TO_RESTORE_TO="${DATASTORE_TO_RESTORE_TO%/}" + VM_TO_RESTORE="${VM_TO_RESTORE%/}" + # ----------------------------------------- #figure the disk format to use if [ "${RESTORE_DISK_FORMAT}" -eq 1 ]; then @@ -220,8 +224,19 @@ ghettoVCBrestore() { if [ "${DISK}" != "" ]; then SCSI_CONTROLLER=$(echo ${DISK} | awk -F '=' '{print $1}') - RENAME_DESTINATION_LINE_VMDK_DISK="${SCSI_CONTROLLER} = \"${VM_DISPLAY_NAME}-${NUM_OF_VMDKS}.vmdk\"" - if [ -z "${VMDK_LIST_TO_MODIFY}" ]; then + + # --- Patched naming scheme --- + if [ ${NUM_OF_VMDKS} -eq 0 ]; then + # First disk uses base VM name + RENAME_DESTINATION_LINE_VMDK_DISK="${SCSI_CONTROLLER} = \"${VM_DISPLAY_NAME}.vmdk\"" + else + # Subsequent disks use sequential numbering starting at 2 + SEQ_NUM=$(printf "%06d" ${NUM_OF_VMDKS}) + RENAME_DESTINATION_LINE_VMDK_DISK="${SCSI_CONTROLLER} = \"${VM_DISPLAY_NAME}-${SEQ_NUM}.vmdk\"" + fi + # ----------------------------- + + if [ -z "${VMDK_LIST_TO_MODIFY}" ]; then VMDK_LIST_TO_MODIFY="${DISK},${RENAME_DESTINATION_LINE_VMDK_DISK}" else VMDK_LIST_TO_MODIFY="${VMDK_LIST_TO_MODIFY};${DISK},${RENAME_DESTINATION_LINE_VMDK_DISK}" @@ -232,6 +247,11 @@ ghettoVCBrestore() { NUM_OF_VMDKS=$((NUM_OF_VMDKS+1)) done IFS=${TMP_IFS} + + # --- Patched Sanitize restore directory --- + VM_RESTORE_DIR="${DATASTORE_TO_RESTORE_TO}/${VM_RESTORE_FOLDER_NAME}" + # ---------------------------------- + else logger "Support for .tgz not supported - \"${VM_TO_RESTORE}\" will not be backed up!" IS_TGZ=1 @@ -324,6 +344,10 @@ if [ ! "${IS_TGZ}" == "1" ]; then DS_VMDK_PATH=$(echo "${SOURCE_LINE_VMDK}" | sed 's/\/vmfs\/volumes\///g') VMDK_DATASTORE=$(echo "${DS_VMDK_PATH%%/*}") VMDK_VM=$(echo "${DS_VMDK_PATH##*/}") + + # --- Patched vmdk paths --- + VMDK_DATASTORE="${VMDK_DATASTORE%/}" + # -------------------------- SOURCE_VMDK="${VM_TO_RESTORE}/${VMDK_DATASTORE}/${VMDK_VM}" else SOURCE_VMDK="${VM_TO_RESTORE}/${SOURCE_LINE_VMDK}" From bff6541f5ea8b7e12379fbc0155dc9201b58b167 Mon Sep 17 00:00:00 2001 From: itiligent <94789708+itiligent@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:24:39 +1000 Subject: [PATCH 6/7] Update ghettoVCB-Express.sh --- ghettoVCB-Express.sh | 154 ++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 45 deletions(-) diff --git a/ghettoVCB-Express.sh b/ghettoVCB-Express.sh index 3cc53a6..fb43a50 100644 --- a/ghettoVCB-Express.sh +++ b/ghettoVCB-Express.sh @@ -16,12 +16,12 @@ # - Cleans up orphan vmkfstools processes or /tmp/ghetto* files after script interruption # # Usage: -# ./ghettoVCB-Express.sh --all # Back up all VMs except excluded -# ./ghettoVCB-Express.sh --name vmname or "vm name" # Back up a specific VM -# ./ghettoVCB-Express.sh --restore --all | --name vmname or "vm name" # Restore all VMs except excluded -# ./ghettoVCB-Express.sh --dry-run --all | --name vmname or "vm name" # Preview which VMs will be backed up +# ./ghettoVCB-Express.sh --all # Back up all VMs except excluded +# ./ghettoVCB-Express.sh --name vmname or "vm name" # Back up a specific VM +# ./ghettoVCB-Express.sh --restore --all | --name vmname or "vm name" # Restore all VMs except excluded +# ./ghettoVCB-Express.sh --dry-run --all | --name vmname or "vm name" # Preview which VMs will be backed up # ./ghettoVCB-Express.sh --restore --dry-run --all | --name vmname or "vm name" # Preview restore targets -# ./ghettoVCB-Express.sh --help # Show these options +# ./ghettoVCB-Express.sh --help # Show these options # # Requirements: # - ghettoVCB.sh, ghettoVCB-restore.sh, and ghettoVCB.conf placed in the same directory @@ -33,42 +33,31 @@ set -eu clear +# Set script variables + + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + VCB_CONF="$SCRIPT_DIR/ghettoVCB.conf" + #RECOVERY_DATASTORE=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ {print $2; exit}') # Get first NFS + RECOVERY_DATASTORE="Datastore1" + RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/Recovery/" + RESTORE_DISK_FORMAT="3" # 1 = zeroedthick, 2 = 2gbsparse, 3 = thin, 4 = eagerzeroedthick + # Excluded VMs (exact names, one per line) EXCLUDE_VMS=" -VMAAA1111 -VMBBB2222 +Router1 " -# Get various script variables -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -VCB_CONF="$SCRIPT_DIR/ghettoVCB.conf" -BACKUPLIST="$SCRIPT_DIR/backuplist.txt" -RESTORELIST="$SCRIPT_DIR/restorelist.txt" -VM_BACKUP_VOLUME=$( - grep -E '^VM_BACKUP_VOLUME=' "$VCB_CONF" \ - | cut -d'=' -f2- \ - | sed 's/^"[[:space:]]*//; s/[[:space:]]*"//; s/[[:space:]]*$//' -) - -# Ensure no trailing slash in path -VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME%/}" -RECOVERY_DATASTORE=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ {print $2; exit}') - -#RECOVERY_DATASTORE= "Manually enter restoration datastore name" -RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/" -RESTORE_DISK_FORMAT="3" # 1 = zeroedthick, 2 = 2gbsparse, 3 = thin, 4 = eagerzeroedthick - usage() { - echo "Usage: $0 [--restore] [--dry-run] --all | --name " + echo "Usage: $0 [--restore] [--dry-run] [--all | --name ]" echo echo "Examples:" - echo " $0 --all # Back up all VMs except excluded" - echo " $0 --name # Back up a specific VM" - echo " $0 --restore --all # Restore all VMs except excluded" - echo " $0 --restore --name # Restore a specific VM" - echo " $0 --dry-run --all | --name # Preview which VMs would be backed up" - echo " $0 --restore --dry-run --all | --name # Preview restore targets" - echo " $0 --help # Show this help message" + echo " $0 --all # Back up all VMs except excluded" + echo " $0 --name # Back up a specific VM" + echo " $0 --restore --all # Restore all VMs except excluded" + echo " $0 --restore --name # Restore a specific VM" + echo " $0 --dry-run --all | --name # Preview which VMs would be backed up" + echo " $0 --restore --dry-run --all | --name # Preview restore targets" + echo " $0 --help # Show this help message" echo exit 1 } @@ -85,6 +74,15 @@ EOF return 1 } +# Get the backup volume from ghettoVCB.conf +VM_BACKUP_VOLUME=$( + grep -E '^VM_BACKUP_VOLUME=' "$VCB_CONF" \ + | cut -d'=' -f2- \ + | sed 's/^"[[:space:]]*//; s/[[:space:]]*"//; s/[[:space:]]*$//' +) +# Ensure no trailing slash in path +VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME%/}" + # Check for availabilty of script dependencies [ -z "$VM_BACKUP_VOLUME" ] && { echo "Error: VM_BACKUP_VOLUME not set in $VCB_CONF"; exit 1; } [ -z "$RECOVERY_DATASTORE" ] && { echo "Error: RECOVERY_DATASTORE not set"; exit 1; } @@ -156,10 +154,35 @@ echo "Excluded VMs:" echo "$EXCLUDE_VMS" | sed '/^$/d' | while IFS= read -r ex; do echo " - $ex" done +echo + +# Normalise recovery datastore path +RECOVERY_DATASTORE_PATH="${RECOVERY_DATASTORE_PATH%/}/" + +# Show backup and restore datastores +echo "Backup datastore (backup target from ghettoVCB.conf):" +echo " - $VM_BACKUP_VOLUME" +echo +echo "Recovery datastore (recovery target set in this script):" +echo " - $RECOVERY_DATASTORE_PATH$RECOVERY_DATASTORE" echo "--------------------------------------------------" echo +# Warn if backup and restore datastores are the same +if [ "$(readlink -f "$VM_BACKUP_VOLUME")" = "$(readlink -f "$RECOVERY_DATASTORE_PATH")" ]; then + echo "************************************************************" + echo "WARNING: Backup volume and recovery datastore are the SAME!" + echo " Backup: $VM_BACKUP_VOLUME" + echo " Recovery: $RECOVERY_DATASTORE_PATH" + echo + echo "Restoring into the same datastore as backups may overwrite or" + echo "corrupt your backups. Review your configuration carefully." + echo "************************************************************" + echo +fi + # Backup list generator +BACKUPLIST="$SCRIPT_DIR/backuplist.txt" generate_backuplist() { > "$BACKUPLIST" @@ -236,6 +259,7 @@ if [ "$ENABLE_NON_PERSISTENT_NFS" = "1" ]; then fi # Restore list generator +RESTORELIST="$SCRIPT_DIR/restorelist.txt" generate_restorelist() { : > "$RESTORELIST" @@ -247,36 +271,62 @@ generate_restorelist() { while IFS= read -r vm || [ -n "$vm" ]; do [ -z "$vm" ] && continue - vm_dir=$(find "$VM_BACKUP_VOLUME" -maxdepth 1 -type d -name "${vm}*" 2>/dev/null | sort | tail -n1) + # Find the VM backup directory + #vm_dir=$(find "$VM_BACKUP_VOLUME" -maxdepth 1 -type d -name "${vm}*" 2>/dev/null | sort | tail -n1) + vm_dir=$(find "$VM_BACKUP_VOLUME" -maxdepth 1 -type d -name "$vm" 2>/dev/null | head -n1) + if [ -z "$vm_dir" ]; then echo "Warning: No backup directory found for '$vm', skipping" >&2 continue fi - latest_gz=$(find "$vm_dir" -maxdepth 1 -type f \( -name "${vm}*.gz" -o -name "${vm}*.tgz" \) 2>/dev/null | sort | tail -n1) - if [ -z "$latest_gz" ]; then - echo "Warning: No matching .gz in '$vm_dir' for '$vm', skipping" >&2 - continue + # Check for already decompressed version (directory containing .vmx) + decompressed_dir=$(find "$vm_dir" -maxdepth 1 -type d -exec sh -c 'ls "$1"/*.vmx >/dev/null 2>&1 && echo "$1"' _ {} \; | sort | tail -n1) + + if [ -n "$decompressed_dir" ]; then + latest_backup="$decompressed_dir" + echo "Using decompressed backup for '$vm': $latest_backup" + else + # Fall back to .gz or .tgz files + latest_backup=$(find "$vm_dir" -maxdepth 1 -type f \( -name "${vm}*.gz" -o -name "${vm}*.tgz" \) 2>/dev/null | sort | tail -n1) + if [ -n "$latest_backup" ]; then + echo "Using compressed backup for '$vm': $latest_backup" + else + echo "Warning: No backup found for '$vm', skipping" >&2 + continue + fi fi # Prompt for rename read -rp "Restore '$vm' as (press Enter to keep original name): " new_vm > "$RESTORELIST" + # Clean VM name + new_vm_clean=$(echo "$new_vm" | sed 's#^/*##; s#/*$##') + + # Ensure the base recovery folder exists + mkdir -p "$RECOVERY_DATASTORE_PATH" || { + echo "Error: Failed to create base recovery path '$RECOVERY_DATASTORE_PATH'" + exit 1 + } + + # Write entry to restorelist + # IMPORTANT: pass the base path, not per-VM + printf '"%s;%s;%s;%s"\n' \ + "$latest_backup" \ + "$RECOVERY_DATASTORE_PATH" \ + "$RESTORE_DISK_FORMAT" \ + "$new_vm_clean" >> "$RESTORELIST" done < "$BACKUPLIST" echo echo "restorelist.txt generated with $(wc -l < "$RESTORELIST") entries" - # Manual edit option + # Allow manual editing read -rp "Do you want to manually edit restorelist before continuing? (y/N): " edit_choice Date: Wed, 24 Sep 2025 19:45:35 +1000 Subject: [PATCH 7/7] Update ghettoVCB-Express.sh added interactive recovery datastore selection and option to delete decompressed backups after restore (to save disk space during bulk migration/restore tasks) plus several small improvements --- ghettoVCB-Express.sh | 396 +++++++++++++++++++++++++++---------------- 1 file changed, 249 insertions(+), 147 deletions(-) mode change 100644 => 100755 ghettoVCB-Express.sh diff --git a/ghettoVCB-Express.sh b/ghettoVCB-Express.sh old mode 100644 new mode 100755 index fb43a50..548ea0e --- a/ghettoVCB-Express.sh +++ b/ghettoVCB-Express.sh @@ -2,7 +2,7 @@ # ============================================================================= # ghettoVCB Backup & Restore Wrapper # -# David Harrop +# David Harrop # August 2025 # # Description: @@ -12,16 +12,20 @@ # - Backup & restore individual vms or all # - Handles VM names with spaces # - Dry-run mode for preview without execution -# - Prompt to rename vm(s) and edit the restore file prior to restore +# - Prompt to rename vm(s) and edit the restore file prior to restore # - Cleans up orphan vmkfstools processes or /tmp/ghetto* files after script interruption # # Usage: -# ./ghettoVCB-Express.sh --all # Back up all VMs except excluded -# ./ghettoVCB-Express.sh --name vmname or "vm name" # Back up a specific VM -# ./ghettoVCB-Express.sh --restore --all | --name vmname or "vm name" # Restore all VMs except excluded -# ./ghettoVCB-Express.sh --dry-run --all | --name vmname or "vm name" # Preview which VMs will be backed up -# ./ghettoVCB-Express.sh --restore --dry-run --all | --name vmname or "vm name" # Preview restore targets -# ./ghettoVCB-Express.sh --help # Show these options +# ./ghettoVCB-Express.sh --all # Back up all VMs +# ./ghettoVCB-Express.sh --name vmname | --name "vm name" # Back up a specific VM (with or without spaces) +# ./ghettoVCB-Express.sh --name vmname1 --name vmname2 # Back up a selection of VMs +# ./ghettoVCB-Express.sh --all # Restore all VMs +# ./ghettoVCB-Express.sh --restore --name vmmname # Restore a specific VM +# ./ghettoVCB-Express.sh --restore --name vmname1 --name vmname2 # Restore a selection of VMs +# ./ghettoVCB-Express.sh --dry-run --all | --name vmname # Preview backup targets +# ./ghettoVCB-Express.sh --restore --dry-run --all | --name vmname # Preview restore targets +# ./ghettoVCB-Express.sh --kill # Free any hung backup processes or locked files +# ./ghettoVCB-Express.sh --help # Show these options # # Requirements: # - ghettoVCB.sh, ghettoVCB-restore.sh, and ghettoVCB.conf placed in the same directory @@ -29,40 +33,24 @@ # - Must only run one instance of this script at a time # ============================================================================= -set -eu - -clear - -# Set script variables - - SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - VCB_CONF="$SCRIPT_DIR/ghettoVCB.conf" - #RECOVERY_DATASTORE=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ {print $2; exit}') # Get first NFS - RECOVERY_DATASTORE="Datastore1" - RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/Recovery/" - RESTORE_DISK_FORMAT="3" # 1 = zeroedthick, 2 = 2gbsparse, 3 = thin, 4 = eagerzeroedthick - -# Excluded VMs (exact names, one per line) +# ======== Excluded from backup or restore (exact names, one per line) ======== EXCLUDE_VMS=" Router1 " -usage() { - echo "Usage: $0 [--restore] [--dry-run] [--all | --name ]" - echo - echo "Examples:" - echo " $0 --all # Back up all VMs except excluded" - echo " $0 --name # Back up a specific VM" - echo " $0 --restore --all # Restore all VMs except excluded" - echo " $0 --restore --name # Restore a specific VM" - echo " $0 --dry-run --all | --name # Preview which VMs would be backed up" - echo " $0 --restore --dry-run --all | --name # Preview restore targets" - echo " $0 --help # Show this help message" - echo - exit 1 -} - -# Gather any exlcuded VMs +set -eu +[ -t 1 ] && clear + +# Set a few important script variables +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +VCB_CONF="$SCRIPT_DIR/ghettoVCB.conf" +DEFAULT_RECOVERY_DATASTORE="Datastore1" +DEFAULT_RECOVERY_FOLDER="" +DEFAULT_RECOVERY_DATASTORE_PATH="/vmfs/volumes/$DEFAULT_RECOVERY_DATASTORE/$DEFAULT_RECOVERY_FOLDER" +RESTORE_DISK_FORMAT="3" # 1 = zeroedthick, 2 = 2gbsparse, 3 = thin, 4 = eagerzeroedthick +DELETE_UNZIPPED="true" # Delete decompressed backup copy after each VM restore to prevent disk space blowout + +# Gather all exlcuded VMs is_excluded() { vm="$1" while IFS= read -r ex; do @@ -74,63 +62,57 @@ EOF return 1 } +# Check to make sure vcb.conf is set +[ ! -f "$VCB_CONF" ] && { + echo "Error: ghettoVCB.conf not found" + exit 1 +} + # Get the backup volume from ghettoVCB.conf VM_BACKUP_VOLUME=$( - grep -E '^VM_BACKUP_VOLUME=' "$VCB_CONF" \ - | cut -d'=' -f2- \ - | sed 's/^"[[:space:]]*//; s/[[:space:]]*"//; s/[[:space:]]*$//' + grep -E '^VM_BACKUP_VOLUME=' "$VCB_CONF" | + cut -d'=' -f2- | + sed 's/^"[[:space:]]*//; s/[[:space:]]*"//; s/[[:space:]]*$//' ) -# Ensure no trailing slash in path +# Ensure no trailing slash in backup path VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME%/}" -# Check for availabilty of script dependencies -[ -z "$VM_BACKUP_VOLUME" ] && { echo "Error: VM_BACKUP_VOLUME not set in $VCB_CONF"; exit 1; } -[ -z "$RECOVERY_DATASTORE" ] && { echo "Error: RECOVERY_DATASTORE not set"; exit 1; } -[ ! -f "$VCB_CONF" ] && { echo "Error: ghettoVCB.conf not found"; exit 1; } +# Check that a backup volume value was read from vcb.conf +[ -z "$VM_BACKUP_VOLUME" ] && { + echo "Error: VM_BACKUP_VOLUME not set in $VCB_CONF" + exit 1 +} # Parse script arguments RESTORE_MODE=0 DRYRUN_MODE=0 ARG_MODE="" -ARG_VM="" +ARG_VM_LIST="" -# Show usage if no arguments -echo -if [ $# -eq 0 ]; then - [ $# -eq 0 ] && usage -fi - -# Detect --restore, --dry-run before main args -while [ $# -gt 0 ]; do - case "$1" in - --help) usage;; - --restore) RESTORE_MODE=1 ;; - --dry-run) DRYRUN_MODE=1 ;; - --all) ARG_MODE="all" ;; - --name) - shift - [ -z "$1" ] && { echo "Error: VM name required after --name"; echo; exit 1; } - if is_excluded "$1"; then - echo "Error: VM '$1' is excluded."; echo; exit 1 - fi - ARG_MODE="name" - ARG_VM="$1" - ;; - *) usage;; - esac - shift - -done - -# Ensure required mode args are set -[ -z "$ARG_MODE" ] && usage - -# Clear out leftover temp files or processes from previous (interrupted) ghettoVCB runs -echo "--------------------------------------------------" -echo "Cleaning temporary files..." -rm -rf /tmp/ghetto* 2>/dev/null +usage() { + echo "Usage: $0 [--restore] [--dry-run] [--all | --name ]" + echo + echo "Examples:" + echo " $0 --all # Back up all VMs" + echo " $0 --name | <\"vm name\"> # Back up a specific VM (with or without spaces)" + echo " $0 --name --name # Back up a selection of VMs" + echo " $0 --restore --all # Restore all VMs" + echo " $0 --restore --name # Restore a specific VM" + echo " $0 --restore --name --name # Restore a selection of VMs" + echo " $0 --dry-run --all | --name # Preview backup targets" + echo " $0 --restore --dry-run --all | --name # Preview restore targets" + echo " $0 --kill # Kill any hung backup processes & unlock files" + echo " $0 --help # Show this help message" + echo + exit 0 +} cleanup_vmkfstools() { + # Clear out leftover temp files or processes from previous (interrupted) ghettoVCB runs + echo "---------------------------------------------------------------------------------------------------------------" + echo "Cleaning temporary files..." + rm -rf /tmp/ghettoVCB.work* 2>/dev/null + echo "Checking for leftover vmkfstools processes..." while true; do # get PIDs safely @@ -138,34 +120,133 @@ cleanup_vmkfstools() { [ -z "$pids" ] && break for pid in $pids; do echo " Killing PID $pid" - kill -9 "$pid" 2>/dev/null || true # ignore errors + kill -9 "$pid" 2>/dev/null || true # ignore errors sleep 0.2 done sleep 0.5 done } -trap 'echo "Script interrupted!"; cleanup_vmkfstools; exit 1' INT TERM +cleanup() { + echo "---------------------------------------------------------------------------------------------------------------" + echo "Running cleanup..." -cleanup_vmkfstools || true + # Kill leftover processes + cleanup_vmkfstools || true + + # Unmount NFS if enabled + if [ "${UNMOUNT_NFS:-0}" = "1" ] && [ -n "${NFS_LOCAL_NAME:-}" ]; then + echo "Unmounting NFS datastore: $NFS_LOCAL_NAME" + esxcli storage nfs remove --volume-name="$NFS_LOCAL_NAME" 2>/dev/null || true + fi + # Remove working lists safely + rm -f "${BACKUPLIST:-}" "${RESTORELIST:-}" current_restore_task.txt 2>/dev/null || true + + echo "Cleanup complete." + echo +} + +# Show script usage if no arguments echo -echo "Excluded VMs:" -echo "$EXCLUDE_VMS" | sed '/^$/d' | while IFS= read -r ex; do - echo " - $ex" +[ $# -eq 0 ] && usage + +RECOVERY_DATASTORE="$DEFAULT_RECOVERY_DATASTORE" +RECOVERY_DATASTORE_PATH="$DEFAULT_RECOVERY_DATASTORE_PATH" +[ -z "$RECOVERY_DATASTORE" ] && { + echo "Error: RECOVERY_DATASTORE not set" + exit 1 +} + +while [ $# -gt 0 ]; do + case "$1" in + --all) + ARG_MODE="all" + ;; + --dry-run) + DRYRUN_MODE=1 + ;; + --help) + usage + ;; + --kill) + echo "Killing leftover backup processes and cleaning temporary files..." + echo + cleanup_vmkfstools + echo "Cleanup complete. Exiting." + exit 0 + ;; + --restore) + RESTORE_MODE=1 + RECOVERY_DATASTORES=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ && $2 !~ /^(BOOTBANK|OSDATA)/ {print $2}') + echo "Available recovery datastores:" + PS=1 + for DS in $RECOVERY_DATASTORES; do + echo "$PS) $DS" + PS=$((PS + 1)) + done + + while true; do + read -p "Select datastore number: " NUM + if [ "$NUM" -ge 1 ] 2>/dev/null && [ "$NUM" -le $(echo "$RECOVERY_DATASTORES" | wc -l) ]; then + RECOVERY_DATASTORE=$(echo "$RECOVERY_DATASTORES" | sed -n "${NUM}p") + RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/$DEFAULT_RECOVERY_FOLDER" + echo "You selected: $RECOVERY_DATASTORE" + break + else + echo "Invalid selection, try again." + fi + done + ;; + + --name) + shift + [ -z "$1" ] && { + echo "Error: VM name required after --name" + exit 1 + } + if is_excluded "$1"; then + echo "Error: VM '$1' is excluded." + exit 1 + fi + ARG_MODE="name" + # Append using newline + if [ -z "$ARG_VM_LIST" ]; then + ARG_VM_LIST="$1" + else + ARG_VM_LIST="$ARG_VM_LIST +$1" + fi + ;; + *) usage ;; + esac + shift done + +# Ensure required mode args are set +[ -z "$ARG_MODE" ] && usage + +# Catch all exit paths +trap 'cleanup' INT TERM EXIT + +# Tidy up any orphan processes next +cleanup_vmkfstools || true + +# Show excluded VMs echo +echo "Excluded VMs:" +echo "$EXCLUDE_VMS" | sed '/^$/d' | while IFS= read -r ex; do echo " - $ex"; done -# Normalise recovery datastore path +# Ensure no trailing slash in recovery datastore path RECOVERY_DATASTORE_PATH="${RECOVERY_DATASTORE_PATH%/}/" # Show backup and restore datastores echo "Backup datastore (backup target from ghettoVCB.conf):" echo " - $VM_BACKUP_VOLUME" echo -echo "Recovery datastore (recovery target set in this script):" -echo " - $RECOVERY_DATASTORE_PATH$RECOVERY_DATASTORE" -echo "--------------------------------------------------" +echo "Recovery datastore (subfolder set in DEFAULT_RECOVERY_FOLDER):" +echo " - $RECOVERY_DATASTORE_PATH" +echo "---------------------------------------------------------------------------------------------------------------" echo # Warn if backup and restore datastores are the same @@ -184,15 +265,15 @@ fi # Backup list generator BACKUPLIST="$SCRIPT_DIR/backuplist.txt" generate_backuplist() { - > "$BACKUPLIST" + >"$BACKUPLIST" if [ "$RESTORE_MODE" -eq 1 ]; then # In restore mode, build list from backup storage - echo "Building backup list from backup repository: $VM_BACKUP_VOLUME" + echo "Building list of available backups from repository: $VM_BACKUP_VOLUME" find "$VM_BACKUP_VOLUME" -maxdepth 1 -mindepth 1 -type d | while IFS= read -r vm_dir; do vm="$(basename "$vm_dir")" if ! is_excluded "$vm"; then - echo "$vm" >> "$BACKUPLIST" + echo "$vm" >>"$BACKUPLIST" fi done else @@ -208,19 +289,18 @@ generate_backuplist() { }' | while IFS= read -r vm; do vm="$(echo "$vm" | sed "s/^[[:space:]]*//;s/[[:space:]]*$//")" if ! is_excluded "$vm"; then - echo "$vm" >> "$BACKUPLIST" + echo "$vm" >>"$BACKUPLIST" fi done fi } +# Output the selection array to the backup list +[ "$ARG_MODE" = "all" ] && generate_backuplist || { + : >"$BACKUPLIST" + echo "$ARG_VM_LIST" >>"$BACKUPLIST" +} -if [ "$ARG_MODE" = "all" ]; then - generate_backuplist -else - echo "$ARG_VM" > "$BACKUPLIST" -fi - -# Handle Non-Persistent NFS Mounts. Only runs if NFS is enabled in ghettoVCB.conf (also ignores comments on these lines in ghettoVCB.conf) +# Handle Non-Persistent NFS Mounts. Only runs if NFS is enabled in ghettoVCB.conf (also ignores any comments on these lines in ghettoVCB.conf) ENABLE_NON_PERSISTENT_NFS=$(grep -E '^ENABLE_NON_PERSISTENT_NFS=' "$VCB_CONF" | sed 's/#.*//;s/^.*=//') if [ "$ENABLE_NON_PERSISTENT_NFS" = "1" ]; then @@ -247,21 +327,17 @@ if [ "$ENABLE_NON_PERSISTENT_NFS" = "1" ]; then exit 1 fi fi - + # Update VM_BACKUP_VOLUME to point to the NFS backup path VM_BACKUP_VOLUME="/vmfs/volumes/$NFS_LOCAL_NAME/$NFS_VM_BACKUP_DIR" echo "VM_BACKUP_VOLUME set to: $VM_BACKUP_VOLUME" - # Optional: trap cleanup if UNMOUNT_NFS=1 - if [ "$UNMOUNT_NFS" = "1" ]; then - trap 'esxcli storage nfs remove --volume-name="$NFS_LOCAL_NAME"' EXIT - fi fi # Restore list generator RESTORELIST="$SCRIPT_DIR/restorelist.txt" generate_restorelist() { - : > "$RESTORELIST" + : >"$RESTORELIST" if [ ! -f "$BACKUPLIST" ]; then echo "Error: BACKUPLIST '$BACKUPLIST' not found" >&2 @@ -272,7 +348,6 @@ generate_restorelist() { [ -z "$vm" ] && continue # Find the VM backup directory - #vm_dir=$(find "$VM_BACKUP_VOLUME" -maxdepth 1 -type d -name "${vm}*" 2>/dev/null | sort | tail -n1) vm_dir=$(find "$VM_BACKUP_VOLUME" -maxdepth 1 -type d -name "$vm" 2>/dev/null | head -n1) if [ -z "$vm_dir" ]; then @@ -304,29 +379,30 @@ generate_restorelist() { # Clean VM name new_vm_clean=$(echo "$new_vm" | sed 's#^/*##; s#/*$##') - # Ensure the base recovery folder exists - mkdir -p "$RECOVERY_DATASTORE_PATH" || { - echo "Error: Failed to create base recovery path '$RECOVERY_DATASTORE_PATH'" - exit 1 - } + # Ensure the base recovery folder exists + mkdir -p "$RECOVERY_DATASTORE_PATH" || { + echo "Error: Failed to create base recovery path '$RECOVERY_DATASTORE_PATH'" + exit 1 + } - # Write entry to restorelist - # IMPORTANT: pass the base path, not per-VM - printf '"%s;%s;%s;%s"\n' \ - "$latest_backup" \ - "$RECOVERY_DATASTORE_PATH" \ - "$RESTORE_DISK_FORMAT" \ - "$new_vm_clean" >> "$RESTORELIST" + # Write entry to restorelist + # IMPORTANT: pass the base path, not per-VM + printf '"%s;%s;%s;%s"\n' \ + "$latest_backup" \ + "$RECOVERY_DATASTORE_PATH" \ + "$RESTORE_DISK_FORMAT" \ + "$new_vm_clean" >>"$RESTORELIST" - done < "$BACKUPLIST" + done <"$BACKUPLIST" echo - echo "restorelist.txt generated with $(wc -l < "$RESTORELIST") entries" + echo "restorelist.txt generated with $(wc -l <"$RESTORELIST") entries" # Allow manual editing read -rp "Do you want to manually edit restorelist before continuing? (y/N): " edit_choice "$SCRIPT_DIR/current_restore_task.txt" + + # Run the restore + "$SCRIPT_DIR/ghettoVCB-restore.sh" -c "$SCRIPT_DIR/current_restore_task.txt" + + # Normalize VMX file + vmx_file="$recovery_path/$vmname/$vmname.vmx" + [ -f "$vmx_file" ] && sed -i 's/[[:space:]]*=[[:space:]]*/ = /g' "$vmx_file" + + # Delete decompressed folder if requested + if [ "$DELETE_UNZIPPED" = "true" ]; then + # The extracted folder is a subdirectory under the VM backup dir + backup_dir=$(dirname "$backup_path") # /vmfs/.../Vmname + # Find subdirectory starting with vmname- (the extracted folder) + decompressed_dir=$(find "$backup_dir" -maxdepth 1 -type d -name "$vmname-*" | sort | tail -n1) + + if [ -n "$decompressed_dir" ] && [ -d "$decompressed_dir" ]; then + rm -rf "$decompressed_dir" + echo "Deleted decompressed backup copy for VM '$vmname': $decompressed_dir" + echo + else + echo "No decompressed backup found to delete for VM '$vmname'" + echo + fi + fi + + done <"$RESTORELIST" + + ACTION="Restore" else echo "Running ghettoVCB backup with $BACKUPLIST..." "$SCRIPT_DIR/ghettoVCB.sh" -g "$VCB_CONF" -f "$BACKUPLIST" ACTION="Backup" fi -echo echo "$ACTION completed. VMs processed:" cat "$BACKUPLIST" -echo - -rm -f "$BACKUPLIST" -rm -f "$RESTORELIST" +echo \ No newline at end of file