Skip to content

Commit a6df5e8

Browse files
committed
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)
1 parent 129d9b7 commit a6df5e8

1 file changed

Lines changed: 126 additions & 65 deletions

File tree

ghettoVCB-Express.sh

Lines changed: 126 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@
1212
# - Backup & restore individual vms or all
1313
# - Handles VM names with spaces
1414
# - Dry-run mode for preview without execution
15-
# - Prompt to rename vm(s) and edit the restore file prior to restore
15+
# - Prompt to rename vm(s) and edit the restore file prior to restore
1616
# - Cleans up orphan vmkfstools processes or /tmp/ghetto* files after script interruption
1717
#
1818
# Usage:
1919
# ./ghettoVCB-Express.sh --all # Back up all VMs
2020
# ./ghettoVCB-Express.sh --name vmname | --name "vm name" # Back up a specific VM (with or without spaces)
2121
# ./ghettoVCB-Express.sh --name vmname1 --name vmname2 # Back up a selection of VMs
2222
# ./ghettoVCB-Express.sh --all # Restore all VMs
23-
# ./ghettoVCB-Express.sh --restore --name vmmname # Restore a specific VM"
23+
# ./ghettoVCB-Express.sh --restore --name vmmname # Restore a specific VM
2424
# ./ghettoVCB-Express.sh --restore --name vmname1 --name vmname2 # Restore a selection of VMs
2525
# ./ghettoVCB-Express.sh --dry-run --all | --name vmname # Preview backup targets
2626
# ./ghettoVCB-Express.sh --restore --dry-run --all | --name vmname # Preview restore targets
27-
# ./ghettoVCB-Express.sh --kill # Free any hung backup processes or locked files
27+
# ./ghettoVCB-Express.sh --kill # Free any hung backup processes or locked files
2828
# ./ghettoVCB-Express.sh --help # Show these options
2929
#
3030
# Requirements:
@@ -33,40 +33,22 @@
3333
# - Must only run one instance of this script at a time
3434
# =============================================================================
3535

36-
set -eu
36+
# ======== Excluded from backup or restore (exact names, one per line) ========
37+
EXCLUDE_VMS="
38+
Router1
39+
"
3740

38-
clear
41+
set -eu
42+
[ -t 1 ] && clear
3943

40-
# Set script variables
44+
# Set a few important script variables
4145
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
4246
VCB_CONF="$SCRIPT_DIR/ghettoVCB.conf"
43-
#RECOVERY_DATASTORE=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ {print $2; exit}') # Automatically get first NFS
44-
RECOVERY_DATASTORE="Datastore1"
45-
RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/"
47+
DEFAULT_RECOVERY_DATASTORE="Datastore1"
48+
DEFAULT_RECOVERY_FOLDER=""
49+
DEFAULT_RECOVERY_DATASTORE_PATH="/vmfs/volumes/$DEFAULT_RECOVERY_DATASTORE/$DEFAULT_RECOVERY_FOLDER"
4650
RESTORE_DISK_FORMAT="3" # 1 = zeroedthick, 2 = 2gbsparse, 3 = thin, 4 = eagerzeroedthick
47-
48-
# List of excluded VMs (exact names, one per line)
49-
EXCLUDE_VMS="
50-
Router1
51-
"
52-
53-
usage() {
54-
echo "Usage: $0 [--restore] [--dry-run] [--all | --name <vmname>]"
55-
echo
56-
echo "Examples:"
57-
echo " $0 --all # Back up all VMs"
58-
echo " $0 --name <vmname> | <\"vm name\"> # Back up a specific VM (with or without spaces)"
59-
echo " $0 --name <vmname1> --name <vmname2> # Back up a selection of VMs"
60-
echo " $0 --restore --all # Restore all VMs"
61-
echo " $0 --restore --name <vmname> # Restore a specific VM"
62-
echo " $0 --restore --name <vmname1> --name <vnmane2> # Restore a selection of VMs"
63-
echo " $0 --dry-run --all | --name <vmname> # Preview backup targets"
64-
echo " $0 --restore --dry-run --all | --name <vmname> # Preview restore targets"
65-
echo " $0 --kill # Kill any hung backup processes & unlock files"
66-
echo " $0 --help # Show this help message"
67-
echo
68-
exit 1
69-
}
51+
DELETE_UNZIPPED="true" # Delete decompressed backup copy after each VM restore to prevent disk space blowout
7052

7153
# Gather all exlcuded VMs
7254
is_excluded() {
@@ -80,11 +62,7 @@ EOF
8062
return 1
8163
}
8264

83-
# Check to make sure recovery datastore and vcb.conf are set
84-
[ -z "$RECOVERY_DATASTORE" ] && {
85-
echo "Error: RECOVERY_DATASTORE not set"
86-
exit 1
87-
}
65+
# Check to make sure vcb.conf is set
8866
[ ! -f "$VCB_CONF" ] && {
8967
echo "Error: ghettoVCB.conf not found"
9068
exit 1
@@ -96,7 +74,7 @@ VM_BACKUP_VOLUME=$(
9674
cut -d'=' -f2- |
9775
sed 's/^"[[:space:]]*//; s/[[:space:]]*"//; s/[[:space:]]*$//'
9876
)
99-
# Ensure no trailing slash in path
77+
# Ensure no trailing slash in backup path
10078
VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME%/}"
10179

10280
# Check that a backup volume value was read from vcb.conf
@@ -111,9 +89,27 @@ DRYRUN_MODE=0
11189
ARG_MODE=""
11290
ARG_VM_LIST=""
11391

92+
usage() {
93+
echo "Usage: $0 [--restore] [--dry-run] [--all | --name <vmname>]"
94+
echo
95+
echo "Examples:"
96+
echo " $0 --all # Back up all VMs"
97+
echo " $0 --name <vmname> | <\"vm name\"> # Back up a specific VM (with or without spaces)"
98+
echo " $0 --name <vmname1> --name <vmname2> # Back up a selection of VMs"
99+
echo " $0 --restore --all # Restore all VMs"
100+
echo " $0 --restore --name <vmname> # Restore a specific VM"
101+
echo " $0 --restore --name <vmname1> --name <vnname2> # Restore a selection of VMs"
102+
echo " $0 --dry-run --all | --name <vmname> # Preview backup targets"
103+
echo " $0 --restore --dry-run --all | --name <vmname> # Preview restore targets"
104+
echo " $0 --kill # Kill any hung backup processes & unlock files"
105+
echo " $0 --help # Show this help message"
106+
echo
107+
exit 0
108+
}
109+
114110
cleanup_vmkfstools() {
115111
# Clear out leftover temp files or processes from previous (interrupted) ghettoVCB runs
116-
echo "--------------------------------------------------"
112+
echo "---------------------------------------------------------------------------------------------------------------"
117113
echo "Cleaning temporary files..."
118114
rm -rf /tmp/ghettoVCB.work* 2>/dev/null
119115

@@ -132,7 +128,7 @@ cleanup_vmkfstools() {
132128
}
133129

134130
cleanup() {
135-
echo "--------------------------------------------------"
131+
echo "---------------------------------------------------------------------------------------------------------------"
136132
echo "Running cleanup..."
137133

138134
# Kill leftover processes
@@ -145,33 +141,64 @@ cleanup() {
145141
fi
146142

147143
# Remove working lists safely
148-
rm -f "${BACKUPLIST:-}" "${RESTORELIST:-}" 2>/dev/null || true
144+
rm -f "${BACKUPLIST:-}" "${RESTORELIST:-}" current_restore_task.txt 2>/dev/null || true
149145

150146
echo "Cleanup complete."
151147
echo
152148
}
153149

154-
# Catch all exit paths
155-
trap 'cleanup' INT TERM EXIT
156-
157150
# Show script usage if no arguments
158151
echo
159152
[ $# -eq 0 ] && usage
160153

161-
# Detect --restore, --dry-run before main args
154+
RECOVERY_DATASTORE="$DEFAULT_RECOVERY_DATASTORE"
155+
RECOVERY_DATASTORE_PATH="$DEFAULT_RECOVERY_DATASTORE_PATH"
156+
[ -z "$RECOVERY_DATASTORE" ] && {
157+
echo "Error: RECOVERY_DATASTORE not set"
158+
exit 1
159+
}
160+
162161
while [ $# -gt 0 ]; do
163162
case "$1" in
164-
--all) ARG_MODE="all" ;;
165-
--dry-run) DRYRUN_MODE=1 ;;
166-
--help) usage ;;
163+
--all)
164+
ARG_MODE="all"
165+
;;
166+
--dry-run)
167+
DRYRUN_MODE=1
168+
;;
169+
--help)
170+
usage
171+
;;
167172
--kill)
168173
echo "Killing leftover backup processes and cleaning temporary files..."
169174
echo
170175
cleanup_vmkfstools
171176
echo "Cleanup complete. Exiting."
172177
exit 0
173178
;;
174-
--restore) RESTORE_MODE=1 ;;
179+
--restore)
180+
RESTORE_MODE=1
181+
RECOVERY_DATASTORES=$(esxcli storage filesystem list | awk '$1 ~ /^\/vmfs/ && $2 !~ /^(BOOTBANK|OSDATA)/ {print $2}')
182+
echo "Available recovery datastores:"
183+
PS=1
184+
for DS in $RECOVERY_DATASTORES; do
185+
echo "$PS) $DS"
186+
PS=$((PS + 1))
187+
done
188+
189+
while true; do
190+
read -p "Select datastore number: " NUM
191+
if [ "$NUM" -ge 1 ] 2>/dev/null && [ "$NUM" -le $(echo "$RECOVERY_DATASTORES" | wc -l) ]; then
192+
RECOVERY_DATASTORE=$(echo "$RECOVERY_DATASTORES" | sed -n "${NUM}p")
193+
RECOVERY_DATASTORE_PATH="/vmfs/volumes/$RECOVERY_DATASTORE/$DEFAULT_RECOVERY_FOLDER"
194+
echo "You selected: $RECOVERY_DATASTORE"
195+
break
196+
else
197+
echo "Invalid selection, try again."
198+
fi
199+
done
200+
;;
201+
175202
--name)
176203
shift
177204
[ -z "$1" ] && {
@@ -199,6 +226,9 @@ done
199226
# Ensure required mode args are set
200227
[ -z "$ARG_MODE" ] && usage
201228

229+
# Catch all exit paths
230+
trap 'cleanup' INT TERM EXIT
231+
202232
# Tidy up any orphan processes next
203233
cleanup_vmkfstools || true
204234

@@ -207,16 +237,16 @@ echo
207237
echo "Excluded VMs:"
208238
echo "$EXCLUDE_VMS" | sed '/^$/d' | while IFS= read -r ex; do echo " - $ex"; done
209239

210-
# Normalise recovery datastore path
240+
# Ensure no trailing slash in recovery datastore path
211241
RECOVERY_DATASTORE_PATH="${RECOVERY_DATASTORE_PATH%/}/"
212242

213243
# Show backup and restore datastores
214244
echo "Backup datastore (backup target from ghettoVCB.conf):"
215245
echo " - $VM_BACKUP_VOLUME"
216246
echo
217-
echo "Recovery datastore (recovery target set in this script):"
218-
echo " - $RECOVERY_DATASTORE_PATH$RECOVERY_DATASTORE"
219-
echo "--------------------------------------------------"
247+
echo "Recovery datastore (subfolder set in DEFAULT_RECOVERY_FOLDER):"
248+
echo " - $RECOVERY_DATASTORE_PATH"
249+
echo "---------------------------------------------------------------------------------------------------------------"
220250
echo
221251

222252
# Warn if backup and restore datastores are the same
@@ -239,7 +269,7 @@ generate_backuplist() {
239269

240270
if [ "$RESTORE_MODE" -eq 1 ]; then
241271
# In restore mode, build list from backup storage
242-
echo "Building backup list from backup repository: $VM_BACKUP_VOLUME"
272+
echo "Building list of available backups from repository: $VM_BACKUP_VOLUME"
243273
find "$VM_BACKUP_VOLUME" -maxdepth 1 -mindepth 1 -type d | while IFS= read -r vm_dir; do
244274
vm="$(basename "$vm_dir")"
245275
if ! is_excluded "$vm"; then
@@ -370,6 +400,7 @@ generate_restorelist() {
370400

371401
# Allow manual editing
372402
read -rp "Do you want to manually edit restorelist before continuing? (y/N): " edit_choice </dev/tty
403+
echo
373404
case "$edit_choice" in
374405
[yY]*) ${EDITOR:-vi} "$RESTORELIST" ;;
375406
esac
@@ -379,40 +410,70 @@ generate_restorelist() {
379410
if [ "$DRYRUN_MODE" -eq 1 ]; then
380411
if [ "$RESTORE_MODE" -eq 1 ]; then
381412
echo "[DRY-RUN] Restore would be run on the following VMs:"
413+
generate_restorelist
414+
echo
415+
cat "$RESTORELIST"
382416
else
383417
echo "[DRY-RUN] Backup would be run on the following VMs:"
418+
cat "$BACKUPLIST"
384419
fi
385-
cat "$BACKUPLIST"
386420
exit 0
387421
fi
388422

389423
# Execute backup/restore
390424
if [ "$RESTORE_MODE" -eq 1 ]; then
391-
echo "Running ghettoVCB-restore with $BACKUPLIST..."
425+
echo "Running ghettoVCB-restore..."
392426
generate_restorelist
393-
"$SCRIPT_DIR/ghettoVCB-restore.sh" -c "$RESTORELIST"
394427

395-
# Normalize restored VMX files: single space around '='
428+
# Process each VM individually
396429
while IFS=";" read -r backup_path recovery_path diskfmt vmname; do
397-
# strip quotes if present
430+
# Strip quotes
431+
backup_path=$(echo "$backup_path" | sed 's/^"//; s/"$//')
398432
recovery_path=$(echo "$recovery_path" | sed 's/^"//; s/"$//')
399433
vmname=$(echo "$vmname" | sed 's/^"//; s/"$//')
400434

435+
echo "---------------------------------------------------------------------------------------------------------------"
436+
echo "Restoring VM '$vmname'..."
437+
echo "Backup path: $backup_path"
438+
echo "Recovery path: $recovery_path"
439+
440+
# Create a temporary restore task file
441+
echo "\"$backup_path\";\"$recovery_path\";\"$diskfmt\";\"$vmname\"" >"$SCRIPT_DIR/current_restore_task.txt"
442+
443+
# Run the restore
444+
"$SCRIPT_DIR/ghettoVCB-restore.sh" -c "$SCRIPT_DIR/current_restore_task.txt"
445+
446+
# Normalize VMX file
401447
vmx_file="$recovery_path/$vmname/$vmname.vmx"
402-
if [ -f "$vmx_file" ]; then
403-
echo "Normalizing VMX file: $vmx_file"
404-
sed -i 's/[[:space:]]*=[[:space:]]*/ = /g' "$vmx_file"
448+
[ -f "$vmx_file" ] && sed -i 's/[[:space:]]*=[[:space:]]*/ = /g' "$vmx_file"
449+
450+
# Delete decompressed folder if requested
451+
if [ "$DELETE_UNZIPPED" = "true" ]; then
452+
# The extracted folder is a subdirectory under the VM backup dir
453+
backup_dir=$(dirname "$backup_path") # /vmfs/.../Vmname
454+
# Find subdirectory starting with vmname- (the extracted folder)
455+
decompressed_dir=$(find "$backup_dir" -maxdepth 1 -type d -name "$vmname-*" | sort | tail -n1)
456+
457+
if [ -n "$decompressed_dir" ] && [ -d "$decompressed_dir" ]; then
458+
rm -rf "$decompressed_dir"
459+
echo "Deleted decompressed backup copy for VM '$vmname': $decompressed_dir"
460+
echo
461+
else
462+
echo "No decompressed backup found to delete for VM '$vmname'"
463+
echo
464+
fi
405465
fi
466+
467+
406468
done <"$RESTORELIST"
407-
ACTION="Restore"
408469

470+
ACTION="Restore"
409471
else
410472
echo "Running ghettoVCB backup with $BACKUPLIST..."
411473
"$SCRIPT_DIR/ghettoVCB.sh" -g "$VCB_CONF" -f "$BACKUPLIST"
412474
ACTION="Backup"
413475
fi
414476

415-
echo
416477
echo "$ACTION completed. VMs processed:"
417478
cat "$BACKUPLIST"
418-
echo
479+
echo

0 commit comments

Comments
 (0)