#! /usr/bin/env perl # script to create a "version" of the existing backup tree by # performing a cp -al $source $target/YYYY-MM-DD # this will create the directory structure and a hard link # from every file in the source (backupTree) to the target (versionDir) # It will also, optionally (with the $cleanup flag) remove old version # trees by calling sub canDelete for every directory under $target # sub canDelete is designed to be modified for each individual installation # Sample bash script to call this routine # #! /bin/bash # /etc/rsbackup/initscripts/makeVersion -s /home/backups -t /home/versions # can add --cleanup and --debug if necessary use warnings; use strict; use Getopt::Long; use Date::Parse; my $SECONDS_IN_DAY = 86400; # just defining it so you don't have to look it up my $TESTING=0; my $source; my $target; my $cleanup = 0; my $remoteServer; # this subroutine MUST be modified to suit your specific needs # The parameter passed is the directory name which is searched # for a date in the form YYYY-MM-DD (using a regex # /.*(\d{4}-\d{2}-\d{2}).*/ # the date is then compared against various criteria to determine # whether the directory should be deleted or not # a "true" return will delete the directory tree, and a "false" # will not. I suggest returning 0 and non-zero, though any # return value that equates to false will work. sub canDelete { my $date = shift; # first, parse the date in the directory name $date =~ m/(\d{4}-?\d{2}-?\d{2}([_-]?\d{2}\d{0,2}\d{0,2})?)/; $date = $1; $date =~ s/[^0-9]//g; # remove any non numerics $date = $date . '0'x(14 - length($date)) if length ( $date ) < 14; $date = substr( $date, 0, 4 ) . '-' . substr( $date, 4, 2 ) . '-' . substr( $date, 6, 2 ) . ' ' . substr( $date, 8, 2 ) . ':' . substr( $date, 10, 2 ) . ':' . substr( $date, 12, 2 ); # now, use str2time to parse it into a date $date = str2time($date); # and, turn it back into its individual parts. This lets us use yday, wday and mday. Note that we don't care # about the time portion in this one my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime( $date ); my $now = time; # get the same information for the current date my ($now_sec,$now_min,$now_hour,$now_mday,$now_mon,$now_year,$now_wday,$now_yday,$now_isdst) = localtime( $now ); ######################## USER MODIFIED CODE BELOW ################################# # Insert your code below. # The following sample code removes everything older than one year # and everything older than 7 days UNLESS it is the first of the month # this gives us monthly backups for a year, and daily backups for a week. return 1 if $year * 12 + $mon < $now_year * 12 - $now_mon; # true if older than one year return 0 unless $mday; # the first of the month is day 0 from localtime. Don't remove backups from the 1st return 1 if $now - ($SECONDS_IN_DAY * 7) > $date; # remove anything older than 7 days. return 0; # everything else is not deleted. } sub runCommandRemote { my $serverString = shift; my @output; while ( my $command = shift ) { if ( $TESTING ) { print "$serverString '$command'\n"; } else { my @thisOutput = `$serverString '$command'`; my $exitCode = $? >> 8; if ( $exitCode == -1 ) { # failed to execute return ( $exitCode, "$command failed with error message\n$!\n" ); } else { @output = ( @output, @thisOutput ); } # if..else } # if TESTING .. else } # while chomp @output; return ( 0, \@output ); } GetOptions( 'source:s' => \$source, 'target:s' => \$target, 'remote:s' => \$remoteServer, 'debug!' => \$TESTING, 'cleanup!' => \$cleanup ); unless ( $source && $target && $remoteServer) { print "Usage: makeVersion -s souceDirectory -t targetDirectory -r remoteServer --[no]debug --[no]cleanup\n"; exit 0; } $source =~ s!/\z!!; # remove trailing backslashes if they exist $target =~ s!/\z!!; $remoteServer = "ssh $remoteServer"; print "TEST MODE\n" if $TESTING; my $targetFileName = "$target/" . `date +'%Y%m%d_%H%M%S'`; # build the file name out of the date and time chomp $targetFileName; # remove the trailing newline. print "Checking if $targetFileName exists on $remoteServer\n"; my ($exitCode, $output ) = &runCommandRemote( $remoteServer, "if [ -d $targetFileName ] ; then echo exists ; fi" ); if ( $$output[0] && $$output[0] eq 'exists' ) { print "Refusing to overwrite existing directory $targetFileName\n"; exit 4; } print "Creating version in $targetFileName from $source\n"; print "Started " . `date`; print "Creating version in $targetFileName from $source\n"; ( $exitCode, $output ) = &runCommandRemote( $remoteServer, "cp -al $source $targetFileName" ); if ( $cleanup ) { # now, look through all subdirectories in $target and build a list of those to delete ( $exitCode, $output ) = &runCommandRemote( $remoteServer, "ls $target" ); # get all directories which do not begin with a period. my @subdirs = grep { /^[^.]/ } @$output; # check each one of them to see if we can delete it for ( my $i = 0; $i < scalar(@subdirs); $i++ ) { $subdirs[$i] = '' unless &canDelete( $subdirs[$i] ) ; } # build a delete string by 1) removing blank members (grep), 2) adding the directory (map) # then 3) joining the resulting array with spaces and 4) then putting the rm command in front my $deleteString = "rm -fR " . join( ' ', map ( "$target\/$_", grep ( !/^$/, @subdirs) ) ); if ( $TESTING ) { print "$deleteString\n"; } else { # print "NOT PERFORMING [$deleteString] while in trial\n"; ( $exitCode, $output ) = &runCommandRemote( $remoteServer, $deleteString ); } } print "Ended " . `date`; 1;