前些天Linode的Fremont机房出现电源事故,造成一些服务器数据丢失,给一些人造成不小的影响。 以前自己也写过简单的在线备份脚本,但功能太弱,所以后来又写了个复杂点的。这里共享现在用的服务器备份脚本,希望对大家有用。该脚本特点如下:
- 用php开发
- 通过sftp把指定的目录和数据库备份到指定服务器
- 备份目录时会检测目录内文件的最后修改日期,如果和上次备份一样就不会再备份
- 每个备份文件都加日期戳,备份文件名是www.20101120.tar.gz 这种。
- 备份目录时可指定剔除的文件名
- 可以设定一个备份文件的保留时间,超过这个时间就删除。还可以设定一个备份文件保留的最少分数,如果一个数据库或一个目录的备份少于这个份数,超过设定时间也不会删除。
代码遵守GPL协议,请在该协议基础上随便使用。
因为当时是自用,而且对php不熟,所以很多地方写得并不严谨,欢迎指出。
下载链接是: http://www.doyj.com/wp-content/uploads/2010/11/phpbk.zip
下面是备份文件中最主要的phpbk.php的代码
[coolcode lang=”php”]
.
*/
require_once(“sftp.php”);
define(‘BKFTP_SERVER’,’www.bkserver.com’); //backup server address
define(‘BKFTP_USER’,’bkuser’); //backup server user name
define(‘BKFTP_PASS’,’password’); //backup server password
define(‘BKFTP_PORT’, 22); //backup server port
define(‘BKFTP_KEEPDAYS’,’60’); //backup files keep days
define(‘BKFTP_MAXBKCOUNT’,’4′); //定义备份保留的最大个数, 当一个备份文件已经超过了设定的保留最大天数,但这个备份的数量少于最大备份个数时不删除此文件
function load_array_dump($filename)
{
return unserialize(file_get_contents($filename));
}
//first char of filepath must be “/”.
function get_bkpath( $filepath )
{
return ‘/home/’.BKFTP_USER.$filepath ; //need be changed according your server configuration. 这段是因为备份服务器的目录结构是 /home/用户名,请根据自己服务器器的情况修改,
}
function save_array_dump($filename, $array)
{
$fp = fopen($filename, ‘w+’) or die(“I could not open $filename.”);
fwrite($fp, serialize($array));
fclose($fp);
}
function nftpupload( $srcfile, $dest )
{
try
{
$ftp_dir=$dest;
$filename = end(explode(‘/’,$srcfile )) ;
//build a fully qualified (FTP) path name where the file will reside
$destination_file= get_bkpath( $ftp_dir.$filename ) ;
$sftp = new SFTPConnection(BKFTP_SERVER, BKFTP_PORT);
$sftp->login(BKFTP_USER, BKFTP_PASS);
$sftp->uploadFile($srcfile, $destination_file);
}
catch (Exception $e)
{
echo $e->getMessage() . “\n”;
}
}
function IsFTPFileExist( $ftpfile )
{
try
{
$sftp = new SFTPConnection(BKFTP_SERVER, BKFTP_PORT);
$sftp->login(BKFTP_USER, BKFTP_PASS);
$filelist = $sftp->scanFilesystem( get_bkpath( dirname( $ftpfile ) ) );
return in_array( basename( $ftpfile ) , $filelist ) ;
}
catch (Exception $e)
{
echo $e->getMessage() . “\n”;
}
return false ;
}
function DeleteNDaysAgoInDirFTPFile( $ftpdir , $days )
{
try
{
$sftp = new SFTPConnection(BKFTP_SERVER, BKFTP_PORT);
$sftp->login(BKFTP_USER, BKFTP_PASS);
$filelist = $sftp->scanFilesystem( get_bkpath( $ftpdir ) );
$ic = count( $filelist ) ;
$afiles = array() ;
for( $i = 0 ; $i < $ic ; $i++ )
{
$afn = explode('.',$filelist[ $i ] ) ;
if( count( $afn ) != 4 )
continue ;
if( strlen( $afn[ 1 ] ) != 8 )
continue ;
if( !isset( $afiles[ $afn[ 0 ] ] ) )
$afiles[ $afn[ 0 ] ] = array() ;
$afiles[ $afn[ 0 ] ][ $afn[ 1 ] ] = $filelist[ $i ] ;
}
$ic = count( $afiles ) ;
foreach ( $afiles as $key => $value )
{
$jc = count( $value ) ;
if( $jc < BKFTP_MAXBKCOUNT )
continue ;
ksort( $value ) ;
foreach( $value as $date => $filepath )
{
$bktime = strtotime( substr( $date , 0 , 4 ).’-‘.substr( $date , 4 , 2 ).’-‘.substr( $date , 6 , 2 ).’ 00:00:00′ ) ;
if( $bktime == false )
continue ;
$diff = time() – $bktime ;
if( $diff > 86400 * $days )
{
$jc– ;
$sftp->deleteFile( get_bkpath( $ftpdir.$filepath ) ) ;
if( $jc < BKFTP_MAXBKCOUNT )
break ;
}
}
}
}
catch (Exception $e)
{
echo $e->getMessage() . “\n”;
}
}
//获取一个目录下所有文件最晚的最后修改时间
function GetPathLastMTime( $filepath , $aexcludefiles )
{
if (is_dir($filepath))
{
$lmtime = strtotime( ‘2000-01-01 00:00:00’ ) ;
if ($dh = opendir($filepath))
{
while (($file = readdir($dh)) !== false)
{
if( $file == ‘.’ || $file == ‘..’ )
continue ;
if( in_array( $file , $aexcludefiles ) )
continue ;
if( is_link( $filepath.”/”.$file ) )
continue ;
$lt1 = GetPathLastMTime( $filepath.”/”.$file , $aexcludefiles ) ;
if( $lt1 > $lmtime )
$lmtime = $lt1 ;
}
closedir($dh);
}
return $lmtime ;
}
else
return filemtime( $filepath ) ;
}
$bkdatafile = ‘/backup/bkdata.dat’ ; //备份时用到的数据文件,会在这里保存目录的最后修改时间,用来在下次备份中做比对,看是否有被修改
$bkdatdir = ‘/backup/tmp’ ; //备份用到的临时目录
$bklogfile = ‘/backup/bk.log’ ; //备份日志文件
$tarexclude = ‘/backup/tarexclude.cfg’ ; //备份tar目录时需要剔除的文件名列表,会把日志,缓存等文件剔除掉
$dbbkftppath = ‘/backup/db/’ ; //数据库备份到备份服务器上的目录名
$filebkftppath = ‘/backup/’ ; //目录备份到备份服务器上的目录名
DeleteNDaysAgoInDirFTPFile( $dbbkftppath , BKFTP_KEEPDAYS ) ;
DeleteNDaysAgoInDirFTPFile( $filebkftppath , BKFTP_KEEPDAYS ) ;
$aexcludefiles = array( ‘logs’ , ‘nobk’ , ‘cache’ , ‘.log.gz’ , ‘.log’ ) ;
$abkdb = array( array( ‘user’ => ‘dbadmin1’ , ‘pin’ => ‘dbpassword1’ , ‘dbname’ => ‘db1’ ) , //这个数组里放入要备份的数据库列表,用户名,口令,数据库名
array( ‘user’ => ‘dbadmin2’ , ‘pin’ => ‘dbpassword2’ , ‘dbname’ => ‘db2’ ) ,
array( ‘user’ => ‘dbadmin3’ , ‘pin’ => ‘dbpassword3’ , ‘dbname’ => ‘db3’ ) ,
) ;
$abkdir = array( ‘/home/user’ , ‘/var/www’ , ‘/etc’ ) ; //在这个数组里放入要备份的目录路径
$bklog = fopen( $bklogfile, ‘a’);
if( file_exists( $bkdatafile ) )
{
$aLastBkTime = load_array_dump( $bkdatafile ) ;
}
else
{
$ic = count( $abkdir ) ;
for( $i = 0 ; $i < $ic ; $i++ )
{
$aLastBkTime[ $abkdir[ $i ] ] = strtotime( '2000-01-01 00:00:00' ) ;
}
}
//备份数据库
$ic = count( $abkdb ) ;
for( $i = 0 ; $i < $ic ; $i++ )
{
$sqlfile = $bkdatdir.$abkdb[ $i ][ 'dbname' ].'.'.gmstrftime ('%Y%m%d', time()).'.sql' ;
$gzsqlfile = $sqlfile.'.gz' ;
$ftpfile = $dbbkftppath.basename( $gzsqlfile ) ;
if( IsFTPFileExist( $ftpfile ) )
{
$smsg = gmstrftime ('%b %d %Y %H:%M:%S', time()).' ftp file exist '.$ftpfile."\n" ;
fwrite($bklog, $smsg);
echo $smsg ;
continue ;
}
$cmd = '/usr/bin/mysqldump -u'.$abkdb[ $i ][ 'user' ].' -p'.$abkdb[ $i ][ 'pin' ].' -f '.$abkdb[ $i ][ 'dbname' ].'>‘.$sqlfile ;
$sr = shell_exec( $cmd ) ;
$smsg = gmstrftime (‘%b %d %Y %H:%M:%S’, time()).’ backup database ‘.$abkdb[ $i ][ ‘dbname’ ].” $sr\n” ;
fwrite($bklog, $smsg);
echo $smsg ;
$cmd = ‘/bin/gzip -9 -f ‘.$sqlfile ;
$sr = shell_exec( $cmd ) ;
$smsg = gmstrftime (‘%b %d %Y %H:%M:%S’, time()).’ compress sql file ‘.$sqlfile.” $sr\n” ;
fwrite($bklog, $smsg );
echo $smsg ;
nftpupload( $gzsqlfile , $dbbkftppath ) ;
$smsg = gmstrftime (‘%b %d %Y %H:%M:%S’, time()).’ ftp upload file ‘.$gzsqlfile.’ to ‘.$dbbkftppath.”\n” ;
fwrite($bklog, $smsg );
echo $smsg ;
unlink( $gzsqlfile ) ;
}
//备份文件
$ic = count( $abkdir ) ;
for( $i = 0 ; $i < $ic ; $i++ )
{
$tarfile = $bkdatdir.end(explode('/',$abkdir[ $i ] )).'.'.gmstrftime ('%Y%m%d', time()).'.tar.gz' ;
$ftpfile = $filebkftppath.basename( $tarfile ) ;
if( IsFTPFileExist( $ftpfile ) )
{
$aLastBkTime[ $abkdir[ $i ] ] = time() ;
$smsg = gmstrftime ('%b %d %Y %H:%M:%S', time()).' ftp file exist '.$ftpfile."\n" ;
fwrite($bklog, $smsg);
echo $smsg ;
continue ;
}
$smsg = gmstrftime ('%b %d %Y %H:%M:%S', time()).' check last update time '.$abkdir[ $i ]."\n" ;
fwrite($bklog, $smsg );
echo $smsg ;
$lmtime = GetPathLastMTime( $abkdir[ $i ] , $aexcludefiles ) ;
if( $lmtime <= $aLastBkTime[ $abkdir[ $i ] ] )
{
$smsg = gmstrftime ('%b %d %Y %H:%M:%S', time()).' file is not changed '.$abkdir[ $i ]."\n" ;
fwrite($bklog, $smsg );
echo $smsg ;
continue ;
}
//备份文件
$cmd = 'tar -czf '.$tarfile.' --exclude-caches --exclude-from='.$tarexclude.' '.$abkdir[ $i ] ;
$sr = shell_exec( $cmd ) ;
$smsg = gmstrftime ('%b %d %Y %H:%M:%S', time()).' compress file '.$tarfile." $sr\n" ;
fwrite($bklog, $smsg );
echo $smsg ;
nftpupload( $tarfile , $filebkftppath ) ;
$smsg = gmstrftime ('%b %d %Y %H:%M:%S', time()).' ftp upload file '.$tarfile.' to '.$filebkftppath."\n" ;
fwrite($bklog, $smsg );
echo $smsg ;
$aLastBkTime[ $abkdir[ $i ] ] = time() ;
unlink( $tarfile ) ;
}
save_array_dump( $bkdatafile , $aLastBkTime ) ;
?>
[/coolcode]
下面是sftp.php的内容
[coolcode lang=”php”]
//Need libssh2 libssh2-php, PECL ssh2 >= 0.9.0
class SFTPConnection
{
private $connection;
private $sftp;
public function __construct($host, $port=22)
{
$this->connection = @ssh2_connect($host, $port);
if (! $this->connection)
throw new Exception(“Could not connect to $host on port $port.”);
}
public function login($username, $password)
{
if (! @ssh2_auth_password($this->connection, $username, $password))
throw new Exception(“Could not authenticate with username $username ” . “and password $password.”);
$this->sftp = @ssh2_sftp($this->connection);
if (! $this->sftp)
throw new Exception(“Could not initialize SFTP subsystem.”);
}
public function uploadFile($local_file, $remote_file)
{
$sconn = $this->connection;
ssh2_scp_send ( $sconn , $local_file , $remote_file ) ;
/*$sftp = $this->sftp;
$stream = @fopen(“ssh2.sftp://$sftp$remote_file”, ‘w’);
if (! $stream)
throw new Exception(“Could not open file: $remote_file”);
$data_to_send = @file_get_contents($local_file);
if ($data_to_send === false)
throw new Exception(“Could not open local file: $local_file.”);
if (@fwrite($stream, $data_to_send) === false)
throw new Exception(“Could not send data from file: $local_file.”);
@fclose($stream);*/
}
function scanFilesystem($remote_file) {
$sftp = $this->sftp;
$dir = “ssh2.sftp://$sftp$remote_file”;
$tempArray = array();
$handle = opendir($dir);
// List all the files
while (false !== ($file = readdir($handle))) {
if (substr(“$file”, 0, 1) != “.”){
if(is_dir($file)){
// $tempArray[$file] = $this->scanFilesystem(“$dir/$file”);
} else {
$tempArray[]=$file;
}
}
}
closedir($handle);
return $tempArray;
}
public function receiveFile($remote_file, $local_file)
{
ssh2_scp_recv( $this->$connection , $remote_file , $local_file ) ;
/*$sftp = $this->sftp;
$stream = @fopen(“ssh2.sftp://$sftp$remote_file”, ‘r’);
if (! $stream)
throw new Exception(“Could not open file: $remote_file”);
$contents = fread($stream, filesize(“ssh2.sftp://$sftp$remote_file”));
file_put_contents ($local_file, $contents);
@fclose($stream);*/
}
public function deleteFile($remote_file){
$sftp = $this->sftp;
unlink(“ssh2.sftp://$sftp$remote_file”);
}
}
?>[/coolcode]
代码运行环境是php 5, mysql 5, 要安装libssh2-php, 没在其他环境下测试过。现在用cron每天定期运行该脚本,曾有次误删网站根目录全部内容,幸亏有前一天的备份才得以立刻恢复。照理应该再写个对应的恢复脚本,但一直犯懒没写,想等着服务器出问题再说,希望不会有这样的机会。
欢迎大家使用,有啥意见请多交流, 联系方式写在了代码中。