io timeout after 180 seconds
chris allen
maillinglist at mychristiannetwork.com
Mon Feb 28 10:15:42 GMT 2005
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Thanks for all the help so far. I am learning a lot from it.
I have a script that I will post bellow that when I run it gives me
this error.
mysql/website_forum/nntp_settings.MYD
io timeout after 180 seconds - exiting
rsync error: timeout in data send/receive (code 30) at io.c(103)
rsync: connection unexpectedly closed (1493583 bytes read so far)
rsync error: error in rsync protocol data stream (code 12) at io.c(165)
WARNING: there seems to have been an rsync error.
Check the logs for more information.
The script is running php and the script is attached and posted bellow
#!/usr/bin/php -q
<?php
//** $Id: ribs 1625 2004-11-24 20:55:48Z jrust $ */
// {{{ version
define('RIBS_VERSION', '2.3');
// }}}
// {{{ description
/**
~ * RIBS (Rsync Incremental Backup System) by Jason Rust
<jrust at rustyaprts.com>
~ * Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>
~ * The latest version of this program can be found at
~ * http://rustyparts.com/scripts.php
~ *
~ * License:
~ *
~ * This source file is subject to the GNU Public License (GPL),
~ * that is bundled with this package in the file LICENSE, and is
~ * available at through the world-wide-web at
~ * http://www.fsf.org/copyleft/lesser.html
~ * If you did not receive a copy of the LGPL and are unable to
~ * obtain it through the world-wide-web, you can get it by writing the
~ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
~ * MA 02111-1307, USA.
~ *
~ * This program is distributed in the hope that it will be useful,
~ * but WITHOUT ANY WARRANTY; without even the implied warranty of
~ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
~ *
~ * See http://www.gnu.org/copyleft/gpl.html for a copy of the license.
~ *
~ * Description:
~ *
~ * RIBS is an incremental backup system written in PHP which utilizes
~ * some common *nix programs (specifically rsync, ssh and cp).
~ *
~ * Requirements:
~ *
~ * rsync - http://samba.anu.edu.au/rsync/
~ * cp & rm - http://www.gnu.org/software/fileutils/fileutils.html
~ * PHP - http://www.php.net/
~ * basic PEAR libraries (as of version 1.1) - http://pear.php.net/
~ *
~ * Use:
~ *
~ * See the README that comes with this package.
~ */
// }}}
// {{{ user settings
/****** Configuration Section ******/
/**
~ * This is the place on the server where the backups will end up WITH
trailing slash
~ * @type string
~ */
$s_destinationDir = dirname(__FILE__) . '/backups/';
/**
~ * An array of the different servers to back up, and what directories
on them to back up
~ * The structure is as follows:
~ * 'backup_name' => array(
~ * 'enabled' => 'this config enabled? true or false',
~ * 'ip' => 'server ip address/FQDN',
~ * 'ssh_user' => 'the ssh user to use for rsync',
~ * 'ssh_port' => 'the ssh port (optional, defaults to 22)',
~ * 'directories' => 'backup directories separated by a space
WITHOUT a trailing slash (i.e. /home /root)',
~ * If a directory has a space in it, escape
it with a \
~ * 'excludes' => 'the directories to exclude separated by a
space.
~ * See the README for more information on
exclude patterns.'
~ * 'limits' => 'an array of directory limits that will
allow you to override the limits you set in
~ * $a_backupTypeSettings. Completely
optional. e.g. array('hourly' => 2) if you back up
~ * hourly twice a day.'
~ * 'use_hard_links'=> 'Whether to do incremental backups using
hard links or real files.
~ * Default option is true. See "Backup
Types" in the README for more info.'
~ * 'post_command' => 'A shell command to run after a successful
backup. Replaces %fullPath%
~ * and %backupType% with the real values.'
~ * 'post_error_command' => 'Same as post_command, but run when an
error occurs.'
~ * ),
~ * @type array
~ */
$a_backupHosts = array(
~ 'luke' => array(
~ 'enabled' => true,
~ 'ip' => 'mychristiannetwork.com',
~ 'ssh_user' => 'root',
~ 'directories' => '/etc /var/lib/mysql /home',
~ 'excludes' => '',
~ 'limits' => array('hourly' => 24), // because this is
backed up every hour
~ ),
~ 'small_host' => array(
~ 'enabled' => false,
~ 'ip' => 'small_host.example.com',
~ 'ssh_user' => 'backup',
~ 'ssh_port' => '9999',
~ 'directories' => '/etc',
~ 'excludes' => '',
~ ),
~ 'big_host' => array(
~ 'enabled' => false,
~ 'ip' => '10.52.1.1',
~ 'ssh_user' => 'root',
~ 'directories' => '/var',
~ 'excludes' => 'mp3/', // exclude any mp3 directories
~ ),
~ // A test example configuration that can be used with the test
~ // directory that comes with RIBS.
~ 'example' => array(
~ 'enabled' => false,
~ 'ip' => 'localhost',
~ 'ssh_user' => get_current_user(),
~ 'directories' => dirname(__FILE__) . '/test\ dir',
~ 'use_hard_links'=> true,
~ // An example of a post_command which will tar up each backup.
~ // NOTE: this is only useful if use_hard_links is false
~ // 'post_command' => 'if [ -d %fullPath%%backupType%.1 ];
then tar czv -C %fullPath%%backupType%.1 -f backup.tar.gz .; rm -rf
%fullPath%%backupType%.1; mkdir %fullPath%%backupType%.1; mv
backup.tar.gz %fullPath%%backupType%.1/; fi',
~ 'excludes' => '/test\ dir/my\ mp3 +/test\ dir/include.mp3
*.mp3 *.svn/ junk/',
~ // exclude the root mp3 directory,
~ // any mp3 files anywhere else (except for
include.mp3),
~ // and any .svn/ and junk/ directories
~ ),
);
/**
~ * Some settings for the four different backup types. For each type
you must specify
~ * 'limit' => of directories to keep. So, if you run every two hours,
make hourly 12,
~ * if every hour then 24, etc. Try to span the entire day. The below
setting runs 8
~ * times a day (every three hours), keeps 7 days, 4 weeks, and 12
months of backups.
~ * 'email' => whether to send email or not after completion
~ * 'log' => whether to log after completion
~ * @type array
~ */
$a_backupTypeSettings = array(
~ 'hourly' => array('limit' => 8, 'email' => true, 'log' => true),
~ 'daily' => array('limit' => 7, 'email' => true, 'log' => true),
~ 'weekly' => array('limit' => 4, 'email' => true, 'log' => true),
~ 'monthly' => array('limit' => 12, 'email' => true, 'log' => true),
);
/**
~ * The email address to send reports to (you can comma separate
multiple addresses)
~ * @type string
~ */
$s_email = 'cyo at mychristiannetwork.com';
/**
~ * The log file.
~ * @type string
~ */
$s_logFile = dirname(__FILE__) . '/ribs.log';
/**
~ * When to start the log over. Note, this does not mean we rotate
the log
~ * it is just to keep the log file from getting too big. Can be
hourly, daily,
~ * weekly, or monthly
~ * @type string
~ */
$s_restartBackupLog = 'weekly';
/**
~ * When to do an actual rsync. Otherwise, we just copy the oldest
directory
~ * of the type below to the current type. Example: if doing a monthly
backup
~ * and $s_rsyncType is not 'monthly' then the oldest weekly directory
will be
~ * copied to monthly.0. This keeps down the amount of disk space
needed. Usually you
~ * will want this to be 'hourly'
~ * @type string
~ */
$s_rsyncType = 'hourly';
/**
~ * Always email if there's an error? This overrides the specific
backup setting
~ * to ensure you will get an email if there is a problem.
~ * @type bool
~ */
$b_emailOnError = true;
/**
~ * Die quietly? If so, then on error we just shut up, email (see
above), and quit,
~ * otherwise we throw the error.
~ * @type bool
~ */
$b_silentOnError = false;
/**
~ * Default permissions for when we have to create a new directory
~ * @type int
~ */
$s_defaultDirPerms = 0750;
/**
~ * Additional comands to be run at the end of each backup (useful for
giving
~ * additional info about file sizes, directories, etc.). Leave empty
for nothing.
~ * %fullPath% is a placeholder that will be filled in with the full
path to the current backup.
~ * @type string
~ */
$s_extraCommands = "ls -l %fullPath%; df -h";
/**
~ * The extra rsync commands to use
~ * @type string
~ */
$s_rsyncArgs = '-arztplv --delete --delete-excluded --stats
- --timeout=180';
/**
~ * The path to the rsync command
~ * @type string
~ */
$s_rsync = '/usr/bin/rsync';
/**
~ * The path to rm
~ * @var string
~ */
$s_rm = '/bin/rm';
/**
~ * The path to cp
~ * @type string
~ */
$s_cp = '/bin/cp';
/****** End Config. No need to edit anything else! ******/
// }}}
// {{{ readline()
/**
~ * Reads a line from standard input. Have to put it up here so php
loads it in.
~ *
~ * @access public
~ * @return string The string from input
~ */
if (!function_exists('readline')) {
~ function readline () {
~ $fp = fopen('php://stdin', 'r');
~ $in = fgets($fp, 4094); // Maximum windows buffer size
~ fclose ($fp);
~ return $in;
~ }
}
// }}}
// {{{ requires
require_once 'Console/Getopt.php';
// }}}
// {{{ grab command line vars
// don't run out of time
set_time_limit(0);
// we want all errors
error_reporting(E_ALL);
$args = Console_Getopt::readPHPArgv();
if (PEAR::isError($args)) {
~ die('Fatal Error: ' . $args->getMessage() . "\n");
}
$options = Console_Getopt::getopt($args, 'dhtr', array('debug',
'help', 'test', 'reinit'));
if (PEAR::isError($options)) {
~ die($options->getMessage() . "\n");
}
$b_debug = false;
$b_dryRun = false;
$b_cleanupDryRun = false;
$b_reinit = false;
$s_log = '';
$b_error = false;
foreach ($options[0] as $option) {
~ switch ($option[0]) {
~ case 'h':
~ case '--help':
~ showUsage($args[0]);
~ exit;
~ break;
~ case 'd':
~ case '--debug':
~ $b_debug = true;
~ break;
~ case 't':
~ case '--test':
~ $b_dryRun = true;
~ break;
~ case 'r':
~ case '--reinit':
~ $b_reinit = true;
~ break;
~ }
}
if (empty($options[1][0])) {
~ writeln('Error: Configuation not specified' . "\n");
~ showUsage($args[0]);
~ exit;
}
// grab the config names from the command line
$a_configNames = explode(',', $options[1][0]);
// ALL is a special keyword to run all backups
if ($a_configNames[0] == 'ALL') {
~ $a_configNames = array_keys($a_backupHosts);
}
if ($b_reinit) {
~ reInitBackups();
~ exit;
}
if (empty($options[1][1])) {
~ writeln('Error: Backup type not specified' . "\n");
~ showUsage($args[0]);
~ exit;
}
// grab the backup type from the command line
$s_backupType = $options[1][1];
if ($b_debug) {
~ array_shift($args);
~ writeln('Debug mode is ON.');
~ writeln('Arguments received: ' . implode(' ', $args));
~ writeln('Configuration names: ' . implode(',', $a_configNames));
~ writeln('Backup type: ' . $s_backupType);
}
if ($b_dryRun) {
~ writeln('Dry run mode is ON.');
}
// }}}
// {{{ set up vars and dirs
// start log
$s_log .= date('F j, Y, g:i a') . ": $s_backupType backups for " .
implode(',', $a_configNames) . "\n";
// make sure it's a valid type
if ($s_backupType != 'hourly' &&
~ $s_backupType != 'daily' &&
~ $s_backupType != 'weekly' &&
~ $s_backupType != 'monthly') {
~ writeln("ERROR: The backup type: '$s_backupType' is not valid
(valid types are hourly, daily, weekly, and monthly).");
~ exit;
}
// check binaries
if (!is_executable($s_rsync)) {
~ writeln("WARNING: The rsync program: '$s_rsync' is not valid.");
~ exit;
}
if (!is_executable($s_rm)) {
~ writeln("WARNING: The rm program: '$s_rm' is not valid.");
~ exit;
}
if (!is_executable($s_cp)) {
~ writeln("WARNING: The cp program: '$s_cp' is not valid.");
~ exit;
}
if (!is_dir($s_destinationDir)) {
~ mkdir($s_destinationDir, $s_defaultDirPerms);
~ $s_log .= writeln("Created destination directory:
$s_destinationDir", true);
}
// determine the type below the current type
if ($s_backupType == 'daily') {
~ $s_typeBelow = 'hourly';
}
elseif ($s_backupType == 'weekly') {
~ $s_typeBelow = 'daily';
}
elseif ($s_backupType == 'monthly') {
~ $s_typeBelow = 'weekly';
}
else {
~ $s_typeBelow = 'monthly';
}
// }}}
// {{{ loop through backup configurations
foreach ($a_configNames as $s_configName) {
~ // make sure that we are using a valid configuration
~ if (!isset($a_backupHosts[$s_configName])) {
~ $tmp_msg = writeln("WARNING: The configuation: '$s_configName'
is not valid.\n", true, true);
~ $s_log .= $tmp_msg;
~ $b_error = true;
~ continue;
~ }
~ else {
~ $s_log .= writeln("-= Beginning backups for $s_configName\n",
true, true);
~ }
~ // make sure it's enabled
~ if (!$a_backupHosts[$s_configName]['enabled']) {
~ $s_log .= writeln("$s_configName is disabled. Skipping
backup.", true);
~ continue;
~ }
~ $s_fullPath = $s_destinationDir . $s_configName . '/';
~ if (!is_dir($s_fullPath)) {
~ mkdir($s_fullPath, $s_defaultDirPerms);
~ $s_log .= writeln("Created directory for $s_configName:
$s_fullPath", true);
~ }
~ // {{{ rotate the current list of backups if we can
~ if ($tmp_handle = opendir($s_fullPath)) {
~ $a_dirList = array();
~ $a_dirListTypeBelow = array();
~ while (false !== ($tmp_file = readdir($tmp_handle))) {
~ if (is_dir($s_fullPath . $tmp_file)) {
~ if (preg_match(":$s_backupType\.\d+$:", $tmp_file)) {
~ $a_dirList[] = $tmp_file;
~ }
~ // for later we need the directories of the type below
~ if (preg_match(":$s_typeBelow\.\d+$:", $tmp_file)) {
~ $a_dirListTypeBelow[] = $tmp_file;
~ }
~ }
~ }
~ closedir($tmp_handle);
~ // determine maximum number of directories allowed for this
backup
~ if (isset($a_backupHosts[$s_configName]['limits']) &&
isset($a_backupHosts[$s_configName]['limits'][$s_backupType])) {
~ $tmp_max =
$a_backupHosts[$s_configName]['limits'][$s_backupType];
~ }
~ else {
~ $tmp_max = $a_backupTypeSettings[$s_backupType]['limit'];
~ }
~ // If the max is 1 (backing up only once a day) then don't
rotate anything
~ if ($tmp_max > 1) {
~ // go through directories in reverse order and rotate them
~ natsort($a_dirList);
~ $a_dirList = array_reverse($a_dirList);
~ foreach ($a_dirList as $tmp_dir) {
~ $tmp_key = preg_replace(':.*\.(\d+)$:', '\\1', $tmp_dir);
~ // rotate off any of the old backups
~ if (($tmp_key + 1) >= $tmp_max) {
~ if ($b_debug || $b_dryRun) {
~ $tmp_msg = 'Rotating off old backup: ' .
$s_fullPath . $tmp_dir;
~ if ($b_dryRun) {
~ $tmp_msg = str_replace('Rotating', 'Would
rotate', $tmp_msg);
~ }
~ writeln($tmp_msg);
~ }
~ if (!$b_dryRun) {
~ // :NOTE: can use System::rm when it supports
symlinks in directories
~ exec("$s_rm -rf $s_fullPath$tmp_dir");
~ }
~ }
~ // otherwise rotate the directory up
~ else {
~ $tmp_key++;
~ $tmp_newDir = preg_replace(':\.\d+$:',
".$tmp_key", $tmp_dir);
~ if ($b_debug || $b_dryRun) {
~ $tmp_msg = "Rotating backup directory from
$s_fullPath$tmp_dir to $s_fullPath$tmp_newDir";
~ if ($b_dryRun) {
~ $tmp_msg = str_replace('Rotating', 'Would
rotate', $tmp_msg);
~ }
~ writeln($tmp_msg);
~ }
~ if (!$b_dryRun) {
~ rename($s_fullPath . $tmp_dir, $s_fullPath .
$tmp_newDir);
~ }
~ }
~ }
~ }
~ }
~ // }}}
~ // {{{ perform rsync
~ // this dir shouldn't be here, but check anyhow to make sure we
~ // don't get recursive directories
~ if (!$b_dryRun && $tmp_max > 1 &&
is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
~ exec("$s_rm -rf $s_fullPath$s_backupType.0");
~ }
~ // we only rsync if the type is hourly or on the type is the one
they want rsynced
~ if ($s_backupType == 'hourly' || $s_backupType == $s_rsyncType) {
~ // make hard-link-only copy of latest directory
~ if (is_dir("$s_fullPath$s_backupType.1") && !$b_dryRun) {
~ if
(!isset($a_backupHosts[$s_configName]['use_hard_links']) ||
~ $a_backupHosts[$s_configName]['use_hard_links']) {
~ exec("$s_cp -al $s_fullPath$s_backupType.1
$s_fullPath$s_backupType.0");
~ } else {
~ // When not using the hard-link method of backups we
~ // want the full backup to be in .0 and any files that
have
~ // been changed to be placed in .1
~ rename("$s_fullPath$s_backupType.1",
"$s_fullPath$s_backupType.0");
~ mkdir("$s_fullPath$s_backupType.1", $s_defaultDirPerms);
~ }
~ }
~ // directory needs to exist for dry run
~ if ($b_dryRun && !is_dir("$s_fullPath$s_backupType.0")) {
~ $b_cleanupDryRun = true;
~ mkdir("$s_fullPath$s_backupType.0");
~ }
~ $tmp_excludes = preg_split('/([^\\\\])\s/',
$a_backupHosts[$s_configName]['excludes'], -1, PREG_SPLIT_NO_EMPTY |
PREG_SPLIT_DELIM_CAPTURE);
~ $exclude_args = '';
~ for ($i = 0; $i < count($tmp_excludes); $i++) {
~ $s_exclude = str_replace('\\', '', $tmp_excludes[$i]);
~ if (isset($tmp_excludes[$i + 1]) &&
strlen($tmp_excludes[$i + 1]) == 1) {
~ $s_exclude .= $tmp_excludes[++$i];
~ }
~ // check if we want to include rather than exclude
~ if (substr($s_exclude, 0, 1) == '+') {
~ $s_exclude = '+ ' . substr($s_exclude, 1);
~ }
~ // in this case we want a literal '+'
~ elseif (substr($s_exclude, 0, 2) == '\+') {
~ $s_exclude = '+' . substr($s_exclude, 2);
~ }
~ $exclude_args = $exclude_args . " --exclude=\"$s_exclude\"";
~ }
~ $tmp_rsyncArgs = $s_rsyncArgs;
~ if ($b_dryRun) {
~ $tmp_rsyncArgs .= ' -n';
~ }
~ // See if ribs is running on the localhost as the current user,
~ // in which case we don't use ssh, making it possible to run ribs
~ // without a password.
~ if ($a_backupHosts[$s_configName]['ip'] == 'localhost' &&
~ $a_backupHosts[$s_configName]['ssh_user'] ==
get_current_user()) {
~ $tmp_rsyncArgs .= ' ' .
$a_backupHosts[$s_configName]['directories'];
~ }
~ else {
~ $tmp_dirs = preg_replace('/\\\\\s/', '\\\\\\\\ ',
$a_backupHosts[$s_configName]['directories']);
~ $s_port = isset($a_backupHosts[$s_configName]['ssh_port']) ?
~ $a_backupHosts[$s_configName]['ssh_port'] : 22;
~ $tmp_rsyncArgs .= " -e \"ssh -p $s_port\" ";
~ $tmp_rsyncArgs .=
"\"{$a_backupHosts[$s_configName]['ssh_user']}@";
~ $tmp_rsyncArgs .= "{$a_backupHosts[$s_configName]['ip']}:";
~ $tmp_rsyncArgs .= $tmp_dirs . '"';
~ }
~ if (isset($a_backupHosts[$s_configName]['use_hard_links']) &&
~ !$a_backupHosts[$s_configName]['use_hard_links']) {
~ $tmp_rsyncArgs .= " --backup
- --backup-dir=\"$s_fullPath$s_backupType.1\"";
~ }
~ // now we do the actual rsync from the system into the latest
snapshot (notice that
~ // rsync behaves like cp --remove-destination by default, so
the destination
~ // is unlinked first. If it were not so, this would copy over
the other snapshot(s) too!
~ $s_args = "$exclude_args $tmp_rsyncArgs " .
~ "$s_fullPath$s_backupType.0 2>&1";
~ $s_cmd = $s_rsync . ' ' . $s_args;
~ if ($b_debug) {
~ writeln('Executing rsync command: ' . $s_cmd);
~ }
~ $s_log .= `$s_cmd`;
~ if ($b_cleanupDryRun) {
~ rmdir("$s_fullPath$s_backupType.0");
~ }
~ }
~ // else we just rotate the most recent directory of the type below
up to the current
~ // type. This saves space so that there is only one "real"
directory with files. The
~ // other directories are hardlinked directories.
~ else {
~ natsort($a_dirListTypeBelow);
~ $tmp_lastDir = end($a_dirListTypeBelow);
~ if ($tmp_lastDir != '' && is_dir($s_fullPath . $tmp_lastDir)) {
~ if ($b_debug || $b_dryRun) {
~ $tmp_msg = "Performing backup by copying
$s_fullPath$tmp_lastDir to $s_fullPath$s_backupType.0";
~ if ($b_dryRun) {
~ $tmp_msg = str_replace('Performing', 'Would
perform', $tmp_msg);
~ }
~ writeln($tmp_msg);
~ }
~ if (!$b_dryRun) {
~ exec("$s_cp -al $s_fullPath$tmp_lastDir
$s_fullPath$s_backupType.0");
~ }
~ $s_log .= writeln("Performed $s_backupType backup by
copying the last $s_typeBelow up to $s_backupType", true);
~ }
~ else {
~ $tmp_msg = writeln("WARNING: Could not perform
$s_backupType backup because the $s_typeBelow type does not exist.",
true, true);
~ $tmp_msg .= writeln("You must run the $s_typeBelow backup
first or change what type of backup performs the actual");
~ $tmp_msg .= writeln("rsync (\$s_rsyncType is the config
variable to check).", true);
~ $s_log .= $tmp_msg;
~ $b_error = true;
~ continue;
~ }
~ }
~ // }}}
~ // {{{ check for rsync problems
~ // see if there was an error
~ if (stristr($s_log, 'rsync error')) {
~ $tmp_msg = writeln("WARNING: there seems to have been an rsync
error.\nCheck the logs for more information.", true, true);
~ $s_log .= $tmp_msg;
~ $b_error = true;
~ if (isset($a_backupHosts[$s_configName]['post_error_command'])) {
~ $s_cmd =
replaceVars($a_backupHosts[$s_configName]['post_error_command']);
~ $s_log .= "\nPost error command: $s_cmd\n";
~ $s_log .= `($s_cmd) 2>&1`;
~ }
~ }
~ // let the snapshot reflect the current date
~ elseif (is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
~ touch("$s_fullPath$s_backupType.0");
~ if (($s_backupType == 'hourly' || $s_backupType ==
$s_rsyncType) &&
~ !is_link($s_fullPath . 'current')) {
~ symlink("$s_fullPath$s_backupType.0", $s_fullPath .
'current');
~ }
~ if (isset($a_backupHosts[$s_configName]['post_command'])) {
~ $s_cmd =
replaceVars($a_backupHosts[$s_configName]['post_command']);
~ $s_log .= "\nPost command: $s_cmd\n";
~ $s_log .= `($s_cmd) 2>&1`;
~ }
~ // give some extra helpful info
~ if ($s_extraCommands != '') {
~ $s_log .= "\nAdditional information:\n";
~ $s_cmd = replaceVars($s_extraCommands);
~ $s_log .= `($s_cmd) 2>&1`;
~ }
~ }
~ elseif (!$b_dryRun) {
~ $tmp_msg = writeln("WARNING: it looks like the backup for
config '$s_configName' was not successful.", true, true);
~ $tmp_msg .= writeln("The snapshot directory that is supposed
to be created was not created:", true);
~ $tmp_msg .= writeln("$s_fullPath$s_backupType.0", true);
~ $tmp_msg .= writeln("Check the logs for more information.",
true);
~ $s_log .= $tmp_msg;
~ $b_error = true;
~ }
~ // }}}
}
// send off the logs
if ($b_error) {
~ mailAndLog($s_log);
}
else {
~ mailAndLog();
}
exit;
// }}}
// {{{ mailAndLog()
/**
~ * Performs the necessary mailing and logging and exiting of the program
~ *
~ * @param string $in_errorMessage (optional) The error message
~ *
~ * @return void
~ */
function mailAndLog($in_errorMessage = false)
{
~ global $a_backupHosts, $a_backupTypeSettings, $a_configNames,
~ $s_backupType, $s_log, $s_email, $s_logFile,
$s_restartBackupLog,
~ $b_silentOnError, $b_emailOnError, $b_debug, $b_dryRun;
~ $s_log =
"-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n$s_log";
~ $s_log .=
"\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
~ // log the message to file
~ if ($a_backupTypeSettings[$s_backupType]['log'] && $s_logFile !=
'') {
~ // determine file mode
~ $tmp_fileMode = $s_backupType == $s_restartBackupLog ? 'w' : 'a';
~ $fp = @fopen($s_logFile, $tmp_fileMode);
~ if ($fp) {
~ fwrite($fp, $s_log);
~ fclose($fp);
~ if ($b_debug) {
~ writeln('Successfully wrote to logfile: ' . $s_logFile);
~ }
~ }
~ else {
~ $in_errorMessage .= writeln("WARNING: Could not write to
log file: $s_logFile", true, true);
~ $s_log .= $in_errorMessage;
~ }
~ }
~ // e-mail the log if they want it or it's an error
~ if (isset($a_backupTypeSettings[$s_backupType]) &&
~ ($a_backupTypeSettings[$s_backupType]['email'] ||
~ ($b_emailOnError && $in_errorMessage !== false)) &&
~ $s_email != '') {
~ $tmp_error = $in_errorMessage !== false ? ' (WARNING) ' : '';
~ $s_subject = "[ RIBS $tmp_error $s_backupType backups for " .
implode(',', $a_configNames) . " ]";
~ if ($b_debug) {
~ writeln('Emailing backup log to: ' . $s_email);
~ }
~ mail($s_email, $s_subject, $s_log);
~ }
~ if ($in_errorMessage !== false && (!$b_silentOnError || $b_debug)) {
~ echo $in_errorMessage;
~ }
~ if ($b_debug || $b_dryRun) {
~ $tmp_msg = 'Dumping log for this backup:';
~ if ($b_dryRun) {
~ $tmp_msg = str_replace('Dumping log', 'What the log would
be', $tmp_msg);
~ }
~ writeln($tmp_msg);
~ writeln($s_log);
~ }
}
// }}}
// {{{ reInitBackups()
/**
~ * Re-initialize a backup by removing the directory associated with it.
~ *
~ * @access public
~ * @return void
~ */
function reInitBackups()
{
~ global $a_backupHosts, $a_configNames, $s_log, $s_destinationDir,
$s_rm;
~ writeln('Beginning Re-initialization.');
~ foreach ($a_configNames as $s_configName) {
~ // make sure that we are using a valid configuration
~ if (!isset($a_backupHosts[$s_configName])) {
~ writeln("WARNING: The configuation: '$s_configName' is not
valid, skipping it.\n", false, true);
~ continue;
~ }
~ $answer = '';
~ $s_fullPath = $s_destinationDir . $s_configName . '/';
~ writeln ("Are you sure you want to re-initialize
$s_configName?", false, true);
~ echo 'This will completely remove it\'s backup directory and
start from scratch. [y/n]: ';
~ $answer = trim(readline());
~ if ($answer == 'y' || $answer == 'Y') {
~ exec("$s_rm -rf $s_fullPath");
~ writeln('Successfully re-initialized ' . $s_configName .
'.');
~ continue;
~ }
~ elseif ($answer == 'n' || $answer == 'N') {
~ writeln('Skipping re-initialization of ' . $s_configName .
'.');
~ continue;
~ }
~ else {
~ writeln('Aborting re-initialization.');
~ exit;
~ }
~ }
~ writeln('Re-initialization complete.');
}
// }}}
// {{{ showUsage()
/**
~ * Shows the usage message
~ *
~ * @param string $in_file The currently running file name
~ *
~ * @access public
~ * @return void
~ */
function showUsage($in_file)
{
~ $s_usage =
'RIBS version ' . RIBS_VERSION . '
Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>
RIBS (Rsync Incremental Backup System) is a command line PHP script
which will perform incremental backups to a variable number of hosts
(and directories on those hosts).
Usage: ' . $in_file . ' [options] CONFIG_NAME BACKUP_TYPE
Variables:
~ CONFIG_NAME A comma separated list of configurations to run
~ or ALL if you want to run all configurations
~ BACKUP_TYPE The type of backup to run. Can be hourly, daily,
~ weekly, or monthly
Options:
~ -h, --help Show this help message
~ -d, --debug Show debug information
~ -t, --test Do a dry run. Does not transfer any files or move
~ any directories, just shows what would happen.
~ -r, --reinit Re-initialize the backup. Removes any existing
~ backups for the specified CONFIG_NAME so that the
~ backups are started from scratch. WARNING: Any
~ existing data will be erased.
Report any bugs to: jrust at rustyparts.com';
~ writeln($s_usage);
}
// }}}
// {{{ writeln()
/**
~ * Writes a line to standard output
~ *
~ * @param string $in_message The message to write.
~ * @param bool $in_return (optional) Return the message instead of
echoing it?
~ * @param bool $in_pre (optional) Prepend a newline to the string?
~ *
~ * @access public
~ * @return void
~ */
function writeln($in_message, $in_return = false, $in_pre = false)
{
~ if ($in_pre) {
~ $in_message = "\n" . $in_message;
~ }
~ $in_message = $in_message . "\n";
~ if ($in_return) {
~ return $in_message;
~ }
~ else {
~ echo $in_message;
~ }
}
// }}}
// {{{ replaceVars()
/**
~ * Replaces the placeholders %fullPath% and %backupType% with the actual
~ * value.
~ *
~ * @var string $in_cmd The command that has the placeholders
~ *
~ * @return string The command with placeholders replaced
~ */
function replaceVars($in_cmd)
{
~ $in_cmd = str_replace('%fullPath%', $GLOBALS['s_fullPath'], $in_cmd);
~ $in_cmd = str_replace('%backupType%', $GLOBALS['s_backupType'],
$in_cmd);
~ return $in_cmd;
}
// }}}
?>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (MingW32)
iD8DBQFCIu82evoM7FFh31sRAnXwAJwPhI6CsNHjkvH+f04rMomz+XvsFgCfdVKs
K6nbLJTDXVkfbsZyBNx6UVc=
=2ZGS
-----END PGP SIGNATURE-----
-------------- next part --------------
#!/usr/bin/php -q
<?php
//** $Id: ribs 1625 2004-11-24 20:55:48Z jrust $ */
// {{{ version
define('RIBS_VERSION', '2.3');
// }}}
// {{{ description
/**
* RIBS (Rsync Incremental Backup System) by Jason Rust <jrust at rustyaprts.com>
* Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>
* The latest version of this program can be found at
* http://rustyparts.com/scripts.php
*
* License:
*
* This source file is subject to the GNU Public License (GPL),
* that is bundled with this package in the file LICENSE, and is
* available at through the world-wide-web at
* http://www.fsf.org/copyleft/lesser.html
* If you did not receive a copy of the LGPL and are unable to
* obtain it through the world-wide-web, you can get it by writing the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See http://www.gnu.org/copyleft/gpl.html for a copy of the license.
*
* Description:
*
* RIBS is an incremental backup system written in PHP which utilizes
* some common *nix programs (specifically rsync, ssh and cp).
*
* Requirements:
*
* rsync - http://samba.anu.edu.au/rsync/
* cp & rm - http://www.gnu.org/software/fileutils/fileutils.html
* PHP - http://www.php.net/
* basic PEAR libraries (as of version 1.1) - http://pear.php.net/
*
* Use:
*
* See the README that comes with this package.
*/
// }}}
// {{{ user settings
/****** Configuration Section ******/
/**
* This is the place on the server where the backups will end up WITH trailing slash
* @type string
*/
$s_destinationDir = dirname(__FILE__) . '/backups/';
/**
* An array of the different servers to back up, and what directories on them to back up
* The structure is as follows:
* 'backup_name' => array(
* 'enabled' => 'this config enabled? true or false',
* 'ip' => 'server ip address/FQDN',
* 'ssh_user' => 'the ssh user to use for rsync',
* 'ssh_port' => 'the ssh port (optional, defaults to 22)',
* 'directories' => 'backup directories separated by a space WITHOUT a trailing slash (i.e. /home /root)',
* If a directory has a space in it, escape it with a \
* 'excludes' => 'the directories to exclude separated by a space.
* See the README for more information on exclude patterns.'
* 'limits' => 'an array of directory limits that will allow you to override the limits you set in
* $a_backupTypeSettings. Completely optional. e.g. array('hourly' => 2) if you back up
* hourly twice a day.'
* 'use_hard_links'=> 'Whether to do incremental backups using hard links or real files.
* Default option is true. See "Backup Types" in the README for more info.'
* 'post_command' => 'A shell command to run after a successful backup. Replaces %fullPath%
* and %backupType% with the real values.'
* 'post_error_command' => 'Same as post_command, but run when an error occurs.'
* ),
* @type array
*/
$a_backupHosts = array(
'luke' => array(
'enabled' => true,
'ip' => 'mychristiannetwork.com',
'ssh_user' => 'root',
'directories' => '/etc /var/lib/mysql /home',
'excludes' => '',
'limits' => array('hourly' => 24), // because this is backed up every hour
),
'small_host' => array(
'enabled' => false,
'ip' => 'small_host.example.com',
'ssh_user' => 'backup',
'ssh_port' => '9999',
'directories' => '/etc',
'excludes' => '',
),
'big_host' => array(
'enabled' => false,
'ip' => '10.52.1.1',
'ssh_user' => 'root',
'directories' => '/var',
'excludes' => 'mp3/', // exclude any mp3 directories
),
// A test example configuration that can be used with the test
// directory that comes with RIBS.
'example' => array(
'enabled' => false,
'ip' => 'localhost',
'ssh_user' => get_current_user(),
'directories' => dirname(__FILE__) . '/test\ dir',
'use_hard_links'=> true,
// An example of a post_command which will tar up each backup.
// NOTE: this is only useful if use_hard_links is false
// 'post_command' => 'if [ -d %fullPath%%backupType%.1 ]; then tar czv -C %fullPath%%backupType%.1 -f backup.tar.gz .; rm -rf %fullPath%%backupType%.1; mkdir %fullPath%%backupType%.1; mv backup.tar.gz %fullPath%%backupType%.1/; fi',
'excludes' => '/test\ dir/my\ mp3 +/test\ dir/include.mp3 *.mp3 *.svn/ junk/',
// exclude the root mp3 directory,
// any mp3 files anywhere else (except for include.mp3),
// and any .svn/ and junk/ directories
),
);
/**
* Some settings for the four different backup types. For each type you must specify
* 'limit' => of directories to keep. So, if you run every two hours, make hourly 12,
* if every hour then 24, etc. Try to span the entire day. The below setting runs 8
* times a day (every three hours), keeps 7 days, 4 weeks, and 12 months of backups.
* 'email' => whether to send email or not after completion
* 'log' => whether to log after completion
* @type array
*/
$a_backupTypeSettings = array(
'hourly' => array('limit' => 8, 'email' => true, 'log' => true),
'daily' => array('limit' => 7, 'email' => true, 'log' => true),
'weekly' => array('limit' => 4, 'email' => true, 'log' => true),
'monthly' => array('limit' => 12, 'email' => true, 'log' => true),
);
/**
* The email address to send reports to (you can comma separate multiple addresses)
* @type string
*/
$s_email = 'cyo at mychristiannetwork.com';
/**
* The log file.
* @type string
*/
$s_logFile = dirname(__FILE__) . '/ribs.log';
/**
* When to start the log over. Note, this does not mean we rotate the log
* it is just to keep the log file from getting too big. Can be hourly, daily,
* weekly, or monthly
* @type string
*/
$s_restartBackupLog = 'weekly';
/**
* When to do an actual rsync. Otherwise, we just copy the oldest directory
* of the type below to the current type. Example: if doing a monthly backup
* and $s_rsyncType is not 'monthly' then the oldest weekly directory will be
* copied to monthly.0. This keeps down the amount of disk space needed. Usually you
* will want this to be 'hourly'
* @type string
*/
$s_rsyncType = 'hourly';
/**
* Always email if there's an error? This overrides the specific backup setting
* to ensure you will get an email if there is a problem.
* @type bool
*/
$b_emailOnError = true;
/**
* Die quietly? If so, then on error we just shut up, email (see above), and quit,
* otherwise we throw the error.
* @type bool
*/
$b_silentOnError = false;
/**
* Default permissions for when we have to create a new directory
* @type int
*/
$s_defaultDirPerms = 0750;
/**
* Additional comands to be run at the end of each backup (useful for giving
* additional info about file sizes, directories, etc.). Leave empty for nothing.
* %fullPath% is a placeholder that will be filled in with the full path to the current backup.
* @type string
*/
$s_extraCommands = "ls -l %fullPath%; df -h";
/**
* The extra rsync commands to use
* @type string
*/
$s_rsyncArgs = '-arztplv --delete --delete-excluded --stats --timeout=180';
/**
* The path to the rsync command
* @type string
*/
$s_rsync = '/usr/bin/rsync';
/**
* The path to rm
* @var string
*/
$s_rm = '/bin/rm';
/**
* The path to cp
* @type string
*/
$s_cp = '/bin/cp';
/****** End Config. No need to edit anything else! ******/
// }}}
// {{{ readline()
/**
* Reads a line from standard input. Have to put it up here so php loads it in.
*
* @access public
* @return string The string from input
*/
if (!function_exists('readline')) {
function readline () {
$fp = fopen('php://stdin', 'r');
$in = fgets($fp, 4094); // Maximum windows buffer size
fclose ($fp);
return $in;
}
}
// }}}
// {{{ requires
require_once 'Console/Getopt.php';
// }}}
// {{{ grab command line vars
// don't run out of time
set_time_limit(0);
// we want all errors
error_reporting(E_ALL);
$args = Console_Getopt::readPHPArgv();
if (PEAR::isError($args)) {
die('Fatal Error: ' . $args->getMessage() . "\n");
}
$options = Console_Getopt::getopt($args, 'dhtr', array('debug', 'help', 'test', 'reinit'));
if (PEAR::isError($options)) {
die($options->getMessage() . "\n");
}
$b_debug = false;
$b_dryRun = false;
$b_cleanupDryRun = false;
$b_reinit = false;
$s_log = '';
$b_error = false;
foreach ($options[0] as $option) {
switch ($option[0]) {
case 'h':
case '--help':
showUsage($args[0]);
exit;
break;
case 'd':
case '--debug':
$b_debug = true;
break;
case 't':
case '--test':
$b_dryRun = true;
break;
case 'r':
case '--reinit':
$b_reinit = true;
break;
}
}
if (empty($options[1][0])) {
writeln('Error: Configuation not specified' . "\n");
showUsage($args[0]);
exit;
}
// grab the config names from the command line
$a_configNames = explode(',', $options[1][0]);
// ALL is a special keyword to run all backups
if ($a_configNames[0] == 'ALL') {
$a_configNames = array_keys($a_backupHosts);
}
if ($b_reinit) {
reInitBackups();
exit;
}
if (empty($options[1][1])) {
writeln('Error: Backup type not specified' . "\n");
showUsage($args[0]);
exit;
}
// grab the backup type from the command line
$s_backupType = $options[1][1];
if ($b_debug) {
array_shift($args);
writeln('Debug mode is ON.');
writeln('Arguments received: ' . implode(' ', $args));
writeln('Configuration names: ' . implode(',', $a_configNames));
writeln('Backup type: ' . $s_backupType);
}
if ($b_dryRun) {
writeln('Dry run mode is ON.');
}
// }}}
// {{{ set up vars and dirs
// start log
$s_log .= date('F j, Y, g:i a') . ": $s_backupType backups for " . implode(',', $a_configNames) . "\n";
// make sure it's a valid type
if ($s_backupType != 'hourly' &&
$s_backupType != 'daily' &&
$s_backupType != 'weekly' &&
$s_backupType != 'monthly') {
writeln("ERROR: The backup type: '$s_backupType' is not valid (valid types are hourly, daily, weekly, and monthly).");
exit;
}
// check binaries
if (!is_executable($s_rsync)) {
writeln("WARNING: The rsync program: '$s_rsync' is not valid.");
exit;
}
if (!is_executable($s_rm)) {
writeln("WARNING: The rm program: '$s_rm' is not valid.");
exit;
}
if (!is_executable($s_cp)) {
writeln("WARNING: The cp program: '$s_cp' is not valid.");
exit;
}
if (!is_dir($s_destinationDir)) {
mkdir($s_destinationDir, $s_defaultDirPerms);
$s_log .= writeln("Created destination directory: $s_destinationDir", true);
}
// determine the type below the current type
if ($s_backupType == 'daily') {
$s_typeBelow = 'hourly';
}
elseif ($s_backupType == 'weekly') {
$s_typeBelow = 'daily';
}
elseif ($s_backupType == 'monthly') {
$s_typeBelow = 'weekly';
}
else {
$s_typeBelow = 'monthly';
}
// }}}
// {{{ loop through backup configurations
foreach ($a_configNames as $s_configName) {
// make sure that we are using a valid configuration
if (!isset($a_backupHosts[$s_configName])) {
$tmp_msg = writeln("WARNING: The configuation: '$s_configName' is not valid.\n", true, true);
$s_log .= $tmp_msg;
$b_error = true;
continue;
}
else {
$s_log .= writeln("-= Beginning backups for $s_configName\n", true, true);
}
// make sure it's enabled
if (!$a_backupHosts[$s_configName]['enabled']) {
$s_log .= writeln("$s_configName is disabled. Skipping backup.", true);
continue;
}
$s_fullPath = $s_destinationDir . $s_configName . '/';
if (!is_dir($s_fullPath)) {
mkdir($s_fullPath, $s_defaultDirPerms);
$s_log .= writeln("Created directory for $s_configName: $s_fullPath", true);
}
// {{{ rotate the current list of backups if we can
if ($tmp_handle = opendir($s_fullPath)) {
$a_dirList = array();
$a_dirListTypeBelow = array();
while (false !== ($tmp_file = readdir($tmp_handle))) {
if (is_dir($s_fullPath . $tmp_file)) {
if (preg_match(":$s_backupType\.\d+$:", $tmp_file)) {
$a_dirList[] = $tmp_file;
}
// for later we need the directories of the type below
if (preg_match(":$s_typeBelow\.\d+$:", $tmp_file)) {
$a_dirListTypeBelow[] = $tmp_file;
}
}
}
closedir($tmp_handle);
// determine maximum number of directories allowed for this backup
if (isset($a_backupHosts[$s_configName]['limits']) &&
isset($a_backupHosts[$s_configName]['limits'][$s_backupType])) {
$tmp_max = $a_backupHosts[$s_configName]['limits'][$s_backupType];
}
else {
$tmp_max = $a_backupTypeSettings[$s_backupType]['limit'];
}
// If the max is 1 (backing up only once a day) then don't rotate anything
if ($tmp_max > 1) {
// go through directories in reverse order and rotate them
natsort($a_dirList);
$a_dirList = array_reverse($a_dirList);
foreach ($a_dirList as $tmp_dir) {
$tmp_key = preg_replace(':.*\.(\d+)$:', '\\1', $tmp_dir);
// rotate off any of the old backups
if (($tmp_key + 1) >= $tmp_max) {
if ($b_debug || $b_dryRun) {
$tmp_msg = 'Rotating off old backup: ' . $s_fullPath . $tmp_dir;
if ($b_dryRun) {
$tmp_msg = str_replace('Rotating', 'Would rotate', $tmp_msg);
}
writeln($tmp_msg);
}
if (!$b_dryRun) {
// :NOTE: can use System::rm when it supports symlinks in directories
exec("$s_rm -rf $s_fullPath$tmp_dir");
}
}
// otherwise rotate the directory up
else {
$tmp_key++;
$tmp_newDir = preg_replace(':\.\d+$:', ".$tmp_key", $tmp_dir);
if ($b_debug || $b_dryRun) {
$tmp_msg = "Rotating backup directory from $s_fullPath$tmp_dir to $s_fullPath$tmp_newDir";
if ($b_dryRun) {
$tmp_msg = str_replace('Rotating', 'Would rotate', $tmp_msg);
}
writeln($tmp_msg);
}
if (!$b_dryRun) {
rename($s_fullPath . $tmp_dir, $s_fullPath . $tmp_newDir);
}
}
}
}
}
// }}}
// {{{ perform rsync
// this dir shouldn't be here, but check anyhow to make sure we
// don't get recursive directories
if (!$b_dryRun && $tmp_max > 1 && is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
exec("$s_rm -rf $s_fullPath$s_backupType.0");
}
// we only rsync if the type is hourly or on the type is the one they want rsynced
if ($s_backupType == 'hourly' || $s_backupType == $s_rsyncType) {
// make hard-link-only copy of latest directory
if (is_dir("$s_fullPath$s_backupType.1") && !$b_dryRun) {
if (!isset($a_backupHosts[$s_configName]['use_hard_links']) ||
$a_backupHosts[$s_configName]['use_hard_links']) {
exec("$s_cp -al $s_fullPath$s_backupType.1 $s_fullPath$s_backupType.0");
} else {
// When not using the hard-link method of backups we
// want the full backup to be in .0 and any files that have
// been changed to be placed in .1
rename("$s_fullPath$s_backupType.1", "$s_fullPath$s_backupType.0");
mkdir("$s_fullPath$s_backupType.1", $s_defaultDirPerms);
}
}
// directory needs to exist for dry run
if ($b_dryRun && !is_dir("$s_fullPath$s_backupType.0")) {
$b_cleanupDryRun = true;
mkdir("$s_fullPath$s_backupType.0");
}
$tmp_excludes = preg_split('/([^\\\\])\s/', $a_backupHosts[$s_configName]['excludes'], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$exclude_args = '';
for ($i = 0; $i < count($tmp_excludes); $i++) {
$s_exclude = str_replace('\\', '', $tmp_excludes[$i]);
if (isset($tmp_excludes[$i + 1]) && strlen($tmp_excludes[$i + 1]) == 1) {
$s_exclude .= $tmp_excludes[++$i];
}
// check if we want to include rather than exclude
if (substr($s_exclude, 0, 1) == '+') {
$s_exclude = '+ ' . substr($s_exclude, 1);
}
// in this case we want a literal '+'
elseif (substr($s_exclude, 0, 2) == '\+') {
$s_exclude = '+' . substr($s_exclude, 2);
}
$exclude_args = $exclude_args . " --exclude=\"$s_exclude\"";
}
$tmp_rsyncArgs = $s_rsyncArgs;
if ($b_dryRun) {
$tmp_rsyncArgs .= ' -n';
}
// See if ribs is running on the localhost as the current user,
// in which case we don't use ssh, making it possible to run ribs
// without a password.
if ($a_backupHosts[$s_configName]['ip'] == 'localhost' &&
$a_backupHosts[$s_configName]['ssh_user'] == get_current_user()) {
$tmp_rsyncArgs .= ' ' . $a_backupHosts[$s_configName]['directories'];
}
else {
$tmp_dirs = preg_replace('/\\\\\s/', '\\\\\\\\ ', $a_backupHosts[$s_configName]['directories']);
$s_port = isset($a_backupHosts[$s_configName]['ssh_port']) ?
$a_backupHosts[$s_configName]['ssh_port'] : 22;
$tmp_rsyncArgs .= " -e \"ssh -p $s_port\" ";
$tmp_rsyncArgs .= "\"{$a_backupHosts[$s_configName]['ssh_user']}@";
$tmp_rsyncArgs .= "{$a_backupHosts[$s_configName]['ip']}:";
$tmp_rsyncArgs .= $tmp_dirs . '"';
}
if (isset($a_backupHosts[$s_configName]['use_hard_links']) &&
!$a_backupHosts[$s_configName]['use_hard_links']) {
$tmp_rsyncArgs .= " --backup --backup-dir=\"$s_fullPath$s_backupType.1\"";
}
// now we do the actual rsync from the system into the latest snapshot (notice that
// rsync behaves like cp --remove-destination by default, so the destination
// is unlinked first. If it were not so, this would copy over the other snapshot(s) too!
$s_args = "$exclude_args $tmp_rsyncArgs " .
"$s_fullPath$s_backupType.0 2>&1";
$s_cmd = $s_rsync . ' ' . $s_args;
if ($b_debug) {
writeln('Executing rsync command: ' . $s_cmd);
}
$s_log .= `$s_cmd`;
if ($b_cleanupDryRun) {
rmdir("$s_fullPath$s_backupType.0");
}
}
// else we just rotate the most recent directory of the type below up to the current
// type. This saves space so that there is only one "real" directory with files. The
// other directories are hardlinked directories.
else {
natsort($a_dirListTypeBelow);
$tmp_lastDir = end($a_dirListTypeBelow);
if ($tmp_lastDir != '' && is_dir($s_fullPath . $tmp_lastDir)) {
if ($b_debug || $b_dryRun) {
$tmp_msg = "Performing backup by copying $s_fullPath$tmp_lastDir to $s_fullPath$s_backupType.0";
if ($b_dryRun) {
$tmp_msg = str_replace('Performing', 'Would perform', $tmp_msg);
}
writeln($tmp_msg);
}
if (!$b_dryRun) {
exec("$s_cp -al $s_fullPath$tmp_lastDir $s_fullPath$s_backupType.0");
}
$s_log .= writeln("Performed $s_backupType backup by copying the last $s_typeBelow up to $s_backupType", true);
}
else {
$tmp_msg = writeln("WARNING: Could not perform $s_backupType backup because the $s_typeBelow type does not exist.", true, true);
$tmp_msg .= writeln("You must run the $s_typeBelow backup first or change what type of backup performs the actual");
$tmp_msg .= writeln("rsync (\$s_rsyncType is the config variable to check).", true);
$s_log .= $tmp_msg;
$b_error = true;
continue;
}
}
// }}}
// {{{ check for rsync problems
// see if there was an error
if (stristr($s_log, 'rsync error')) {
$tmp_msg = writeln("WARNING: there seems to have been an rsync error.\nCheck the logs for more information.", true, true);
$s_log .= $tmp_msg;
$b_error = true;
if (isset($a_backupHosts[$s_configName]['post_error_command'])) {
$s_cmd = replaceVars($a_backupHosts[$s_configName]['post_error_command']);
$s_log .= "\nPost error command: $s_cmd\n";
$s_log .= `($s_cmd) 2>&1`;
}
}
// let the snapshot reflect the current date
elseif (is_dir("$s_fullPath$s_backupType.0") && !$b_dryRun) {
touch("$s_fullPath$s_backupType.0");
if (($s_backupType == 'hourly' || $s_backupType == $s_rsyncType) &&
!is_link($s_fullPath . 'current')) {
symlink("$s_fullPath$s_backupType.0", $s_fullPath . 'current');
}
if (isset($a_backupHosts[$s_configName]['post_command'])) {
$s_cmd = replaceVars($a_backupHosts[$s_configName]['post_command']);
$s_log .= "\nPost command: $s_cmd\n";
$s_log .= `($s_cmd) 2>&1`;
}
// give some extra helpful info
if ($s_extraCommands != '') {
$s_log .= "\nAdditional information:\n";
$s_cmd = replaceVars($s_extraCommands);
$s_log .= `($s_cmd) 2>&1`;
}
}
elseif (!$b_dryRun) {
$tmp_msg = writeln("WARNING: it looks like the backup for config '$s_configName' was not successful.", true, true);
$tmp_msg .= writeln("The snapshot directory that is supposed to be created was not created:", true);
$tmp_msg .= writeln("$s_fullPath$s_backupType.0", true);
$tmp_msg .= writeln("Check the logs for more information.", true);
$s_log .= $tmp_msg;
$b_error = true;
}
// }}}
}
// send off the logs
if ($b_error) {
mailAndLog($s_log);
}
else {
mailAndLog();
}
exit;
// }}}
// {{{ mailAndLog()
/**
* Performs the necessary mailing and logging and exiting of the program
*
* @param string $in_errorMessage (optional) The error message
*
* @return void
*/
function mailAndLog($in_errorMessage = false)
{
global $a_backupHosts, $a_backupTypeSettings, $a_configNames,
$s_backupType, $s_log, $s_email, $s_logFile, $s_restartBackupLog,
$b_silentOnError, $b_emailOnError, $b_debug, $b_dryRun;
$s_log = "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n$s_log";
$s_log .= "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
// log the message to file
if ($a_backupTypeSettings[$s_backupType]['log'] && $s_logFile != '') {
// determine file mode
$tmp_fileMode = $s_backupType == $s_restartBackupLog ? 'w' : 'a';
$fp = @fopen($s_logFile, $tmp_fileMode);
if ($fp) {
fwrite($fp, $s_log);
fclose($fp);
if ($b_debug) {
writeln('Successfully wrote to logfile: ' . $s_logFile);
}
}
else {
$in_errorMessage .= writeln("WARNING: Could not write to log file: $s_logFile", true, true);
$s_log .= $in_errorMessage;
}
}
// e-mail the log if they want it or it's an error
if (isset($a_backupTypeSettings[$s_backupType]) &&
($a_backupTypeSettings[$s_backupType]['email'] ||
($b_emailOnError && $in_errorMessage !== false)) &&
$s_email != '') {
$tmp_error = $in_errorMessage !== false ? ' (WARNING) ' : '';
$s_subject = "[ RIBS $tmp_error $s_backupType backups for " . implode(',', $a_configNames) . " ]";
if ($b_debug) {
writeln('Emailing backup log to: ' . $s_email);
}
mail($s_email, $s_subject, $s_log);
}
if ($in_errorMessage !== false && (!$b_silentOnError || $b_debug)) {
echo $in_errorMessage;
}
if ($b_debug || $b_dryRun) {
$tmp_msg = 'Dumping log for this backup:';
if ($b_dryRun) {
$tmp_msg = str_replace('Dumping log', 'What the log would be', $tmp_msg);
}
writeln($tmp_msg);
writeln($s_log);
}
}
// }}}
// {{{ reInitBackups()
/**
* Re-initialize a backup by removing the directory associated with it.
*
* @access public
* @return void
*/
function reInitBackups()
{
global $a_backupHosts, $a_configNames, $s_log, $s_destinationDir, $s_rm;
writeln('Beginning Re-initialization.');
foreach ($a_configNames as $s_configName) {
// make sure that we are using a valid configuration
if (!isset($a_backupHosts[$s_configName])) {
writeln("WARNING: The configuation: '$s_configName' is not valid, skipping it.\n", false, true);
continue;
}
$answer = '';
$s_fullPath = $s_destinationDir . $s_configName . '/';
writeln ("Are you sure you want to re-initialize $s_configName?", false, true);
echo 'This will completely remove it\'s backup directory and start from scratch. [y/n]: ';
$answer = trim(readline());
if ($answer == 'y' || $answer == 'Y') {
exec("$s_rm -rf $s_fullPath");
writeln('Successfully re-initialized ' . $s_configName . '.');
continue;
}
elseif ($answer == 'n' || $answer == 'N') {
writeln('Skipping re-initialization of ' . $s_configName . '.');
continue;
}
else {
writeln('Aborting re-initialization.');
exit;
}
}
writeln('Re-initialization complete.');
}
// }}}
// {{{ showUsage()
/**
* Shows the usage message
*
* @param string $in_file The currently running file name
*
* @access public
* @return void
*/
function showUsage($in_file)
{
$s_usage =
'RIBS version ' . RIBS_VERSION . '
Copyright (c) 2002-2003 Jason Rust <jrust at rustyparts.com>
RIBS (Rsync Incremental Backup System) is a command line PHP script
which will perform incremental backups to a variable number of hosts
(and directories on those hosts).
Usage: ' . $in_file . ' [options] CONFIG_NAME BACKUP_TYPE
Variables:
CONFIG_NAME A comma separated list of configurations to run
or ALL if you want to run all configurations
BACKUP_TYPE The type of backup to run. Can be hourly, daily,
weekly, or monthly
Options:
-h, --help Show this help message
-d, --debug Show debug information
-t, --test Do a dry run. Does not transfer any files or move
any directories, just shows what would happen.
-r, --reinit Re-initialize the backup. Removes any existing
backups for the specified CONFIG_NAME so that the
backups are started from scratch. WARNING: Any
existing data will be erased.
Report any bugs to: jrust at rustyparts.com';
writeln($s_usage);
}
// }}}
// {{{ writeln()
/**
* Writes a line to standard output
*
* @param string $in_message The message to write.
* @param bool $in_return (optional) Return the message instead of echoing it?
* @param bool $in_pre (optional) Prepend a newline to the string?
*
* @access public
* @return void
*/
function writeln($in_message, $in_return = false, $in_pre = false)
{
if ($in_pre) {
$in_message = "\n" . $in_message;
}
$in_message = $in_message . "\n";
if ($in_return) {
return $in_message;
}
else {
echo $in_message;
}
}
// }}}
// {{{ replaceVars()
/**
* Replaces the placeholders %fullPath% and %backupType% with the actual
* value.
*
* @var string $in_cmd The command that has the placeholders
*
* @return string The command with placeholders replaced
*/
function replaceVars($in_cmd)
{
$in_cmd = str_replace('%fullPath%', $GLOBALS['s_fullPath'], $in_cmd);
$in_cmd = str_replace('%backupType%', $GLOBALS['s_backupType'], $in_cmd);
return $in_cmd;
}
// }}}
?>
More information about the rsync
mailing list