###################################################################################
# This is a rotating remote backup script with rsync and 
# "compression" using hardlinks
# (c) David Bourget 2010, http://www.dbourget.com
# License: GPU General Public License. 
# License details here: http://www.gnu.org/licenses/gpl.html
###################################################################################
use warnings;
use strict;

###################################################################################
# CONFIGURATION 
#
# This is what you'd give as parameter to ssh to log in to the backup host
# IMPORTANT: you need to configure you ssh keys on the backup host and the client for
# password-less login for the user running this script on the client.
my $login = "user\@xyz.exavault.com";
#
# The directory on the backup host (relative to your home) where you want the backups
# Leave blank to use the home directory
my $dest = "";
#
# How many backups to keep. If you run the script once a day, 7 = 7 days of backups
my $max = 7;
#
# Path ro rsync. Default is the Debian/Ubuntu location.
my $R = "/usr/bin/rsync";
#
# Path to the ssh client. Default is the Debian/Ubuntu location.
my $SSH = "/usr/bin/ssh";
#
# Parameters to give to cp on backup host. This should make cp copy recursively and
# create hardlinks instead of copying.
my $CPPARAMS = "-lpR"; # this works with FreeBSD
# IMPORTANT: use this instead with a Linux backup host:
# my $CPPARAMS = "-ldpR";
# (if someone tests other hosts, let me know what params you need.)
#
# IMPORTANT: you also need to list the files and directories to backup in 
# HOME/.backup-targets. That file should have this format:
# my_docs /home/me/my_docs
# etc /etc
# ...
# The first column is the label of the item and the destination dir as well (inside
# $dest). It should be a valid filename.
# The second column is the directory or file to backup. Use absolute paths.
# The columns are space-separated, so no spaces inside the labels or the filenames.
# 
###################################################################################

###################################################################################
# KNOWN PROBLEMS
#
# - The script doesn't tell us much in the way of errors. You should check your 
# backups once in a while (you should do that in any case).
# - Single files (as opposed to directories) listed directly as backup targets
# are renamed 0, 1, etc. on the backup host, so extensions are lost. 
# - Hardlinks are useless if all your files always change, e.g. if you're backing
# up a single tarball. In most cases you'd be better of backing up 
# untarred directories.

my $list = "$ENV{HOME}/.backup-targets";
die "Can't read your backup targets at $list" unless -r $list;

$dest .= "/" if $dest;

open F, $list;
while (my $l = <F>) {
    chomp $l;
    my ($name, $path) = split(/\s+/, $l);
    next unless $name and $path;
    do_dir( $name, $path );
}
close F;

sub do_dir {

    my $dir = shift;
    my $path = shift;
    my @commands; # we buffer commands here

    # the mkdir and mv commands generate errors for the first runs
    # due to missing versions, but that's harmless as far as I can tell.

    print "rotating $dir.\n";

    # make the receiving dir
    push @commands, "mkdir $dest$dir";

    # remove the oldest version
    push @commands, "rm -rf $dest$dir/$max";

    # shift the numbers
    foreach my $i (reverse (1..$max-1)) {
        my $p = $i + 1;
        push @commands, "mv $dest$dir/$i $dest$dir/$p";
    }

    # link 0 to 1. 
    push @commands, "cp $CPPARAMS $dest$dir/0 $dest$dir/1";

    # Make a big command
    my $cmd = join(';',@commands);

    # Execute it remotely. We capture the remote host's output in $r. 
    my $r = `$SSH $login "$cmd" 2>&1`;

    # Uncomment for debugging
    #print $r;

    print "rsyncing $dir.\n";
    $path .= "/" if -d $path and $path !~ m/\/$/;

    $r = `$R -e ssh -avHz --delete $path $login:$dest$dir/0`;
    #print $r;

}


