#!/bin/bash # # xenBackup - Backup Xen Domains # # Version: 1.0: Created: John D Quinn, http://www.johnandcailin.com/john # Version: 1.01: Modified: Adam Wilbraham 20080829 - Added basic functionality to use LVM snapshots # Version: 1.02: Modified: Adam Wilbraham 20080830 - Added NTFS mounting support # Version: 1.03: Modified: Adam Wilbraham 20080904 - Changed "all" behaviour so it backs up all "*-disk" LVs as opposed to only the running domUs # Version: 1.04: Modified: Adam Wilbraham 20080904 - Added support for backing up a block device to a compressed image (eg for Windows VMs) # initialize our variables domains="null" # the list of domains to backup allDomains="null" # backup all domains? targetLocation="/mnt/backups" # the default backup target directory mountPoint="/mnt/xen-backup" # the mount point to use to mount disk areas xenDiskArea="/dev/xen-cluster" # the LVM volume group to use shutdownDomains=false # don't shutdown domains by default quiet=false # keep the chatter down backupEngine=rsync # the default backup engine rsyncExe=/usr/bin/rsync # rsync executable rdiffbackupExe=/usr/bin/rdiff-backup # rdiff-backup executable tarExe=/bin/tar # tar executable ddExe=/bin/dd # dd executable bz2Exe=/bin/bzip2 # bzip2 executable xmExe=/usr/sbin/xm # xm executable lvcreateExe=/sbin/lvcreate # lvcreate executable lvremoveExe=/sbin/lvremove # lvremove executable lvdisplayExe=/sbin/lvdisplay # lvdisplay executable purgeAge="null" # age at which to purge increments globalBackupResult=0 # success status of overall job # settings for logging (syslog) loggerArgs="" # what extra arguments to the logger to use loggerTag="xen-backup" # the tag for our log statements loggerFacility="local3" # the syslog facility to log to # trap user exit and cleanup trap 'cleanup;exit 1' 1 2 cleanup() { ${logDebug} "Cleaning up" if [ ${backupEngine} != "dd" ] then cd / ; umount ${mountPoint} > /dev/null fi ${lvremoveExe} -f ${xenDisk} # restart the domain if test ${shutdownDomains} = "true" then ${logDebug} "Restarting domain" ${xmExe} create ${domain}.cfg > /dev/null fi } # function to print a usage message and bail usageAndBail() { cat << EOT Usage: xenBackup [OPTION]... Backup xen domains to a target area. different backup engines may be specified to produce a tarfile, an exact mirror of the disk area or a mirror with incremental backup. -d backup only the specified DOMAINs (comma seperated list) -t target LOCATION for the backup e.g. /tmp or root@www.example.com:/tmp (not used for tar engine) -a backup all domains -s shutdown domains before backup (and restart them afterwards) -q run in quiet mode, output still goes to syslog -e backup ENGINE to use, either tar, rsync, rdiff-backup or dd -p purge increments older than TIME_SPEC. this option only applies to rdiff-backup, e.g. 3W for 3 weeks. see "man rdiff-backup" for more information Example 1 Backup all domains to the /tmp directgory $ xenBackup -a -t /tmp Example 2 Backup domain: "wiki" using rsync to directory /var/xenImages on machine backupServer, $ xenBackup -e rsync -d wiki -t root@backupServer:/var/xenImages Example 3 Backup domains "domainOne" and "domainTwo" using rdiff-backup purging old increments older than 5 days $ xenBackup -e rdiff-backup -d "domainOne, domainTwo" -p 5D EOT exit 1; } # parse the command line arguments while getopts p:e:qsad:t:h o do case "$o" in q) quiet="true";; s) shutdownDomains="true";; a) allDomains="true";; d) domains="$OPTARG";; t) targetLocation="$OPTARG";; e) backupEngine="$OPTARG";; p) purgeAge="$OPTARG";; h) usageAndBail;; [?]) usageAndBail esac done # if quiet don't output logging to standard error if test ${quiet} = "false" then loggerArgs="-s" fi # setup logging subsystem. using syslog via logger logCritical="logger -t ${loggerTag} ${loggerArgs} -p ${loggerFacility}.crit" logWarning="logger -t ${loggerTag} ${loggerArgs} -p ${loggerFacility}.warning" logDebug="logger -t ${loggerTag} ${loggerArgs} -p ${loggerFacility}.debug" # make sure only root can run our script test $(id -u) = 0 || { ${logCritical} "This script must be run as root"; exit 1; } # make sure that the guest manager is available test -x ${xmExe} || { ${logCritical} "xen guest manager (${xmExe}) not found"; exit 1; } # assemble the list of domains to backup if test ${allDomains} = "true" then #domainList=`${xmExe} list | cut -f1 -d" " | egrep -v "Name|Domain-0"` domainList=`${lvdisplayExe} -c ${xenDiskArea} | cut -d':' -f1 | cut -d'/' -f4 | egrep -v "\-tempsnap|\-swap" | grep "\-disk" | sed {s/-disk//}` else # make sure we've got some domains specified if test "${domains}" = "null" then usageAndBail fi # create the domain list by mapping commas to spaces domainList=`echo ${domains} | tr -d " " | tr , " "` fi # function to do a "rdiff-backup" of domain backupDomainUsingrdiff-backup () { domain=$1 test -x ${rdiffbackupExe} || { ${logCritical} "rdiff-backup executable (${rdiffbackupExe}) not found"; exit 1; } if test ${quiet} = "false" then verbosity="3" else verbosity="0" fi targetSubDir=${targetLocation}/${domain}.rdiff-backup.mirror # make the targetSubDir if it doesn't already exist mkdir ${targetSubDir} > /dev/null 2>&1 ${logDebug} "backing up domain ${domain} to ${targetSubDir} using rdiff-backup" # rdiff-backup to the target directory ${rdiffbackupExe} --verbosity ${verbosity} ${mountPoint}/ ${targetSubDir} backupResult=$? # purge old increments if test ${purgeAge} != "null" then # purge old increments ${logDebug} "purging increments older than ${purgeAge} from ${targetSubDir}" ${rdiffbackupExe} --verbosity ${verbosity} --force --remove-older-than ${purgeAge} ${targetSubDir} fi return ${backupResult} } # function to do a "rsync" backup of domain backupDomainUsingrsync () { domain=$1 test -x ${rsyncExe} || { ${logCritical} "rsync executable (${rsyncExe}) not found"; exit 1; } targetSubDir=${targetLocation}/${domain}.rsync.mirror # make the targetSubDir if it doesn't already exist mkdir ${targetSubDir} > /dev/null 2>&1 ${logDebug} "backing up domain ${domain} to ${targetSubDir} using rsync" # rsync to the target directory #${rsyncExe} -essh -avz --delete ${mountPoint}/ ${targetSubDir} ${rsyncExe} -essh -avz --quiet --stats --delete ${mountPoint}/ ${targetSubDir} backupResult=$? return ${backupResult} } # function to a "tar" backup of domain backupDomainUsingtar () { domain=$1 # make sure we can write to the target directory test -w ${targetLocation} || { ${logCritical} "target directory (${targetLocation}) is not writeable"; exit 1; } targetFile=${targetLocation}/${domain}.`date '+%d%b%y'`.$$.tar.gz ${logDebug} "backing up domain ${domain} to ${targetFile} using tar" # tar to the target directory cd ${mountPoint} ${tarExe} pcfz ${targetFile} * > /dev/null backupResult=$? return ${backupResult} } # function create a compressed block device backup of domain backupDomainUsingdd () { domain=$1 # make sure we can write to the target directory test -w ${targetLocation} || { ${logCritical} "target directory (${targetLocation}) is not writeable"; exit 1; } targetFile=${targetLocation}/${domain}.`date '+%d%b%y'`.$$.img.bz2 ${logDebug} "backing up domain ${domain} to ${targetFile} using dd and compressing with bzip2" # for a block device backup we don't need it mounted #umount ${mountPoint} ${ddExe} if=${xenDiskArea}/${domain}-disk-tempsnap bs=2048 | ${bz2Exe} > ${targetFile} backupResult=$? return ${backupResult} } # backup the specified domains for domain in ${domainList} do date=`/bin/date` ${logDebug} "${date}" ${logDebug} "backing up domain: ${domain}" # make sure that the domain is shutdown if required if test ${shutdownDomains} = "true" then ${logDebug} "shutting down domain ${domain}" ${xmExe} shutdown -w ${domain} > /dev/null fi # unmount mount point if already mounted umount ${mountPoint} > /dev/null 2>&1 # create LVM snapshot of the domain we want to backup and mount it read-only ${lvcreateExe} -s -L 5G -n ${domain}-disk-tempsnap ${xenDiskArea}/${domain}-disk xenDisk=${xenDiskArea}/${domain}-disk-tempsnap test -r ${xenDisk} || { ${logCritical} "xen disk area not readable. are you sure that the domain \"${domain}\" exists?"; exit 1; } if [ ${backupEngine} != "dd" ] then ${logDebug} "Mounting ${xenDisk} read-only" mount -r ${xenDisk} ${mountPoint} || { ${logCritical} "mount failed, attempting to mount NTFS"; mount -o offset=32256 -r ${xenDisk} ${mountPoint} || { ${logCritical} "mount failed, does mount point (${mountPoint}) exist?"; exit 1; } ; } fi # do the backup according to the chosen backup engine backupDomainUsing${backupEngine} ${domain} # make sure that the backup was successful if test $? -ne 0 then ${logCritical} "FAILURE: error backing up domain ${domain}" globalBackupResult=1 else ${logDebug} "SUCCESS: domain ${domain} backed up" fi # clean up cleanup; done if test ${globalBackupResult} -eq 0 then ${logDebug} "SUCCESS: backup of all domains completed successfully" else ${logCritical} "FAILURE: backup completed with some failures" fi exit ${globalBackupResult}