Add zmbh script, a simple backup helper

This commit is contained in:
Daniel Berteaud 2019-08-14 19:02:03 +02:00
parent f80d13de91
commit bf6dc08be3
2 changed files with 207 additions and 0 deletions

22
zmbh/README.md Normal file
View File

@ -0,0 +1,22 @@
# Backup Helper
This script is an helper for backup operation. It won't backup anything by itself, but will prepare everything so your backup software can do its job. I made it for BackupPC but you can use it with anything else.
It will try to :
* Shut down Zimbra services to ensure data integrity (optionaly, you can choose to skip this part, or to only shut down LDAP which is the most sensitive service)
* Create a snapshot of the volume where /opt/zimbra is (only LVM supported right now)
* Mount the snapshoted FS on a specified dir, making sure Zimbra's tree appears where you asked
* Restart any service
Now, you can backup the snapshot with any backup tool, data is frozen. Once the backup is finished, just call the script with the --post arg to cleanup everything
Arguments :
* --pre or --post : Set if we run a pre or post backup routine. Default is --pre
* --snap-size : Size of the snapshot to create, for standard LVM volume. Default is 5G. Ignored for LVM thin volumes
* --mount : specify the directory where Zimbra tree should be mounted. Default is /home/lbkp/zimbra/mount
* --quiet : less details will be printed during execution
* --verbose : more details will be printed during execution
* --shutdown=[none|all|ldap] : select which components should be sutted down before taking the snapshot. Default is all, and will stop all Zimbra services. You can choose none (no Zimbra service will be shutted down) or ldap (only ldap, if installed, will be shutted down)

185
zmbh/zmbh.pl Normal file
View File

@ -0,0 +1,185 @@
#!/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");
system("/opt/zimbra/bin/ldap start");
} 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;
}