On Mac servers I love the advent of a neat feature called Time Machine. Time Machine does incremental backups without having to deal with 3rd-party software, or custom shell scripts like the one I'm about to show. Unfortunately, on the Linux side there's always some degree of elbow grease that's required. Which isn't to say you couldn't have Time Machine-like backups on Linux, you certainly can, it just takes a more concentrated technical effort, and you don't get the nifty GUI.
The following is a custom PHP shell script that I wrote for doing automated backups using the rsync command.
<?php
// Backup script extraordinaire!
// rsync docs
// http://optics.ph.unimelb.edu.au/help/rsync/rsync.html
// This requires PHP CLI (Command Line Interface)
// http://www.php.net/
// Just call it from the command line like this:
// php -q /path/to/this/script.php (CGI version)
// php /path/to/this/script.php (CLI version)
//
// Use the -q switch to supress the HTTP headers that PHP outputs by default.
// The folder or disk to backup
$source_volume = '/Volumes/Lothlorien';
// The folder or disk to backup to
$destination_volume = '/Volumes/Backup';
// The number of backups
$backup_count = 31;
//////////////////////////////////////////////////////////////////////////////////////
// Don't edit below this line!
//////////////////////////////////////////////////////////////////////////////////////
// Make sure the max is unlimited.
ini_set('max_execution_time', 0);
// If the destination and the source exist
if (file_exists($destination_volume))
{
if (is_writable($destination_volume))
{
if (file_exists($source_volume))
{
// Get the contents of the cursor file
if (file_exists($destination_volume.'/cursor.txt'))
{
$cursor = @file_get_contents($destination_volume.'/cursor.txt');
}
else
{
$cursor = null;
}
if (!empty($cursor))
{
$cursor = explode('|', $cursor);
$counter = (int) $cursor[0]; // The counter is before the vertical bar
$date = (int) $cursor[1]; // The date is after the vertical bar
}
else
{
// There is no cursor yet
$counter = 1;
$date = 0;
}
// If the day in the cursor and the day now don't match,
// go ahead and do a backup. This ensures that only one
// daily backup is done.
if (date('j', $date) != date('j') || empty($date))
{
// Open the cursor file for writing
$f_cursor = fopen($destination_volume.'/cursor.txt', 'w+');
// Make the Backups directory, if it doesn't exist
if (!file_exists($destination_volume.'/Backups'))
{
mkdir($destination_volume.'/Backups', 0755);
}
// Set up individual backup directories on the first run
for ($i = 1; $i <= $backup_count; $i++)
{
if (!file_exists($destination_volume.'/Backups/'.$i))
{
mkdir($destination_volume.'/Backups/'.$i, 0755);
}
}
// Output which backup is being written to the terminal
echo "Active backup: {$destination_volume}/Backups/{$counter}\n";
// Run the rsync command, and sync up the backup.
`rsync -r {$source_volume} {$destination_volume}/Backups/{$counter} --delete --ignore-errors`;
// Increment the counter for tomorrow's backup.
$counter++;
// Reset the counter back to one if the counter has
// exceded the backup count.
if ($counter > $backup_count)
{
$counter = 1;
}
// Write the cursor to a file on the backup volume, so it can be
// referenced for next time, and so you can determine which
// backup is the most up to date
fwrite($f_cursor, $counter.'|'.mktime());
fclose($f_cursor);
}
else
{
// Otherwise, it's still today's backup, and today's backup has already ran.
// Since today's backup has already ran, sync today's backup again
// Go back one, so you're not syncing to tomorrow's backup
if ($counter > 1)
{
$counter--;
}
else
{
$counter = $backup_count;
}
// Ouput which backup is being written to the terminal
echo "Active backup: {$destination_volume}/Backups/{$counter}\n";
// Run the rsync command, and sync up the backup
`rsync -r {$source_volume} {$destination_volume}/Backups/{$counter} --delete --ignore-errors`;
}
}
else
{
echo "Error: The source volume {$source_volume} does not exist or is not mounted.\n";
}
}
else
{
echo "Error: The destination volume {$destination_volume} is not writable.\n";
}
}
else
{
echo "Error: The destination volume {$destination_volume} does not exist or is not mounted.\n";
}
?>
The backup script makes 31 uncompressed backups of the same content. I prefer uncompressed copies because that makes restoring from backup much easier. The script could easily be modified to archive and compress using the switches offered by rsync. And the script could also be modified to make more or less than 31 backups, I find a month a good round number for backups, combine this script with multiple external hard drives, and you can go back in time as much as you like.
This script backups to the same copy for 24 hours, after 24 hours, it will go on to the next, until it gets to 31, then it will start over at 1 again. I typically set this script to run on a cronjob on Linux or, on a Mac, you can link to it from an AppleScript, then set the Apple Script up as an event in iCal. Alternatively you can automate it via launchd as well if the iCal approach isn't feasible.
Using rsync for the backup processes makes the backup more efficient with time, since it only syncs changes since the last time a backup was done.
A crafty onlooker might see a lot of opportunity for adding command line arguments to this script that would let you passthrough the configurations automatically from the shell... I could have done that too, but the script suited my needs as it is written, so I didn't bother.
Et voilà! A PHP shell script for backups. Help yourself.