This script performs a network backup using unix’s rsync command over ssh. It performs an incremental backup, which means that it only updates new files and files that have changed since the last backup, thus minimizing network traffic.

This script runs on the source machine (SOURCE) and connects to the backup machine (BACKUP). (SOURCE->BACKUP). It assumes a user named backup-SOURCE on the backup machine and connects as that user.

One advantage of using SOURCE->BACKUP setup rather than a BACKUP->SOURCE method is that if your BACKUP server is compromised, the attacker will not be able to gain access to all of your production machines.

There are admittedly disadvantages to running a SOURCE->BACKUP setup, rather than a BACKUP->SOURCE setup. One, you’ll need a copy of the script on each of the machines that you want to backup, making maintenance a bit more involved. Second, if your SOURCE machine becomes compromised, the attacker will be able to gain access to your backup server. This second issue can be mitigated by setting the permissions properly on the BACKUP machine. (This is why I use a separate user account for each machine that I backup.)

How to use this script:

  1. Edit the configuration options in the script.
  2. Add a user called “backup-{$SOURCE} to the BACKUP machine.
  3. Set up the appropriate SSH keys for the user to bypass password prompts.
  4. Set up a cron job to run the script nightly.
  5. If you want Apple-ish “time machine style” backups, set up a script to rotate the backups on the BACKUP machine. I use this rotate_backups.pl script to make backups using cp -al (hardlinks).

#!/usr/bin/perl
# Filename: rsync_backup.pl
# Will back things up from one computer to another over SSH using rsync.
# Use in conjunction with a rotate script on the destination machine for
# time-machine-like incremental backups.
# Note: SSH SSL auth keys must be installed on each end to avoid passwd prompt
# Assumes a user named "backup-$THIS_COMPUTER" on remote machine.
# All output will go to Logfile
#
# (c) 2009 eddie@eddieoneverything.com 

use Data::Dumper;

# This computer's name.  Used to name the backup files.
$THIS_COMPUTER = "yoakam";

# The remote machine's URL or IP address.
$REMOTE_MACHINE = "127.0.0.1";

# The backup directory on the remote machine
$REMOTE_BACKUP_LOCATION="/mnt/backup";

# Log things
$LOGFILE = "/var/log/rsync_backup";

# Directories to backup.
@BACKUP_DIRS=(
   '/home',
   '/root',
   '/etc',
   '/var/log',
   '/var/lib/mysql',
   '/var/spool/cron'
);

# Directories to ignore.
@IGNORE_DIRS=(
   '/home/lost+found',
   '/home/tmp',
   '/home/virtfs'
);

#####################################################
########### END Configuration options ###############
#####################################################

open hLOG, ">>$LOGFILE";
$now = `date`;
print hLOG "-" x 80, "\n";
print hLOG "START BACKUP $THIS_COMPUTER at $now\n" ;
print hLOG "-" x 80, "\n";
# Get a list of dirs to back up
foreach (@BACKUP_DIRS){
   $dir = $_;
   opendir(DIR, $dir) || alert ("cant opendir $dir: $!");
   while ($file = readdir(DIR)){
      if ($file ne '.' && $file ne '..'){
         $my_file = $dir . "/" .  $file;
         if ( -d $my_file){
            if (grep {/$my_file/} @IGNORE_DIRS){
               print hLOG "Ignore Dir: $my_file\n";
            }else{
               print hLOG "Dir: $my_file\n";
               push @BACKUP_LIST, {'dir' => $dir, 'name'=>$file};
            }
         }else{
            print hLOG "file is $my_file -- \n";
            @arr_temp =  ($dir, $my_file);
            push @BACKUP_LIST, {'dir' => $dir, 'name'=>$file};
         }
      }
   }
   closedir DIR;
}

#print Dumper(@BACKUP_LIST);

#Back up each dir in the backup list
foreach $my_entry (@BACKUP_LIST){
   $dir = $my_entry->{'dir'};
   $b = $my_entry->{'name'};

   $cmd = "rsync -avz --delete-after -e ssh \"$dir/$b\" backup-$THIS_COMPUTER\@$REMOTE_MACHINE:\"/$REMOTE_BACKUP_LOCATION/$THIS_COMPUTER$dir\"";
   $now=`date`;
   print hLOG "\n$now\t$cmd ";
   #print "$cmd\n";
   `$cmd >> $LOGFILE 2>&1`;
}

$now = `date`;
print hLOG "-" x 80, "\n";
print hLOG "END BACKUP of $THIS_COMPUTER at $now\n" ;
print hLOG "-" x 80, "\n";
close hLOG;

###################### Subs #########################
sub alert(){
   $a=shift(@_);
   print hLOG "#############################\n";
   print hLOG "#############################\n";
   print hLOG "#############################\n";
   print hLOG "Cannot backup $a $!\n";
   print hLOG "#############################\n";
   print hLOG "#############################\n";
   print hLOG "#############################\n";
}