From what I understand is that backing up a running VM is not recommended, unless you do it inside the guest. Somehow the few minutes of downtime bother me a little bit, however in practical life that should probably not be an issue at all. So I have created the following small script to backup my various VMs individually. The script does the following:
- Define the variables at the beginning
- Check the state of the VM and shut it down if it is running
- Check in a loop if the VM is powered off (wait max. 60 seconds or then abort)
- Copy the directory to the specified location
- Start the VM again (only if it was running before)
- Send out an email with the results
The script also creates a logfile with date/time stamps. I run this script with cron once per day during the night, and have one script for each VM that I want to backup. A typical VM is backed up in less than 5 minutes in my case. Can that be further reduced?
My question though is whether this is a valid and "safe" way to keep a backup of my VMs. In case of a failure of the system for example, can I simply take the backed up directory and fire the same VM up on a different machine? Would the new machine need to be the same OS and/or Virtualbox version? Or could it be a different Virtualbox version, with different hardware?
Also, are there any crucial steps that I missed, or is this a reasonable way to backup my VM?
Code: Select all
#!/bin/bash
# =============== Set your variables here ===============
MYMAIL=email@inter.net
VMDIR="/home/user/VMs/Proxy/"
EXPORTDIR="/mnt/backup/vmbackup/"
LOGDIR="/home/user/logs/"
LOGNAME="proxy.log"
VM="Proxy"
ERR="nothing"
SECONDS=0
STARTUP=1
LOGFILE="$LOGDIR$(date +"%Y-%m-%d-%T")-$LOGNAME"
cdt=$(date)
echo "${cdt}: Backing up VM ${VM}" >> $LOGFILE
# Get the vm state
VMSTATE=$(vboxmanage showvminfo "$VM" --machinereadable | grep "VMState=" | cut -f 2 -d "=")
cdt=$(date)
echo "${cdt}: ${VM}'s state is: ${VMSTATE}." &>> $LOGFILE
# If the VM's state is running or paused, save its state
if [[ $VMSTATE == \"poweroff\" ]]; then
# skip the wait
cdt=$(date)
echo "${cdt}: VM ${VM} is already powered off" &>> $LOGFILE
STARTUP=0
else
# Shut down the VM
cdt=$(date)
echo "${cdt}: Shutting down the VM ${VM}" &>> $LOGFILE
vboxmanage controlvm "$VM" acpipowerbutton
if [ $? -ne 0 ]; then ERR="shutting down"; fi
# cdt=$(date)
# echo "${cdt}: Waiting 60 seconds to let the VM ${VM} shut down" &>> $LOGFILE
# sleep 60s
fi
max_wait=$((SECONDS+60))
cnt=1
# Check state of VM
VMSTATE=$(vboxmanage showvminfo "$VM" --machinereadable | grep "VMState=" | cut -f 2 -d "=")
# Wait a maximum of 60 seconds to avoid an infinite loop in case the VM can't be stopped
while [[ $VMSTATE != \"poweroff\" ]]
do
cdt=$(date)
echo "${cdt}: ${cnt} loop(s) waiting for the VM ${VM} to shut down. Current state is ${VMSTATE}" &>> $LOGFILE
# Wait a few seconds before checking again
sleep 5s
# Checking state of VM again
VMSTATE=$(vboxmanage showvminfo "$VM" --machinereadable | grep "VMState=" | cut -f 2 -d "=")
cnt=$(( $cnt + 1 ))
# If loop has taken more than 60 seconds, break it to avoid an infinite loop
if [ $SECONDS -gt $max_wait ]; then break; fi
done
# Check if VM is now powered off
# VMSTATE=$(vboxmanage showvminfo "$VM" --machinereadable | grep "VMState=" | cut -f 2 -d "=")
cdt=$(date)
echo "${cdt}: ${VM}'s state is: ${VMSTATE}." &>> $LOGFILE
if [[ $VMSTATE == \"poweroff\" ]]; then
eval cp -R "${VMDIR}" $EXPORTDIR
cdt=$(date)
echo "${cdt}: VM ${VM} has been copied from ${VMDIR} to ${EXPORTDIR}" &>> $LOGFILE
else
ERR="not powered off"
cdt=$(date)
echo "${cdt}: Not exporting because the VM ${VM} is not powered off." &>> $LOGFILE
fi
if [[ $STARTUP == 1 ]]; then
vboxmanage startvm "$VM" --type headless &>> $LOGFILE
if [ $? -ne 0 ]; then
ERR="starting up"
else
cdt=$(date)
echo "${cdt}: VM is started up again" >> $LOGFILE
fi
fi
# Calculate duration
duration=$SECONDS
duration="Operation took $(($duration / 60)) minutes, $(($duration % 60)) seconds." &>> $LOGFILE
# Notify the admin
if [ "$ERR" == "nothing" ]; then
MAILSUBJECT="VM ${VM} succesfully backed up"
MAILBODY="Virtual Machine ${VM} was succesfully backed up!"
MAILBODY="$MAILBODY"$'\n'"$duration"
# MAILBODY=$(echo $MAILBODY && cat $LOGFILE)
else
MAILSUBJECT="Error $ERR VM ${VM}"
MAILBODY="There was an error ${ERR} VM ${VM}."
MAILBODY=$(echo $MAILBODY && cat $LOGFILE)
fi
# Send the mail
echo "$MAILBODY" | mail -s "$MAILSUBJECT" $MYMAIL