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:
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
4145SCRIPT_DIR=" $( cd " $( dirname " $0 " ) " && pwd) "
4246VCB_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 "
4650RESTORE_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
7254is_excluded () {
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
10078VM_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
11189ARG_MODE=" "
11290ARG_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+
114110cleanup_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
134130cleanup () {
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
158151echo
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+
162161while [ $# -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 " ] && {
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
203233cleanup_vmkfstools || true
204234
@@ -207,16 +237,16 @@ echo
207237echo " Excluded VMs:"
208238echo " $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
211241RECOVERY_DATASTORE_PATH=" ${RECOVERY_DATASTORE_PATH%/ } /"
212242
213243# Show backup and restore datastores
214244echo " Backup datastore (backup target from ghettoVCB.conf):"
215245echo " - $VM_BACKUP_VOLUME "
216246echo
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 " --------------------------------------------------------------------------------------------------------------- "
220250echo
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() {
379410if [ " $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
387421fi
388422
389423# Execute backup/restore
390424if [ " $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"
409471else
410472 echo " Running ghettoVCB backup with $BACKUPLIST ..."
411473 " $SCRIPT_DIR /ghettoVCB.sh" -g " $VCB_CONF " -f " $BACKUPLIST "
412474 ACTION=" Backup"
413475fi
414476
415- echo
416477echo " $ACTION completed. VMs processed:"
417478cat " $BACKUPLIST "
418- echo
479+ echo
0 commit comments