Firstly, I've only been playing with Zimbra since Friday last, so please be gentle... 
I read with interest this per user backup thread and all credit to the author - good stuff!
But backups are no good without a matching restore, so I've set to and created one based upon reversing the actions the backup script takes (and un-doing a few things that don't exactly reverse!). Now, caveat emptor is the rule here because I've only undertaken limited testing against a single account on a test server. So far so good, but I'd really appreciate bigger brains that mine taking a look at the script below and telling me what I've missed.
There are a couple of gotchas which I've tried to comment but I'm sure there are others.
Hope this is of some interest.
Keith.
Code:
#!/bin/bash
#===========================================================================================
# Zimbra Restore User - based on reversing the actions of zimbra_backup_user.sh
#
# Basic Testing ONLY undertaken.
#
# ************************
# * USE AT YOUR OWN RISK *
# ************************
#
#===========================================================================================
###
# Requirements : bash, whoami, su, echo, tee, cut, grep, mkdir, chown, rsync, du, zimbra 5.0.x
#
###
# Are we root?
if [ `whoami` != "root" ]; then
echo "ERROR! ${0} must be run by the root user"
exit 1
fi
# Output help, needs work.
function display_usage {
echo "${0} [emailaddress] [backupDirToRestore]"
echo ""
echo " [emailaddress] : A valid email account on your Zimbra server."
echo " [backupDirToRestore] : A valid directory from which to restore"
exit 1
}
#===========================================================================================
# New functions added to facilitate restore.
#
# Included here to avoid changes to zimbra_functions.sh - even
# though that is the logical place for them.
#===========================================================================================
# rsync from cache
# $1 [string] : file/directory path
function zimbra_restore_rsync {
if [ ! -e ${1} ]; then
log "WARNING! I can't rsync ${1} because it does not exist."
else
local DIRS=`ls -1 ${zimbra_cache}/${current_account}${1}`
for DIR in ${DIRS}
do
# Skip the incoming directory
if [ ${DIR} != "incoming" ]; then
log "Syncing ${1}/${DIR}/${mailbox_id} from ${zimbra_cache}/${current_account}${1}/${DIR}"
rsync -aHK ${zimbra_cache}/${current_account}${1}/${DIR} ${1}
log "Size : `du -s -h ${zimbra_cache}/${current_account}${1}/${DIR} | cut -f1`"
fi
done
fi
}
# Executes mysql restore
# $1 [string] : mysql paramters
function zimbra_sql {
log "Restoring ${1}"
zimbra_run "${mysql_directory}/bin/mysql -f -S ${mysql_socket} -u ${zimbra_mysql_user} --password=${zimbra_mysql_password} ${1}"
}
#===========================================================================================
# Have we got enough parameters
if [ $# -ne 2 ]; then
display_usage
else
current_account="${1}"
current_backup="${2}"
fi
# Setup the zimbra variables and functions
source ./zimbra_functions.sh || exit 1
if [ ! -e ${current_backup} ]; then
display_usage
else
zimbra_chown ${current_backup}
fi
# Line breaking please.
IFS=$'\n'
log "Processing ${current_account}"
# Get the zimbraId
current_account_zimbraId=`zimbra_run "${zimbra_home}/bin/zmprov ga ${current_account} zimbraId | grep zimbraId | cut -d' ' -f2"`
restoreZimbraID=`grep zimbraId: ${current_backup}/ga.txt | cut -d' ' -f2`
# Don't try to restore a deleted (or otherwise damaged) account
if [ "${current_account_zimbraId}" != "${restoreZimbraID}" ]; then
echo "***************************************************************"
echo "* There seems to be a problem with account ${current_account} *"
echo "* Either the account doesn't exist or the backup and *"
echo "* current zimbraIds don't match *"
echo "* *"
echo "* Restore operation cancelled. *"
echo "***************************************************************"
exit 1
fi
# Ensure cache exists and cleared for current account. Savage, but it works for me.
rm -rf ${zimbra_cache}/${current_account}
mkdir -p ${zimbra_cache}/${current_account}
# Get the quotaUsed - Could be used to (optionally?) skip accounts using zero quota in the future
current_account_quotaUsed=`zimbra_run "${zimbra_home}/bin/zmprov gmi ${current_account} | grep quotaUsed | cut -d' ' -f2"`
# Get the mailboxId
mailbox_id=`zimbra_run "${zimbra_home}/bin/zmprov gmi ${current_account} | grep mailboxId | cut -d' ' -f2"`
# Get the group and volume ids for the current account.
log "Getting group_id and index_volume_id"
zimbra_create_sql "SELECT group_id, index_volume_id FROM zimbra.mailbox WHERE account_id='"${current_account_zimbraId}"'"
zimbra_execute_sql
mailbox_group_id=`echo ${zimbra_sql_result} | cut -f1`
mailbox_index_volume_id=`echo ${zimbra_sql_result} | cut -f2`
mailbox="mboxgroup${mailbox_group_id}"
log "zimbraId : ${current_account_zimbraId}"
log "Id : ${mailbox_id}"
log "Group : ${mailbox_group_id}"
log "Mailbox : ${mailbox}"
log "Quota Used : ${current_account_quotaUsed}"
# Get the volume details for the current account.
log "Getting message_volume_id and index_volume_id"
zimbra_create_sql "SELECT message_volume_id, index_volume_id FROM zimbra.current_volumes WHERE index_volume_id='"${mailbox_index_volume_id}"'"
zimbra_execute_sql
message_volume_id=`echo ${zimbra_sql_result} | cut -f1`
index_volume_id=`echo ${zimbra_sql_result} | cut -f2`
# Get the index volume name and path
log "Getting index volume path"
zimbra_create_sql "SELECT name, path FROM zimbra.volume WHERE id='"${index_volume_id}"'"
zimbra_execute_sql
index_volume_name=`echo ${zimbra_sql_result} | cut -f1`
index_volume_path=`echo ${zimbra_sql_result} | cut -f2`
# Get the message volume name and path
log "Getting message volume path"
zimbra_create_sql "SELECT name, path FROM zimbra.volume WHERE id='"${message_volume_id}"'"
zimbra_execute_sql
message_volume_name=`echo ${zimbra_sql_result} | cut -f1`
message_volume_path=`echo ${zimbra_sql_result} | cut -f2`
log "${index_volume_name} path : ${index_volume_path}"
log "${message_volume_name} path : ${message_volume_path}"
#===========================================================================================
# No hot restore - not sure what to do with ga.txt and gsig.txt...
#===========================================================================================
# Set the account to maintenance mode (Read-only, new mail will be queued at the MTA)
zimbra_set_account_status maintenance
### Warm restore of the current account database, current account rows, index and store directories.
#
log "Restoring index_n_store tarball to ${zimbra_cache}/${current_account}"
tar zxvf ${current_backup}/index_n_store.tar.gz ${zimbra_cache}/${current_account}/ 2&> /dev/null
zimbra_restore_rsync ${index_volume_path}
zimbra_restore_rsync ${message_volume_path}
#===========================================================================================
# Note that the way the mboxgroupN is dumped by zimbra_backup_user.sh means restoring it
# will cause anything held in mboxgroupN that was created after the backup was taken to
# be lost. This could maybe do with some work to merge rather than replace the entries?
#===========================================================================================
zimbra_sql "${mailbox} < ${current_backup}/${mailbox}.sql"
zimbra_sql "zimbra < ${current_backup}/mailbox.sql"
zimbra_sql "zimbra < ${current_backup}/mailbox_metadata.sql"
zimbra_sql "zimbra < ${current_backup}/out_of_office.sql"
zimbra_sql "zimbra < ${current_backup}/scheduled_task.sql"
zimbra_sql "zimbra < ${current_backup}/table_maintenance.sql"
# Tidy up
rm -rf ${zimbra_cache}/${current_account}
#
### Warm restore ends
# Set the account to back to active mode
zimbra_set_account_status active
# Purely cosmetic
echo ""