Backup - Individual Mailbox
The 2nd method involves backing up individual mailboxes. This process can occur while the server is online and thus will not cause any downtime. It can be run as often as you want but you should consider the performance impact on the server as well as how long each run will take. I plan to schedule it once per day during non-peak hours.
mailbox-backup.sh
Code:
#!/bin/bash
#############################################
## Name : mailbox-backup.sh
## Version : 1.1
## Date : 2012-01-09
## Author : LHammonds
## Purpose : Backup individual mailbox accounts.
## Compatibility : Verified on Ubuntu Server 10.04.3 - 10.04.4 LTS, Zimbra 7.1.2 - 7.2.0 OSE
## Requirements : Zimbra must be online, must be run as root user.
## Run Frequency : Once or more per day.
## Exit Codes : (if multiple errors, value is the addition of codes)
## 0 = Success (or non-critical failure)
## 1 = Root access failure
## 2 = Archive creation failure
## 4 = Archive purge error
## 8 = configuration error
## 16 = Offsite mount failure
## 32 = Offsite copy failure
################ CHANGE LOG #################
## DATE WHO WHAT WAS CHANGED
## ---------- --- ----------------------------
## 2011-10-23 LTH Created script.
## 2011-11-05 LTH Move common variables and functions to external file.
## 2011-11-06 LTH Additional error checks and better log info.
## 2012-01-09 LTH Bugfix f_PurgeOldestArchive
#############################################
## Import common variables and functions.
source /var/scripts/common/standard.conf
## Define local variables.
LOGFILE="${TEMPDIR}/mailbox-backup.log"
HISTORYFILE="${TEMPDIR}/mailbox-backup-size-history.log"
TARGETDIR="${BACKUPDIR}/mailbox"
OFFSITEBACKDIR="${OFFSITEDIR}/mailbox"
ARCHIVEFILE="`date +%Y-%m-%d-%H-%M`_mailbox-all.tar"
LOCKFILE="${TEMPDIR}/mailbox-backup.lock"
EXCEPTIONS="spam.ppnvqogp0@${MYDOMAIN};ham.iki6sotcy@${MYDOMAIN};virus-quarantine.qvj6nc_jl@${MYDOMAIN}"
RETURNVALUE=0
UCOUNT=0
ERRORFLAG=0
#######################################
## FUNCTIONS ##
#######################################
function f_PurgeOldestArchive()
{
## Purpose: Delete the oldest archive on the remote site.
## Return values:
## 0 = Success
## 1 = Cannot delete file
## 9 = Configuration error, path empty
## Variable Error Check. *
if [ ${OFFSITEBACKDIR} = "" ]; then
## Make darn sure the path is not empty since we do NOT
## want to start purging files from a random location.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Purge error: OFFSITEBACKDIR site variable is empty!" >> ${LOGFILE}
return 9
fi
## Get the name of the oldest file.
OLDESTFILE=`ls -1t ${OFFSITEBACKDIR} | tail -1`
FILESIZE=`ls -la ${OFFSITEDIR}/${OLDFILE} | awk '{print $5}' | sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta'`
if [ "${OLDESTFILE}" = "" ]; then
## Error. Filename variable empty.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Purge error: OLDESTFILE variable is empty." >> ${LOGFILE}
return 9
else
FILESIZE=`ls -lak "${OFFSITEBACKDIR}/${OLDESTFILE}" | awk '{ print $5 }' | sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta'`
echo "`date +%Y-%m-%d_%H:%M:%S` --- Purging old file: ${OFFSITEBACKDIR}/${OLDESTFILE}, Size = ${FILESIZE} kb" >> ${LOGFILE}
rm "${OFFSITEBACKDIR}/${OLDESTFILE}"
if [ -f "${OFFSITEBACKDIR}/${OLDESTFILE}" ]; then
## File still exists. Return error.
return 1
else
return 0
fi
fi
}
function f_cleanup()
{
if [ -f "${LOCKFILE}" ]; then
## Remove run file since this script is complete.
rm ${LOCKFILE}
fi
}
function f_emergencyexit()
{
## Purpose: Exit script as cleanly as possible.
## Parameter #1 = Error Code.
f_cleanup
f_sendmail "Zimbra Mailbox Backup Error" "EXIT CODE = ${1}"
echo "`date +%Y-%m-%d_%H:%M:%S` - Mailbox backup aborted. EXIT CODE: ${1}" >> ${LOGFILE}
## Write script name and error code to the system log.
logger "${SCRIPTNAME}: ERROR CODE = ${1}"
exit $1
}
#######################################
## MAIN PROGRAM ##
#######################################
## Requirement Check: Script must run as root user.
if [ "$(id -u)" != "0" ]; then
## FATAL ERROR DETECTED: Document problem and terminate script.
ERRORFLAG=$((${ERRORFLAG} + 1))
echo "`date +%Y-%m-%d_%H:%M:%S` - Mailbox backup aborted. ERROR CODE ${ERRORFLAG}: Script must be run as root user instead of $(whoami)." | tee -a ${LOGFILE}
f_emergencyexit ${ERRORFLAG}
fi
if [ -f "${LOCKFILE}" ]; then
## Last call to this script is still running or failed to remove its lock file.
## As an additional check, see if the file is older than today...if so, we should
## probably send an email notification of a problem that may need manual interention.
FILEDATE=$(stat -c %y ${LOCKFILE})
FILEDATE=${FILEDATE%% *}
if [ "${FILEDATE}" != "$(date +%Y-%m-%d)" ]; then
## Lock file not created today, might need to be manually deleted. Send email notification.
f_sendmail "Zimbra Mailbox Backup Warning" "Warning: This script cannot run if it detects this lock file: ${LOCKFILE}\n\nThis file should only exist while this script is running which should not take more than a day.\n\nSystem Date: $(date +%Y-%m-%d)\nLock File Date: ${FILEDATE}\n\nIf you determine that the file should be removed, do so by typing this command on the server's console: rm ${LOCKFILE}"
fi
exit 0
else
## Create the "script is running" lock file and process the script.
echo "`date +%Y-%m-%d_%H:%M:%S` - ${HOSTNAME}:${SCRIPTNAME} is currently running." > ${LOCKFILE}
fi
## Record the start time of the backup process.
STARTTIME="$(date +%s)"
echo "`date +%Y-%m-%d_%H:%M:%S` - Individual mailbox backup started." >> ${LOGFILE}
if [ -d "${TARGETDIR}" ]; then
## Purge existing archives.
rm ${TARGETDIR}/*.tgz 1>/dev/null 2>&1
else
## Make the folder since it does not exist.
mkdir -p ${TARGETDIR} 1>/dev/null 2>&1
fi
for ACCT in `su - zimbra -c "zmprov -l gaa"`
do
## Check to see if current account should be skipped.
if echo "${EXCEPTIONS}" | grep -q ${ACCT}
then
## Exception found, skip this account.
echo "" > /dev/null
else
## Backup user account.
UCOUNT=$((UCOUNT+1))
${ZIMBRADIR}/bin/zmmailbox -z -m ${ACCT} getRestURL "//?fmt=tgz" > ${TARGETDIR}/${ACCT}.tgz
RETURNVALUE=$?
if [ ${RETURNVALUE} -ne 0 ]; then
## Something went wrong.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Error on ${ACCT}, RETURN VALUE = ${RETURNVALUE}" >> ${LOGFILE}
ERRORFLAG=$((ERRORFLAG+1))
else
## Calculate archive size.
MAILBOXSIZE=$(stat -c %s ${TARGETDIR}/${ACCT}.tgz)
## Comment out the below line if you do not want details in the log file.
echo "`date +%Y-%m-%d_%H:%M:%S` --- ${ACCT}, ${MAILBOXSIZE} bytes" >> ${LOGFILE}
fi
fi
done
echo "`date +%Y-%m-%d_%H:%M:%S` --- ${UCOUNT} accounts processed." >> ${LOGFILE}
## Calculate mailbox backup time.
FINISHTIME="$(date +%s)"
ELAPSEDTIME="$(expr ${FINISHTIME} - ${STARTTIME})"
HOURS=$((${ELAPSEDTIME} / 3600))
ELAPSEDTIME=$((${ELAPSEDTIME} - ${HOURS} * 3600))
MINUTES=$((${ELAPSEDTIME} / 60))
SECONDS=$((${ELAPSEDTIME} - ${MINUTES} * 60))
echo "`date +%Y-%m-%d_%H:%M:%S` --- Backup time for ${UCOUNT} mailboxes: ${HOURS} hour(s) ${MINUTES} minute(s) ${SECONDS} second(s)" >> ${LOGFILE}
echo "`date +%Y-%m-%d_%H:%M:%S` --- Setting file permissions on ${TARGETDIR}/*.tgz" >> ${LOGFILE}
chmod 0600 ${TARGETDIR}/*.tgz
echo "`date +%Y-%m-%d_%H:%M:%S` --- Creating a single file for archiving: ${TEMPDIR}/${ARCHIVEFILE}" >> ${LOGFILE}
tar -cf ${TEMPDIR}/${ARCHIVEFILE} ${TARGETDIR} 1>/dev/null 2>&1
RETURNVALUE=$?
if [ ${RETURNVALUE} -ne 0 ]; then
## Something went wrong.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Error creating ${TEMPDIR}/${ARCHIVEFILE}, Return Value: ${RETURNVALUE}" >> ${LOGFILE}
ERRORFLAG=$((ERRORFLAG+2))
fi
ARCHIVESIZE=$(stat -c %s ${TEMPDIR}/${ARCHIVEFILE})
if [ ! -f "${HISTORYFILE}" ]; then
## Create history log and include header description as 1st line, field separator = space character.
echo "Date Archive SizeInBytes" > ${HISTORYFILE}
fi
## Append archive size to the history log.
echo "`date +%Y-%m-%d` ${ARCHIVEFILE} ${ARCHIVESIZE}" >> ${HISTORYFILE}
## Mount the offsite Windows share folder.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Mounting ${OFFSITEDIR}" >> ${LOGFILE}
f_mount
if [ -f ${OFFSITETESTFILE} ]; then
## Remote site is online / available.
if [ ! -d ${OFFSITEBACKDIR} ]; then
## Make the backup folder since it does not exist yet.
mkdir -p ${OFFSITEBACKDIR}
fi
if [ -f ${TEMPDIR}/${ARCHIVEFILE} ]; then
## Make sure space is available on the remote server to copy the file.
FREESPACE=`df -k ${OFFSITEDIR} | grep ${OFFSITEDIR} | awk '{ print $3 }'`
BACKUPSIZE=`ls -lak "${TEMPDIR}/${ARCHIVEFILE}" | awk '{ print $5 }'`
if [ ${FREESPACE} -lt ${BACKUPSIZE} ]; then
## Not enough free space available. Purge existing backups until there is room.
ENOUGHSPACE=0
while [ ${ENOUGHSPACE} -eq 0 ]
do
f_PurgeOldestArchive
RETURNVALUE=$?
case ${RETURNVALUE} in
1)
## Cannot purge archives to free up space. End program gracefully.
echo "`date +%Y-%m-%d_%H:%M:%S` - ERROR: Not enough free space on ${OFFSITEBACKDIR} and cannot purge old archives. Script aborted." >> ${TEMPLOG}
## Stop and exit the script with an error code.
ERRORFLAG=$((${ERRORFLAG} + 4))
f_emergencyexit ${ERRORFLAG}
;;
9)
## Configuration error, end program gracefully.
echo "`date +%Y-%m-%d_%H:%M:%S` - ERROR: Configuration problem. Script aborted." >> ${TEMPLOG}
## Stop and exit the script with an error code.
ERRORFLAG=$((${ERRORFLAG} + 8))
f_emergencyexit ${ERRORFLAG}
;;
esac
FREESPACE=`df -k ${OFFSITEDIR} | grep ${OFFSITEDIR} | awk '{ print $3 }'`
if [ ${FREESPACE} -gt ${BACKUPSIZE} ]; then
## Enough space is now available.
ENOUGHSPACE=1
else
## Not enough space is available yet.
ENOUGHSPACE=0
fi
done
fi
echo "`date +%Y-%m-%d_%H:%M:%S` --- Copying ${TEMPDIR}/${ARCHIVEFILE} to ${OFFSITEBACKDIR}/" >> ${LOGFILE}
cp ${TEMPDIR}/${ARCHIVEFILE} ${OFFSITEBACKDIR}/${ARCHIVEFILE} 1>/dev/null 2>&1
fi
else
## Remote site is offline / unavailable.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Error: Remote site is unavailable: ${OFFSITEBACKDIR}" >> ${LOGFILE}
ERRORFLAG=$((ERRORFLAG+16))
fi
if [ -f ${OFFSITEBACKDIR}/${ARCHIVEFILE} ]; then
## Remote copy worked. Remove local archive.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Copied ${ARCHIVESIZE} bytes." >> ${LOGFILE}
echo "`date +%Y-%m-%d_%H:%M:%S` --- Removing ${TEMPDIR}/${ARCHIVEFILE}" >> ${LOGFILE}
rm ${TEMPDIR}/${ARCHIVEFILE}
FREESPACE=`df -k ${OFFSITEDIR} | grep ${OFFSITEDIR} | awk '{ print $3 }'`
## Comment out the below line if you do not want to receive statistic emails.
f_sendmail "Zimbra Individual Mailbox Backup" "${UCOUNT} accounts backed up.\n\nTotal archive size: $((${ARCHIVESIZE}/1024)) kb\n\nAvailable Backup Space: ${FREESPACE} kb"
## Uncomment the following 2 lines if you do not wish to have a local copy of individual mailboxes.
# rm ${TARGETDIR}/*.tgz
# rmdir ${TARGETDIR}
else
## Remote copy failed.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Error copying to ${OFFSITEBACKDIR}/${ARCHIVEFILE}" >> ${LOGFILE}
ERRORFLAG=$((ERRORFLAG+32))
fi
## Calculate total time for backup.
FINISHTIME="$(date +%s)"
ELAPSEDTIME="$(expr ${FINISHTIME} - ${STARTTIME})"
HOURS=$((${ELAPSEDTIME} / 3600))
ELAPSEDTIME=$((${ELAPSEDTIME} - ${HOURS} * 3600))
MINUTES=$((${ELAPSEDTIME} / 60))
SECONDS=$((${ELAPSEDTIME} - ${MINUTES} * 60))
echo "`date +%Y-%m-%d_%H:%M:%S` --- Total backup time: ${HOURS} hour(s) ${MINUTES} minute(s) ${SECONDS} second(s)" >> ${LOGFILE}
if [ ${ERRORFLAG} -ne 0 ]; then
f_sendmail "Zimbra Individual Mailbox Backup Error" "${ERRORFLAG} errors detected while trying to backup ${UCOUNT} individual mailboxes."
fi
## Unmount the offsite backup location.
echo "`date +%Y-%m-%d_%H:%M:%S` --- Dismounting ${OFFSITEDIR}" >> ${LOGFILE}
f_umount
## Perform cleanup routine.
f_cleanup
## Exit with the combined return code value.
echo "`date +%Y-%m-%d_%H:%M:%S` - Individual mailbox backup complete. Exit code: ${ERRORFLAG}" >> ${LOGFILE}
exit ${ERRORFLAG}
Sample output of mailbox-backup.log
Code:
2011-11-06_15:48:02 - Individual mailbox backup started.
2011-11-06_15:48:11 --- admin@mydomain.com, 6781 bytes
2011-11-06_15:48:13 --- lhammonds@mydomain.com, 1066 bytes
2011-11-06_15:48:16 --- jsmith@mydomain.com, 1067 bytes
2011-11-06_15:48:18 --- jdoe@mydomain.com, 1066 bytes
2011-11-06_15:48:21 --- administrator@mydomain.com, 1069 bytes
2011-11-06_15:48:23 --- foobar@mydomain.com, 1067 bytes
2011-11-06_15:48:25 --- jvorhees@mydomain.com, 1068 bytes
2011-11-06_15:48:28 --- conferencerooms@mydomain.com, 1067 bytes
2011-11-06_15:48:30 --- test@mydomain.com, 1068 bytes
2011-11-06_15:48:48 --- 9 accounts processed.
2011-11-06_15:48:48 --- Backup time for 9 mailboxes: 0 hour(s) 0 minute(s) 46 second(s)
2011-11-06_15:48:48 --- Setting file permissions on /var/backup/mailbox/*.tgz
2011-11-06_15:48:48 --- Creating a single file for archiving: /var/temp/2011-11-06-15-48_mailbox-all.tar
2011-11-06_15:48:48 --- Mounting /mnt/backup
2011-11-06_15:48:49 --- Copying /var/temp/2011-11-06-15-48_mailbox-all.tar to /mnt/backup/mailbox/
2011-11-06_15:48:49 --- Copied 61440 bytes.
2011-11-06_15:48:49 --- Removing /var/temp/2011-11-06-15-48_mailbox-all.tar
2011-11-06_15:48:49 --- Total backup time: 0 hour(s) 0 minute(s) 47 second(s)
2011-11-06_15:48:49 --- Dismounting /mnt/backup
2011-11-06_15:48:49 - Individual mailbox backup complete. Exit code: 0
Sample output of mailbox-backup-size-history.log
Code:
Date Archive SizeInBytes
2011-11-06 2011-11-06-11-21_mailbox-all.tar 61440
2011-11-06 2011-11-06-15-17_mailbox-all.tar 61440
2011-11-06 2011-11-06-15-21_mailbox-all.tar 61440
2011-11-06 2011-11-06-15-22_mailbox-all.tar 61440
2011-11-06 2011-11-06-15-32_mailbox-all.tar 61440
2011-11-06 2011-11-06-15-48_mailbox-all.tar 61440
Sample email notification
Code:
Subject: Zimbra Individual Mailbox Backup
9 accounts backed up.
Total archive size: 60 kb
Available Backup Space: 28440616 kb
Server: mail
Program: /var/scripts/prod/mailbox-backup.sh
Log: /var/temp/mailbox-backup.log
Sample error email notification
Code:
Subject: Zimbra Individual Mailbox Backup Error
1 errors detected while trying to backup 9 individual mailboxes.
Server: mail
Program: /var/scripts/prod/mailbox-backup.sh
Log: /var/temp/mailbox-backup.log
Sample /var/log/messages
Code:
Nov 6 11:13:01 mail administrator: /var/scripts/prod/mailbox-backup.sh: ERROR CODE = 1
Nov 6 11:15:02 mail root: /var/scripts/prod/mailbox-backup.sh: ERROR CODE = 2
Nov 6 11:18:03 mail root: /var/scripts/prod/mailbox-backup.sh: ERROR CODE = 4
Nov 6 11:23:04 mail root: /var/scripts/prod/mailbox-backup.sh: ERROR CODE = 8
Nov 6 11:33:05 mail root: /var/scripts/prod/mailbox-backup.sh: ERROR CODE = 16
Nov 6 11:43:06 mail root: /var/scripts/prod/mailbox-backup.sh: ERROR CODE = 32