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.
202 lines
6.0 KiB
202 lines
6.0 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} ) {
|
|
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 ) {
|
|
die "Failed to create snapshot\n";
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
# 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;
|
|
}
|
|
|