As a follow-up and complement to the scripts I have previously posted
to create/delete VSS Shadow backups for Windows, here is a similar
script that can be used for BTRFS snapshots on Linux.
The script can be extended without much difficulty to other *nix
filesystems with snapshot capability.
- Once again, all is done in the host config file (e.g.,
/etc/backuppc/pc/<host>.pl) using the somewhat obscure (but
officially supported) ability to substitute perl code for a single
shell command in the variables $Conf{DumpPreUsrCmd} and
$Conf{DumpPostUsrCmd}.
- Again, no external functions or scripts are required besides basic
installation of 'ssh', 'sudo' and btrs utilities
- As discussed on a recent thread, I use '/etc/sudoers' to allow
restricted sudo access to create/delete btrfs
subvolumes. Specifically, if backing up the backuppc server itself,
I just run 'sudo' (without 'ssh') and create the required privileges
for the 'backuppc' user itself. When backing up a remote client, I
create a special user on the remote machine (RemoteUser) who has
those same sudo privileges and who allows ssh from the backuppc user
on the backuppc server.
- The variable '$islocal' intelligently tries to determine if the
backup is local or not.
- To hashes are used to make sure the right snapshots are created:
%snapshots - which maps share names to the root mount of their
corresponding btrfs subvolumes
%snappaths - which allows for optional relative paths relative to
the btrfs subvolume root for each corresponding share
- A parameter 'Days' can also be set to cleanup old btrfs snapshots
that haven't been deleted (this mostly occurs if the connection to
the remote machine is lost during backup preventing the running of
$Conf{DumpPostUsrCmd}.
Here is the latest code:
--------------------------------------------------------------------
################################################################################
##### Create/delete BTRFS subvolume snapshots and adjust
##### $Conf{ShareName2Path} accordingly
##### Version 0.6 (Feb 2021)
##### Jeff Kosowsky
# COPYRIGHT
# Copyright (C) 2020-2021 Jeff Kosowsky
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
if(%snapshots) { #Create/delete btrfs snapshots and adjust
$Conf{ShareName2Path} accordingly
#Note: '%snapshots' is a hash of btrfs volumes to snapshot of form:
# my %snapshots = (
# 'sharename_1' => '<path-to-corresponding-btrfs-volume-root>',
# 'sharename_2' => '<path-to-corresponding-btrfs-volume-root>',
# ...
# );
#Note: '%snappaths' is a hash of paths relative to each btrfs snapshot root
# my %snappaths = (
# 'sharename_1' => '<relative-path-to-share-base-from-volume-root>',
# 'sharename_2' => '<relative-path-to-share-base-from-volume-root>',
# ...
# );
#Note: Set 'islocal' if you are backing up the local host and don't need to
ssh to a remote machine
my $islocal = (($Conf{ClientNameAlias} ne '' && `hostname -s` =~
/^$Conf{ClientNameAlias}$/) || `hostname -s` =~ /^$_[1]$/) ? 1 : 0;
#Login name for remote host (needs to have permissions to create/delete
btrfs subvolumes
$RemoteUser = 'backuppc-client'; #Create a restricted user with limited
sudo permissions
#Note: permission to create/delete btrfs snapshots is added to /etc/sudoers:
#backuppc,<RemoteUser> ALL=NOPASSWD: /bin/btrfs subvolume snapshot -r *,
/bin/btrfs subvolume delete *
#E.g., backuppc,backuppc-client ALL=NOPASSWD: /bin/btrfs subvolume
snapshot -r /mnt/btrfs-*/@* /mnt/btrfs-*/@*_snapshot-backuppc-*, /bin/btrfs
subvolume delete /mnt/btrfs-*/@*_snapshot-backuppc-*
my $DAYS = 2; #Number of days to wait before expiring stale snapshots
chomp(my $hostname = `hostname -s`);
chomp(my $timestamp = `date +"%Y%m%d.%H%M%S"`);
my $snapid = "_snapshot-backuppc";
my $suffix = "$snapid-$timestamp-$hostname";
my $snapshots = join(',', %snapshots); #Pack as string since can only pass
in scalar context to $Conf{Dump...}
my $snappaths = join(',', %snappaths);
#Note: Need to escape scalar and array variables but not hashes
$Conf{DumpPreUserCmd} = qq(&{sub {
my \$RemoteUser = "$RemoteUser";
my \$hostname = "$hostname";
my \$snapid = "$snapid";
my \$suffix = "$suffix";
my \$AGE = `date +%s` - $DAYS * 86400;
my \@sshcmd = $islocal == 1 ? () : ('ssh', '-q', '-x', '-l',
\$RemoteUser, \$args[0]->{host});
#If <host> is not local, prepend 'ssh -l -q -x [-i <id_rsa>]
<RemoteUser> <host>' so that command is executed on remote host via ssh
my %snapshots = split(/,/, "$snapshots"); #Convert back to hash (note
need to quote '\$snapshots' since gets expanded in scalar context)
my %snappaths = split(/,/, "$snappaths");
my \@snapsdone;
system('sync');
while(my (\$name, \$mount) = each %snapshots) { #Iterate through hash
#Set the path of 'sharename' to point to the corresponding snapshot
of the share plus optional 'snappath' ending path
\$bpc->{Conf}{ClientShareName2Path}->{\$name} = \$mount . \$suffix
.
(\$snappaths{\$name} =~ m|^[^/]| ? '/' : '') .
\$snappaths{\$name}; #Prepend '/' if not already present
next if grep(/^\${mount}\$/, \@snapsdone); #Skip the following if
already created (i.e. if share has same btrfs-volume)
#Delete old expired snapshots (older than $DAYS)
my \@oldsnaps = split ' ', `\@sshcmd ls -d
\${mount}\${snapid}-*-\${hostname} 2>/dev/null`; #Array of old snapshots for
$mount
foreach(\@oldsnaps) {
/\${mount}\${snapid}-([0-9]*)\.([0-9]{2})([0-9]{2})([0-9]{2})-\${hostname}/;
#Extract date
system(\@sshcmd, 'sudo', '/bin/btrfs', 'subvolume', 'delete',
\$_) if
`date -d "\$1 \$2:\$3:\$4" +%s` < \$AGE;
}
#Create read-only snapshot of 'sharemount' with added '$suffix'
system(\@sshcmd, 'sudo', '/bin/btrfs', 'subvolume', 'snapshot',
'-r', \$mount, \$mount . \$suffix);
if(\$? >> 8 != 0) { #Error creating snapshots
foreach(\@snapsdone) { #Unwind existing snapshots
system(\@sshcmd, 'sudo', '/bin/btrfs', 'subvolume',
'delete', \$_ . \$suffix); #Delete snapshot
}
\$? = 257; #Set error code
return;
}
push(\@snapsdone, \$mount); #Add to list of successful snapshots
}
}});
$Conf{DumpPostUserCmd} = qq(&{sub {
my \$RemoteUser = "$RemoteUser";
my \$suffix = "$suffix";
my \@sshcmd = $islocal == 1 ? () : ('ssh', '-q', '-x', '-l',
\$RemoteUser, \$args[0]->{host});
#If <host> is not local, prepend 'ssh -l -q -x [-i <id_rsa>]
<RemoteUser> <host>' so that command is executed on remote host via ssh
my %snapshots = split(/,/, "$snapshots"); #Convert back to hash (note
need to quote $snapshots since gets expanded in scalar context)
foreach (keys %{{ map{\$_ => 1} values(%snapshots)}}) { #Kluge to
create list of unique 'mounts' (values) from %snapshot hash
system(\@sshcmd, 'sudo', '/bin/btrfs', 'subvolume', 'delete', \$_ .
\$suffix); #Delete snapshot
}
}});
} else {
$Conf{DumpPreUserCmd} = undef;
$Conf{DumpPostUserCmd} = undef;
}
_______________________________________________
BackupPC-users mailing list
[email protected]
List: https://lists.sourceforge.net/lists/listinfo/backuppc-users
Wiki: https://github.com/backuppc/backuppc/wiki
Project: https://backuppc.github.io/backuppc/