backup scripts

Boris Goldowsky <boris-FrUbXkNCsVf2fBVCVOL8/[email protected]>
Newsgroups gmane.comp.sysutils.backup.hdup.general
Message-ID <[email protected]>
Here are two scripts, one for doing backups with hdup and one for
cleaning out old files.

The backup script is similar in capability to the one currently in the
examples folder of the hdup distribution, but I converted the 
implementation from bourne shell to perl, because the behavior of the 
find command was just too inconsistent between the systems I use (Linux 
and Mac OS X).

The clean up script removes old backups in a configurable way.  You can
specify how long to keep weekly and daily backups, and for monthlies
you can give a preferred set of ages (for example, you might want to
have archives from 1, 2, 4 and 8 months ago).  Of course it can't force
them to be exactly those ages all the time, but it does the best
approximation that it can.

Both scripts now will read a config file (they can share a config file 
with each other if you like, but not the same as the hdup
config file), and they gained some debugging features along the
way: with -v they will tell you exactly what they're doing and why;
with -v -n they will tell you all that, but not actually do it.  I
think that's a nice feature in a script whose purpose is to delete your
files :-).

Feel free to use these, or add them to the 'examples' folder of the
distribution.  Feel free also to improve them.  Use at your own risk,
however -- I haven't tested on many systems or with all possible
configurations, so standard free software disclaimers apply.

Bng
--
Boris Goldowsky
boris-FrUbXkNCsVf2fBVCVOL8/[email protected]
backup (text/plain, 3.5 KB)
#!/usr/bin/perl -T -w

### Determine what type of backup to run, and start hdup process to do it.

use strict;
use POSIX;

our $usage="USAGE: $0 [options]
Options:
  -v, --verbose  Give details about what was done and why.
  -n, --dry-run  Show what would be done, but do not actually run any backups.
  -h, --help     Show this help message.\n";

### Customize the variables below for your site
### Or, put all your settings in a configuration file and list it here:

our $conf_file = "/opt/hdup/backup-script.conf";

# Make PATH sufficient to find hdup, hostname, and ls:

$ENV{PATH}="/opt/hdup/sbin:/usr/sbin:/usr/bin:/bin";
$ENV{BASH_ENV} = '';

# Name of this host

our $host="hostname";

# Directory where hdup backup files are stored

our $basedir="/backup";

# Set these to the number of days that backups are to be considered current.
# So if you want weekly backups done every 7 days, set week to 6 (6 days good,
# the next day they'll be forced to be re-run).  If you want to run a daily
# backup every time this script is run, set day to 0.

our $month = 30;
our $week  =  6;
our $day   =  0;

# Additional args to pass to hdup running on this host:
our $hdup_args = "";

# Remote user & host to store backups on, expressed as @[email protected]
# For local backups, set to empty string:
our $remote = "";

######################################################
### You shouldn't have to change anything below here
######################################################

# Filenames to be checked to determine when previous backups were performed.

our $monthlyfile="inclist.monthly";
our $weeklyfile="inclist.weekly";
our $dailyfile="inclist.daily";

# Command line options defauls here, so conf file can override them:
our $verbose = 0;
our $dry_run = 0;

if (-r $conf_file) {
    do $conf_file;
    warn "Couldn't parse $conf_file: $@" if $@;
}

# Parse command line
while ($_ = shift) {
    if (/^-h$/ or /^--help$/) { 
	print "$usage";
	exit 0;
    }
    if (/^-v$/ or /^--verbose$/) {
	$verbose = 1;
	next;
    }
    if (/^-n$/ or /^--dry-run$/) {
	$dry_run = 1;
	next;
    }
    print STDERR "Unknown option: $_\n$usage";
    exit 1;
}

# Function CHECKFILE.
# takes two arguments: a FILENAME and an AGE.
# returns success (0) if and only if:
#  FILENAME exists
#  FILENAME is not zero length
#  FILENAME is newer than AGE days old.
# ie, returns true if there is a good-enough backup there.
sub checkfile ($$) {
    my $file = shift;
    my $age = shift;
    if ( ! -f $file ) {
	print "No $file\n";
	return 1;
    }

    if ( ! -s $file ) {
	print "$file is zero length\n";
	print `ls -l $file` if ($verbose);
	return 2;
    }

    my $daysold = POSIX::ceil ((time() - (stat($file))[9])/3600/24);
    if ($daysold > $age) {
	print "$file is $daysold days old: more than $age\n" if ($verbose);
	print `ls -l $file` if ($verbose);
	return 3;
    }

    print "$file is new enough ($daysold days old is <= $age):\n"
	. `ls -l $file` if ($verbose);
    return 0;
}

# Location of inclist files
my $indexdir="$basedir/$host/etc";

my $today = "";
if (checkfile ("$indexdir/$monthlyfile", $month )) {
    $today = "monthly";
} elsif (checkfile ("$indexdir/$weeklyfile", $week)) {
    $today = "weekly";
} elsif (checkfile ("$indexdir/$dailyfile", $day)) {
    $today = "daily";
} else {
    print "No need to run backup.\n";
    exit 0;
}

print "Running $today\n";

my $cmd = "hdup $hdup_args $today $host $remote";
print "  Command is $cmd\n";
if (!$dry_run) {
    print `hdup $hdup_args $today $host $remote`;
    exit $?;
}
cleanup (text/plain, 5.7 KB)
#!/usr/bin/perl -T -w

### Clean up old backup files according to desired schedule.
### This can be run daily on each machine where backups are stored;
### it will clean up the directories for all client machines.

use strict;

our $usage = "USAGE: $0 [options]
Options:
  -v, --verbose  Give details about what was done and why.
  -n, --dry-run  Show what would be done, but do not actually run any backups.
  -h, --help     Show this help message.\n";

### Customize the variables below for your site
### Or, put all your settings in a configuration file and list it here:

our $conf_file = "/opt/hdup/backup-script.conf";

# Path should be sufficient to find "df"

$ENV{PATH}="/opt/hdup/sbin:/usr/sbin:/usr/bin:/bin";
$ENV{BASH_ENV} = '';

# Directory where hdup backup files are stored
our $basedir = "/backup";

# Keep all daily backups from the last N days
our $daily_keep_days = 13;

# Keep all weekly backups from the last N days 
# For dailies to be useful, this value should be >= $daily_keep_days,
# and you should keep all monthlies through this time period as well.
our $weekly_keep_days = 30;

# List desired approximate ages for monthlies to be kept.
# The number of elements in this list is the number of monthly dumps that
# will be kept at all times.
# Each element defines a target age; the best matches are kept.
# The default keeps the newest monthly, plus approx. 1, 3, and 6 months ago.
our @monthly_keep = (180, 90, 30, 0);

######################################################
### You shouldn't have to change anything below here
######################################################

# Function prototypes
sub delete_file_and_dir ($);
sub remove_backups ($$);
sub age_of ($);

# Command line options defauls here, so conf file can override them:
our $verbose = 0;
our $dry_run = 0;

if (-r $conf_file) {
    do $conf_file;
    warn "Couldn't parse $conf_file: $@" if $@;
}

# Parse command line
while ($_ = shift) {
    if (/^-h$/ or /^--help$/) { 
	print "$usage";
	exit 0;
    }
    if (/^-v$/ or /^--verbose$/) {
	$verbose = 1;
	next;
    }
    if (/^-n$/ or /^--dry-run$/) {
	$dry_run = 1;
	next;
    }
    print STDERR "Unknown option: $_\n$usage";
    exit 1;
}

if ($weekly_keep_days < $daily_keep_days) {
    print STDERR "Unreasonable parameters:
  \$weekly_keep_days ($weekly_keep_days) is less than \$daily_keep_days ($daily_keep_days).
  But the older daily backups will not be useful without preceding weeklies.
";
    exit 1;
}

remove_backups ($daily_keep_days, "*daily*");
	
remove_backups ($weekly_keep_days, "*weekly*");

# Algorithm for monthlies:
#   Generate list of ages of existing monthlies.
#   For each desired age, starting with oldest:
#    Find closest age match from existing backups.
#    Mark that one as a keeper, and remove it from the list (can't use twice)
#   Finally, delete monthlies that are not marked as keepers.

# Future enhancements:
#   should check for zero-length files.
#   should force keeping monthlies up to and just past $weekly_keep_days.

foreach my $dir (glob ("$basedir/*")) {
    ## Security check on names:
    if ($dir =~ m{\A($basedir/\w[\w .-]*)\Z}) {
	$dir = $1;
    } else {
	print STDERR "Directory does not match expected pattern: $dir";
	next;
    }
    my %ages = ();
    my @files = glob ("$dir/*/*monthly.tar*");
    if ($#files <= $#monthly_keep) {
	# print "\nNo extra monthlies in $dir\n" if ($verbose);
    } else {
	print "\nAges of monthlies in $dir:\n" if ($verbose);
	for my $f (@files) {
	    ## For security, check filename against expected pattern
	    if ($f =~ m{\A($dir/\w[\w .-]*/\w[\w .-]*)\Z}i) {
		my $file = $1;
		my $age = age_of($file);
		printf "%4d %s\n", $age, $file;
		$ages{$age} = $file;
	    } else {
		print STDERR "File does not match expected pattern: $f";
	    }
	}
	
	for my $want (@monthly_keep) {
	    my $best_match = -1;
	    for my $age (keys %ages) {
		if ($best_match < 0
		    || abs($age-$want) < abs($best_match-$want)) {
		    $best_match = $age;
		}
	    }
	    if ($best_match>=0) {
		print "Best match for desired age $want is $best_match days old\n" if ($verbose);
		delete $ages{$best_match}; # remove from future consideration
	    }
	}

	# Remaining files are extraneous
	for my $age (keys %ages) {
	    if ($age < $weekly_keep_days) {
		print STDERR "Algorithm says to delete $ages{$age},
  but I'm keeping it so that weekly backups are still valid.
  You should change your setting of \@monthly_keep to keep all monthly backups
  for at least $weekly_keep_days days (the value of \$weekly_keep_days).\n";
	    } else {
		delete_file_and_dir ($ages{$age});
	    }
	}
    }
}

print "\n" . `df -lkh $basedir` if ($verbose);

sub remove_backups ($$)
{
    my ($age, $glob) = @_;
    my $header_printed = 0;
    foreach my $file (glob ("$basedir/*/*/$glob")) {
	## Security check on filename.  This is not just a good idea, it's required by -T.
	if ($file =~ m{\A($basedir/\w[\w .-]*/\w[\w .-]*/\w[\w .-]*)\Z}) {
	    $file = $1;
	} else {
	    print STDERR "File doesn't match expected pattern: $file\n";
	    next;
	}
	my $fileage = age_of($file);
	if ($fileage > $age) {
	    if ($verbose && not $header_printed) {
		print "Cleaning up $glob files older than $age days:\n";
		$header_printed = 1;
	    }
	    delete_file_and_dir ($file);
	}
    }
}

sub delete_file_and_dir ($)
{
    my $file = shift;
    my ($directory) = ($file =~ m{\A(.*)/[^/]+\Z});
    
    printf ("  Removing (age %2d) %s\n", age_of($file), $file) if ($verbose);
    if (not $dry_run) {
	if (unlink ($file) != 1) {
	    print STDERR "Problem deleting: $!\n";
	}
    }
    printf ("           and directory %s\n", $directory) if ($verbose);
    if (not $dry_run) {
	rmdir $directory or warn $!;
    }
}

## Return age of file, in days
sub age_of ($)
{
    return int ((time() - (stat($_[0]))[9])/3600/24);
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.