#!/usr/bin/env perl use warnings; use strict; # Performs simple rsync of local file system to remote # now, see what kind of controls we have. If %CONTROL is empty, only @options will be used. # however, if %OPTIONS has values, the key is the flag to be passed to rsync, and # the value is a reference to a function which returns true or false. Actually, the function # can return any value, but it is evaluated as a perl logical expression. # NOTE: using this schema, you can not have multiple entries for any flags to be passed, ie you # MUST have each flag/group of flags only one time. Also, it is your responsibility to ensure # valid flag setups: use the $configuration{'TEST_CONDITIONS'} flag above to show output instead of executing # Examples follow: # always adds --dry-run, using an anonymous hash # $CONTROL{'--dry-run'} = sub { return 1; }; # using a sub, and a reference to it, do checksum processing on first Sunday of month # sub firstOfMonth { # my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # return $wday == 0 && $mday < 7; # day of week is Sunday, and day of month < 7 # } # $CONTROL{'--cksum'} = \&firstOfMonth; # add reference to above routine # # Only perform chksum on saturdays # $CONTROL{'--cksum'} = sub{my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return $wday == 6; } ; # Requires: # IO::Zlib (found in libio-zlib-perl under Debian, perl-IO-Zlib.noarch under Fedora) # Add two directories to the include string, the script directory # and the directory above it. We are not sure whether rsbackup.pm will # be in either of those places. # # History: # version 0.1.1 20160826 RWR # fixed bug where was printing to STDOUT even with TEST_CONDITIONS set to 0 # # version 0.1.2 20180301 RWR # modified to use unshift for the libraries, giving them priority # # also modified code to use correct syntax for an array as a member of a # hash, using @{$configuration{'OPTIONS'}} instead of # $configuration{'OPTIONS'} # BEGIN { use File::Basename; use Cwd 'abs_path'; unshift @INC, abs_path( dirname(__FILE__) ); unshift @INC, abs_path( dirname(__FILE__) ) . '/../'; unshift @INC, abs_path( dirname(__FILE__) ) . '/../rsbackup_lib'; } # Now, get the library files we need require 'rsbackup.pm'; # standard package library use Getopt::Long; use File::Path qw(make_path); use Data::Dumper; my %configuration; # following variables to be overridden by configuration file # boolean to not run command, just prints command that would be executed. # also, at level 3, will not use standard configurations, but will use configuration file ../../etc/rsbackup/rsbackup.conf # for development $configuration{'TEST_CONDITIONS'} = 0; # who gets backup logs $configuration{'MAIL_TO'} = 'backups@example.com'; $configuration{'MY_NAME'} = 'client'; # full server name/IP and path to backup server, suitable for pasting into the rsync command # NOTE: the default assumes the user is root, the server is named backup in the same domain as this machine # and the target path on the server is /home/backups directory $configuration{'BACKUP_SERVER'} = 'root@backup.example.com'; # full server name to back up to (or IP) $configuration{'PATH_ON_SERVER'} = '/'; # root path on the server # THIS IS THE ACTUAL PATH USED. It is generally used by calculating from variables above. Note that it can be # used for other purposes also, such as a root directory for versions. $configuration{'BACKUP_PATH'} = "$configuration{'BACKUP_SERVER'}:$configuration{'PATH_ON_SERVER'}"; # The following list determines the directories on THIS MACHINE that are to be backed up. NOTE: During # upgrades, this entry is assumed to be on one line, upgrade may corrupt this entry if you write it # as a multi-line. $configuration{'DIRS_TO_BACKUP'} = ['opt', '/root', '/home']; # which directories on source are to be backed up # where to store the logs on this machine. NOTE: log files are compressed and stored, and are not removed # by rsync_backup. The default is to place them in /root/backup_logs, so they are actually backed up # then next day also. $configuration{'LOGDIR'} = '/home/backup_logs'; # log directory, either relative to script or absolute # if the following scripts are set, they are run at the appropriate time # the scripts MUST return a null on stdio to indicate success. Any output on stdio is considered # an error message and the backup is halted immediately with the error being emailed to the # account defined in the cron file. # see additional information with some pre-written scripts in the examples directory of the docs folder $configuration{'INITIALIZATION_SCRIPT'} = ''; # optional script to execute immediately after configuration file read $configuration{'CLEANUP_SCRIPT'} = ''; # optional script to clean up when backup done $configuration{'EXCLUDE_FILE'} = '/etc/rsync.exclude'; # location of file containing file patterns to be excluded $configuration{'RSYNC'} = ''; # full path to rsync executable $configuration{'GZIP'} = ''; $configuration{'MPACK'} = ''; $configuration{'COMPRESSED_FILE_EXTENSION'} = 'gz'; # extension for the compressed file (gz for gzip, zip for zip) $configuration{'OPTIONS'} = (); # List of command line flags to be passed to rsync $configuration{'CONTROL'} = {}; # options flags and conditions for using them. See notes above $configuration{'MAILER_SCRIPT'} = ''; # option used if a mailer script is used $configuration{'MAIL_FROM'} = ''; # name of sender for e-mail $configuration{'MAIL_SERVER'} = ''; # name of mail server to use $configuration{'TEMP_FILE'} = '/tmp/rsbackup.tmp'; # name of some temp file that can be overwritten at will $configuration{'PREPARE_COMMAND'} = ''; $configuration{'CLEANUP_COMMAND'} = ''; # Global Variables my $MY_PATH; my $VERSION = '0.1.2'; my $START_TIME; my $END_TIME; my $USAGE; my $body; my $logFile; my $rsyncReturnCode; my $remoteOutput = ''; my $testConfigurationFile; my $scriptName = ''; sub makeHeader { my $headerString = shift; my $separator = "\n" . '='x40 . "\n"; $headerString = $separator . $headerString . $separator; return $headerString; } sub buildPrepCommands { my $rsyncCommand = shift; my $sshCommand = 'ssh -p 22 '; if ( $rsyncCommand =~ m/(ssh -p \d+)/ ) { $sshCommand = $1; } $sshCommand .= " $configuration{'BACKUP_SERVER'} "; my $prepareCommand = $configuration{'PREPARE_COMMAND'} ? "$sshCommand ". qq/'$configuration{'PREPARE_COMMAND'}'/ : ''; my $cleanupCommand = $configuration{'CLEANUP_COMMAND'} ? "$sshCommand ". qq/$configuration{'CLEANUP_COMMAND'}/ : ''; return ($prepareCommand, $cleanupCommand); } GetOptions( 'testlevel:i' => \$configuration{'TEST_CONDITIONS'}, 'configuration:s' => \$testConfigurationFile, 'help!' => \$USAGE ); if ($USAGE) { print "rsbackup [--testlevel={0|1|3}] [--configuration=configurationFileName]\n"; print "--testlevel\n\t0 (normal run)\n\t1 (show commands but do not run)\n"; print "--testconf\n\tconffile (use alternate conf file, bypasses all configuration file settings)\n"; exit 0; } my $temp = &loadConfigurationFile(); die "No Configuration file found" unless $temp; # load configuration file into memory and die if it is invalid eval( $temp ) or die "Error in configuration file. Execute perl -c conffile for all copies"; # if utilities were found with which command, they have trailing \n chomp($configuration{'RSYNC'}); chomp($configuration{'GZIP'}); chomp($configuration{'MPACK'}); # make sure there is a slash at the end of the backup path name $configuration{'BACKUP_PATH'} = &addSlashToPath( $configuration{'BACKUP_PATH'} ); # if we can't find rsync, there is no use in continuing die "Could not execute rsync at $configuration{'RSYNC'}" unless -x $configuration{'RSYNC'}; my $scriptResults; # this will store any output from the initialization / cleanup scripts so they can be returned in summary my $scriptReturnCode; # stores the return code of the scripts called. # If there is an initialization script, run it print "Initialization Script set to [$configuration{'INITIALIZATION_SCRIPT'}]\n" if $configuration{'TEST_CONDITIONS'}; if ( $configuration{'INITIALIZATION_SCRIPT'} ) { ($scriptReturnCode, $scriptResults) = &runScript( $configuration{'INITIALIZATION_SCRIPT'} ); if ( $scriptReturnCode == 1 ) { # any return code other than 1 indicates an error print "Initialization Script Failed. Output Follows\n"; print "$scriptResults\n"; exit 1; } else { $scriptResults = &makeHeader( 'Initialization Script Output' ) . $scriptResults if $scriptResults; } } # record the start time $START_TIME = &dateTimeStamp('u'); # find the location where we are supposed to put the logs. print "Logging to $configuration{'LOGDIR'}\n" if $configuration{'TEST_CONDITIONS'}; make_path( $configuration{'LOGDIR'} ) unless -d $configuration{'LOGDIR'}; # create it if it doesn't exist. # now, create a log file name based on the date. Name will be: # YYYYMMDD_computername.backup.log in $LOGDIR $logFile = "$configuration{'LOGDIR'}/" . &dateTimeStamp('t',$START_TIME) . "_$configuration{'MY_NAME'}.backup.log"; print "Logfile is $logFile\n" if $configuration{'TEST_CONDITIONS'}; # if exclude file exists, add the exclude option push @{$configuration{'OPTIONS'}}, "--exclude-from=$configuration{'EXCLUDE_FILE'}" if $configuration{'EXCLUDE_FILE'} and -e $configuration{'EXCLUDE_FILE'}; # determine if any additional flags need to be used, ie checksum or dryrun while ( my ($value, $function ) = each(%{$configuration{'CONTROL'}})){ print "Checking Function for $value " . (&$function ? "Yes" : "No") . "\n" if $configuration{'TEST_CONDITIONS'}; push @{$configuration{'OPTIONS'}}, $value if &$function; } # we are now ready to begin the actual backup. open OUTPUT,">>$logFile" or die "Could not append to $logFile"; print OUTPUT "Backing up " . join (' ', @{$configuration{'DIRS_TO_BACKUP'}} ) . " to $configuration{'BACKUP_SERVER'} at $START_TIME\n"; print OUTPUT "$scriptName v$VERSION\n"; close OUTPUT; print "Backing up " . join (' ', @{$configuration{'DIRS_TO_BACKUP'}} ) . " to $configuration{'BACKUP_SERVER'} at $START_TIME\n" if $configuration{'TEST_CONDITIONS'}; # build the final copy command my $command = join( ' ', ($configuration{'RSYNC'}, join( ' ', @{$configuration{'OPTIONS'}} ) , join( ' ', @{$configuration{'DIRS_TO_BACKUP'}} ), "'$configuration{'BACKUP_PATH'}' >> $logFile") ); # build the prepare and cleanup commands. Note it gets the ssh parameters from that command my ($prepareCommand, $cleanupCommand) = &buildPrepCommands( $command ); if ( $configuration{'TEST_CONDITIONS'} ) { print "$prepareCommand\n"; print "$command\n"; print "$cleanupCommand\n"; } else { $scriptResults .= &makeHeader( 'Remote Prepare Command' ) . qx/$prepareCommand/ if $prepareCommand; qx/$command/; $rsyncReturnCode = $?; $scriptResults .= &makeHeader( 'Remote Cleanup Command' ) . qx/$cleanupCommand/ if $cleanupCommand; } $END_TIME = &dateTimeStamp('u'); print "Cleanup Script set to [$configuration{'CLEANUP_SCRIPT'}]\n" if $configuration{'TEST_CONDITIONS'}; if ( $configuration{'CLEANUP_SCRIPT'} ) { ($scriptReturnCode, $temp) = &runScript( $configuration{'CLEANUP_SCRIPT'} ); if ( $scriptReturnCode == 1 ) { # any return code other than 1 indicates an error print "Cleanup Script Failed. Output Follows\n"; print "$temp\n"; } else { $scriptResults .= &makeHeader( 'Cleanup Script Output' ) . $temp if $temp; } } $body = "\n\nBackup Version $VERSION\nComputer: $configuration{'CLIENT_NAME'} - $configuration{'MY_NAME'}\nCompleted Synchronization\nBegin " . &dateTimeStamp('t',$START_TIME) . " [$START_TIME]\nComplete " . &dateTimeStamp('t',$END_TIME) . " [$END_TIME]\n"; $body .= "Duration " . &time2HHMMSS($END_TIME-$START_TIME) . ' [' . ($END_TIME-$START_TIME) . " seconds]\n"; open my $fh, "<$logFile" or die "Could not open $logFile: $!"; $body .= &summarizeRsyncLog($fh, $rsyncReturnCode ); close $fh; $body .= "\n" . &makeHeader( 'Output of Initialization and/or Cleanup Scripts' ) . "\n$scriptResults" if $scriptResults; open OUTPUT,">>$logFile" or die "Could not append to $logFile"; print OUTPUT $body; close OUTPUT; &sendLogs ( &compressFile($logFile), $configuration{'MAIL_TO'}, $body, $configuration{'TEST_CONDITIONS'}, $configuration{'MAILER_SCRIPT'} ? $configuration{'MAILER_SCRIPT'} : '', ( $configuration{'MAIL_FROM'} ? $configuration{'MAIL_FROM'} : 'root@' . $configuration{'MY_NAME'} ), 'localhost', "Backup for $configuration{'MY_NAME'}, " . &dateTimeStamp($START_TIME) ); 1;