Scripts and utilities for Zimbra
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
6.2 KiB

#!/usr/bin/perl -w
use warnings;
use strict;
use Getopt::Long;
use File::Path;
use File::Which;
use JSON;
my $opt = {
shutdown => 'all',
snap_size => '5G',
snapshot => 1,
mount => '/home/lbkp/zimbra/mount',
pre => 1,
post => 0,
quiet => 0,
verbose => 0
};
GetOptions (
'shutdown=s' => \$opt->{shutdown},
'snap-size=s' => \$opt->{snap_size},
'snapshot!' => \$opt->{snapshot},
'mount=s' => \$opt->{mount},
'pre' => \$opt->{pre},
'post' => \$opt->{post},
'quiet' => \$opt->{quiet},
'verbose' => \$opt->{verbose}
);
if ( not -d $opt->{mount} ) {
die $opt->{mount} . " must exist\n";
}
$opt->{pre} = 0 if $opt->{post};
# Start by assuming we can run snapshots
my $can_snapshot = 1;
my $lvs = which('lvs');
if (not $lvs) {
log_info("lvs not found, no snapshot will be attempted");
$can_snapshot = 0;
}
my $lv_info = {};
my ($dev, $fs, undef, undef, undef, undef, $mp) = split /\s+/, ( qx( df -PTl /opt/zimbra ) )[1];
log_verbose("Found device $dev mounted on $mp with an $fs filesystem");
if ( $can_snapshot ) {
log_verbose("Trying to detect if $dev is an LVM volume");
$lv_info = from_json(qx( $lvs --reportformat=json -o vg_name,lv_name,pool_lv $dev 2>/dev/null));
if (defined $lv_info->{report}->[0]->{lv}->[0] ){
$lv_info = $lv_info->{report}->[0]->{lv}->[0];
}
}
if ( $opt->{pre} ) {
my $failure = 0;
if ($opt->{shutdown} =~ m/^no(ne)?/){
log_info("Not shutting down any service");
} elsif ($opt->{shutdown} eq 'ldap' and -e '/opt/zimbra/bin/ldap'){
log_info("Stoping Zimbra LDAP service");
system("/opt/zimbra/bin/ldap stop");
} else {
log_info("Stoping Zimbra services");
system('systemctl stop zimbra');
}
if ( not $lv_info->{vg_name} or not $lv_info->{lv_name} or $lv_info->{vg_name} eq '' or $lv_info->{lv_name} eq '' ) {
# We cannot take a snapshot. Zimbra will just be kept shut down until the end of the backup (unless you choose not to shut down services)
# Just bind mount /opt/zimbra on the backup dir
log_info("Can't create a snapshot of $dev");
if ( system('mount -o bind,ro /opt/zimbra ' . $opt->{mount} ) ) {
die "Can't mount /opt/zimbra on $opt->{mount}\n";
}
} else {
log_info("Trying to create a snapshot of device $dev");
my $snap_args = '-s -n ' . $lv_info->{lv_name} . '_bkp';
# Detect if thin pool or standard LVM
if ( defined $lv_info->{pool_lv} and $lv_info->{pool_lv} ne '' ) {
# Thin LVM
log_verbose("$dev is a thin LVM volume");
$snap_args .= ' -kn';
} else {
# Standard LVM
log_verbose("$dev is a standard LVM volume");
$snap_args .= ' -L' . $opt->{snap_size};
}
# Take the snapshot
if ( system( "lvcreate $snap_args $dev") != 0 ) {
log_info("Failed to create snapshot");
# Record the failure but don't die now, we need to restart services
$failure = 1;
} else {
log_info("snapshot created as $dev" . '_bkp');
}
# Restart Zimbra now to minimize down time
if ($opt->{shutdown} =~ m/^no(ne)?/){
log_info("No service were shutted down");
} elsif ($opt->{shutdown} eq 'ldap' and -e '/opt/zimbra/bin/ldap'){
log_info("Starting Zimbra LDAP service");
my $try = 0;
my $running = 0;
while ($try < 20) {
system("/opt/zimbra/bin/ldap start");
sleep 1;
if (system("/opt/zimbra/bin/ldap status") == 0){
$running = 1;
last;
} else {
log_info('ldap service not running, trying again to start it');
$try++;
}
}
# Couldn't start ldap ? Restart all the services
if (not $running){
log_info("Failed to restart ldap, restarting all Zimbra services");
system('systemctl restart zimbra');
}
} else {
log_info("Starting Zimbra services");
system('systemctl start zimbra');
}
if ($failure){
die "Stoping backup process now, as snapshot failed\n";
}
# Now mount the snapshot RO
my $mount_args = "-o ro -t $fs";
if ( $fs eq 'xfs' ) {
$mount_args .= ' -o nouuid';
}
log_verbose("Mounting the snapshot readonly on $opt->{mount}");
if ( system("mount $mount_args /dev/mapper/" . $lv_info->{vg_name} . '-' . $lv_info->{lv_name} . '_bkp ' . $opt->{mount}) != 0 ) {
die "Can't mount " . $lv_info->{lv_name} . '_bkp on ' . $opt->{mount} . "\n";
}
# The snapshot is mounted, but we might need an additional bind mount if the volume hosts / or /opt
my $level = grep { $_ ne '' } split( /\//, $mp);
my $level2subdir = {
0 => '/opt/zimbra',
1 => '/zimbra'
};
if ( defined $level2subdir->{$level} ) {
if ( system('mount -o bind,ro ' . $opt->{mount} . $level2subdir->{$level} . ' ' . $opt->{mount} ) ) {
die "Can't mount $opt->{mount}$level2subdir->{$level} on $opt->{mount}\n";
}
}
}
} elsif ( $opt->{post} ) {
log_info("unmounting $opt->{mount}");
while (is_mounted($opt->{mount})){
# We need to loop as we can have a stacked bind mount over the standard FS
system( "umount $opt->{mount}" );
}
if ( not $lv_info->{vg_name} or not $lv_info->{lv_name} or $lv_info->{vg_name} eq '' or $lv_info->{lv_name} eq '' ) {
# No backup snapshot, zimbra should just be started again
log_info("Restating Zimbra services");
system('systemctl start zimbra');
} else {
log_verbose("Removing LVM snapshot");
if ( system( "lvremove -y $lv_info->{vg_name}/$lv_info->{lv_name}" . '_bkp' ) != 0 ) {
die "Failed to remove LVM snapshot\n";
}
}
}
# Print messages only if the verbose flag was given
sub log_verbose {
my $msg = shift;
print $msg . "\n" if ( $opt->{verbose} );
}
# Print info messages unless the quiet flag was given
sub log_info {
my $msg = shift;
print $msg . "\n" if ( not $opt->{quiet} );
}
# Print errors
sub log_error {
my $msg = shift;
print $msg . "\n";
}
# Check if something is mounted on a dir
sub is_mounted {
my $dir = shift;
$dir =~ s/\/$//;
my $is_mounted = 0;
open MOUNTS, '</proc/mounts';
while (<MOUNTS>){
my ($what, $where, $type, $options) = split(/\s+/, $_);
if ($where eq $dir){
$is_mounted = 1;
last;
}
}
close MOUNTS;
return $is_mounted;
}