I thought better to start with Off-line backup script. Here's one. I have tested this on my system running CentOS 4.3 and ZCS 4.0.0 GA. I also restored the entire /opt/zimbra directory from the backup and am now running on it now ...
I have around 20 users on the system and approx. 3,000 emails in all. All contacts, calendars, wiki, and everything is intact and working well.
Attached the script with README: zimbraColdBackup-Ver0.01beta-Rev15.tgz
Code:
#!/usr/bin/perl -w
use strict;
use POSIX;
use Proc::ProcessTable;
use File::Path;
use File::Rsync;
use Mail::Mailer;
my $version = "0.01beta"; # version of this program
my $revision = "15"; # revision of this program
# License
my $license = qq(
###########################################################################
# Program Name: zimbraColdBackup Ver $version Rev $revision #
# #
# This script can be used to backup Zimbra Collaboration Suite 4.0.0 GA #
# #
# Copyright (C) 2006 Chintan Zaveri #
# E-mail: smile\@sis.net.in #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License version 2, as #
# published by the Free Software Foundation #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., #
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #
###########################################################################
);
###########################################################################
# Make changes BELOW this line to suit your environment / requirement #
###########################################################################
# Change the following to the path to your backup directory. This directory must exist on your system.
my $backup_directory = '/backup';
# Change the following to the path to "rsync"
my $path_to_rsync = '/usr/bin/rsync';
# Do you want to rotate backups?
my $rotate_backups = 1; # "1" for yes, and "0" for no
# If you want to rotate backups, please specify the number of days after which it should be rotated
my $rotation_days = 7;
# You must not need to change the following, but just in case
# Specify the path to zimbra
my $zimbra_path = '/opt/zimbra';
# zimbra user
my $zimbra_user = 'zimbra';
# Do you want this script to send an e-mail after backup is done?
my $to_email = 0; # 1 for yes, 0 for no
# If you specified 1 above, please provide your email address below
my $email_address = 'you@yourdomain.com';
###########################################################################
# DON'T MAKE CHANGES BELOW THIS LINE - unless you know what you are doing #
###########################################################################
my $prog_name = $0; # name of this program
my $option = shift; # the first parameter passed to this program
# If any useless parameters are provided to script give error.
# We are checking only the first param
usage("Invalid parameter(s)") if (($option) && ($option ne "--help") && ($option ne "?") && ($option ne "help") && ($option ne "--confirm") && ($option ne "confirm"));
# do we need to print help?
if (! defined($option)) { $option = "nevermind"; } # This is necessary so that perl does not complain about use of uninitialized value
help("Help Screen") if ($option =~ /--help|help|\?/);
usage("Usage") if (($option ne "confirm") && ($option ne "--confirm"));
# Validate Inputs
if (($rotate_backups !~ /^\d+$/) || ($rotate_backups < 0) || ($rotate_backups > 1)) {
die "The variable \$rotate_backups can only contain either 0 or 1";
}
if (($rotation_days !~ /^\d{1,3}$/) || ($rotation_days < 1)) {
die "The variable \$rotation_days must contain only, either 1, 2 or 3, digits - you can't leave this blank even if you are not rotating backups";
}
if (($backup_directory eq "") || ($backup_directory !~ /^\//)) {
die "You need to specify an absolute path to backup directory";
}
if (! (-d $backup_directory)) {
die "There is no such directory as $backup_directory";
}
if (! (-d $zimbra_path)) {
die "There is no such directory as $zimbra_path";
}
if (! (-e $path_to_rsync)) {
die "The path to 'rsync' provided by you as $path_to_rsync seems wrong. Please check.";
}
if (($to_email !~ /^\d+$/) || ($to_email < 0) || ($to_email > 1)) {
die "The variable \$to_email can only contain either 0 or 1";
}
if (! $email_address) {
die "Please enter a valid email address";
}
# Define an array to store the status of various actions of this script. This will be sent by email.
my @results;
# Stop Zimbra
my $zimbra_stop_status = system ("su - zimbra -c '$zimbra_path/bin/zmcontrol stop'");
if ($zimbra_stop_status) {
warn "Something was not right when trying to stop Zimbra";
push (@results, "Stopping Zimbra: Something was not right when trying to stop Zimbra");
} else {
push (@results, "Stopping Zimbra: Success");
}
die "Error stopping Zimbra" if ($zimbra_stop_status == -1);
# Kill all lingering Zimbra processes
my $zimbra_uid = getpwnam($zimbra_user);
my $process_table = Proc::ProcessTable->new;
# Gracefully kill lingering processes: kill -15, sleep, kill -9
foreach my $process ( @{$process_table->table} ) {
if (($process->uid eq $zimbra_uid) || (($process->cmndline =~ /$zimbra_user/) && ($process->cmndline !~ /$prog_name/))) {
kill -15,$process->pid; # thanks, merlyn
sleep 10; # not sure if there'll be buffering. If you know, please improve.
kill -9,$process->pid;
}
}
my $current_time = POSIX::strftime('%a-%d-%b-%Y-%H-%M', localtime(time)); # current day, date, month, time, ...
my $since_epoch = time(); # seconds since epoch
# Backup Zimbra using "rsync"
my $rsync_obj = File::Rsync->new( {
'rsync-path' => $path_to_rsync,
'archive' => 1,
'recursive' => 1,
'links' => 1,
'hard-links' => 1,
'keep-dirlinks' => 1,
'perms' => 1,
'owner' => 1,
'group' => 1,
'devices' => 1,
'times' => 1
} );
$rsync_obj->exec( { src => "$zimbra_path/", dest => "$backup_directory/$since_epoch-$current_time-zcsColdBackup" } ) or warn "rsync failed\n";
push (@results, "Backup using Rsync: Successfully created $backup_directory/$since_epoch-$current_time-zcsColdBackup");
# Now that backup is done, start Zimbra
my $zimbra_start_status = system ("su - zimbra -c '$zimbra_path/bin/zmcontrol start'");
if ($zimbra_start_status) {
warn "Something was not right when trying to start Zimbra";
push (@results, "Starting Zimbra: Something was not right when trying to start Zimbra");
} else {
push (@results, "Starting Zimbra: Success");
}
# Rotate backups
if ($rotate_backups) {
# get a list of all files from the backup directory
opendir (DIR, $backup_directory) or die "can't opendir $backup_directory: $!";
while (defined(my $filename = readdir(DIR))) { # actually, $filename is the name of all rsynced directories
next if ($filename !~ /-zcsColdBackup/); # if it is not a cold backup directory, ignore it
# if $filename is older than $rotation_days then delete it
my @filename_parts = split("-",$filename); # cutting from hyphens
my $allowed_age = $since_epoch - (60 * 60 * 24 * $rotation_days); # allowed age is converted to seconds
if ($filename_parts[0] < $allowed_age) { # if the first part of $filename is not equal to or more than the allowed age
rmtree ("$backup_directory/$filename") || die "Cannot delete $filename"; # delete it
push (@results, "Backup Rotation: Removed $backup_directory/$filename");
}
}
closedir (DIR);
}
if ($to_email) {
my $email_body = join("\n", @results);
my $mailer = Mail::Mailer->new("sendmail");
$mailer->open( {
'From' => 'root@localhost',
'To' => $email_address,
'Subject' => 'Results of zimbraColdBackup'
} ) or die "Can't open: $!\n";
print $mailer "Date: ", $current_time, "\n\n";
print $mailer $email_body, "\n";
$mailer->close();
}
sub usage {
# what to print when usage is incorrect
my $options = qq(
Usage: $prog_name [--help|?|help] [--confirm]
--help|?|help display help
--confirm|confirm run script
If no parameters are provided then the script will display usage.
Please edit this file to suit your system before using it.
);
die @_, $license, $options;
}
sub help {
my $help_txt = qq(
1. OVERVIEW:
This script can help in taking cold or off-line backups of Zimbra Collaboration Suite.
2. FEATURES:
a. Take cold or off-line backups of Zimbra Collaboration Suite
b. Rotate backups
c. E-mail backup reports
3. INSTALLATION:
This script relies on the following Perl modules:
POSIX, Proc::ProcessTable, File::Path, File::Rsync, Mail::Mailer
Please ensure that these modules are available on your system before you execute the script.
These can be downloaded from http://cpan.org
Once the modules are installed edit the zimbraColdBackup.pl script to match your system / requirements.
chmod this script to 755
chown this script to root:root
Done!
);
die @_, $license, $help_txt;
}