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
BackupPC-users@lists.sourceforge.net
List:    https://lists.sourceforge.net/lists/listinfo/backuppc-users
Wiki:    https://github.com/backuppc/backuppc/wiki
Project: https://backuppc.github.io/backuppc/

Reply via email to