January 26, 2004
Every blog starts out with a single post. Over time, it grows both in volume and as a history of thought and events. Before long, it becomes a collective work which should be preserved. Many tremble at the idea of losing all or even just a portion of one's posts, comments and trackbacks.

Such an event is rare, but it does happen. It happened to me not that long ago. Fortunately, I had made a backup about a week prior to the server crash. I also used Google's caching feature to capture the content of the missing week. I believe I only lost one post and about one week's worth of comments (translation: roughly 3 comments). Not too bad, but it inspired me to do better. I found a backup script and modified it to meet my own needs. Now, a complete backup of my MovableType database is e-mailed to me each evening.

In case there might be some other folks also interested in having a 'safety-net', this post contains both the script and installation instructions.

Please note that this script backs up a MySQL database. If you are using MT but not MySQL, this will not help you. Also note that if you use any other blogging software that does use MySQL, then this script will also work for you. Finally, your server must have both php support and cron for this to work.

Here are step by step instructions on creating an automated backup:

Step 1: Create a File Containing the Script

Copy the following script, paste it into a text editor (notepad, ultraedit, etc.) and save it as backup_db.php:
<?php

// mySQL backup & mail v1.02
// created by: Justin O. Kirk
// 
// copyrighted © 2001-2002 Justin Kirk

// Enhancements added by: King of Fools
// 
// http://www.king-of-fools.com
// October, 2003
// { Added compression option }
// { Added datestamp option }


// Run Script As:
// php -q /home/yourusername/locationofscript/backup_db.php 
//
// * Must be run using ssh or cron
// * Browser execution will fail
// * Take appropriate security precautions
// (Your DB Password is in this file)


//*** BEGIN variables ***//

// Backup Time Variables
  $backupdate = date("F j, Y");
  $backuptime = date("H:i");

//*** Edit Below ***//

// ==========================
// MySQL DATABASE INFORMATION
// ==========================

  // database host: (blank if localhost)
  $dbhost = '';
  
  // name of the database:
  $database = 'dbname';   
  
  // username with db access:
  $username = 'dbuser';
  
  // password for username:
  $password = 'dbpassword';

// ==============================
// BACKUP FILENAME & LOCATION
// ==============================

  // filename for the backup: (no extension)
  $backupas = "mysql";
  
  // absolute path to backup destination: (no trailing slash)
  $backupto = '/home/username/tmp';

// ===================
// E-MAIL INFORMATION:
// ===================

  // who the mail is from.
  $mailname = 'autobackup';
  
  // reply address (not needed)
  $mailfrom = '';
  
  // email address to send the database to.
  $mailto = 'you@yourdomain.com';
  
  // subject of email.
  $subject = "MySQL Backup - $backupdate";
  
  // message body.
  $message = "Backup Created at $backuptime on $backdate:";

// ========
// OPTIONS:
// ========

  $send_email       = '1';
  // 1=send backup copy via e-mail.     0= backup data, don't e-mail.
  
  $delete_local     = '1';
  // 1=delete local copy when done.     0=leave local copy when done.
  
  $compress_archive = '1';
  // 1=compress archive file with gzip. 0=I like large emails.
  
  $date_stamp       = '1';
  // 1=add datestamp to archive file.   0=Always use same backup name

//*** END of variables. ***//

//*************************************//
//*** Do Not Edit Beyond This Point ***//
//*************************************//
// Resolve Archive Target:
  if ($date_stamp) { $backupas = $backupas.'_'.date("Ymd"); }
  $backupfile = $backupas.'.sql';

// Call Functions
  backupdb();
  if ($compress_archive) { compressdb(); }
  if ($send_email)       { makeandsend(); }
  if ($delete_local)     { removedb(); }


// Functions
function backupdb() {
  global $dbhost,$username,$password,$database,$backupto,$backupfile;
  if($dbhost == "") {
    $backupcommand = "mysqldump -u$username -p$password $database >$backupto/$backupfile";
  } else {
    $backupcommand = "mysqldump -h$dbhost  -u$username -p$password $database >$backupto/$backupfile";
  }
  passthru ("$backupcommand", $error);
  if($error) { echo ("Dump Problem: $errorn"); exit;}
}

function compressdb() {
  global $backupto,$backupfile;
  $compresscmd = "cd $backupto; gzip -f $backupfile";
  passthru ("$compresscmd", $error);
  if($error) { echo ("GZip Problem: $errorn"); exit; }
  $backupfile = $backupfile.'.gz';
}

function removedb() {
  global $backupto,$backupfile;
  if(!unlink("$backupto/$backupfile")) { echo ("Cannot Remove $backupto/$backupas"); exit;}
}

function makeandsend() {
  global $backupto,$backupfile,$message,$mailto,$subject,$mailname,$mailfrom,$database;
  $mail_boundary = '--=nextpart_' . md5(uniqid(time()));
  $mail_head = "From: $mailnamernReply-to: $mailfromrn";
  $mail_head .= "MIME-Version: 1.0rn";
  $mail_head .= "Content-type: multipart/mixed; boundary="$mail_boundary"";
  $mail_head .= "rnrn";
  $mail_head .= "This is a multi-part message in MIME format.";
  $mail_head .= "rnrn";

  $db_file = $backupto.'/'.$backupfile;
  $fp = fopen($db_file, "r");
  $file = fread($fp, filesize($db_file));
  $file = chunk_split(base64_encode($file));

  $mail_body = "--$mail_boundaryrn";
  $mail_body .= "Content-type: text/plain; charset=us-asciirn";
  $mail_body .= "Content-transfer-encoding: 8bitrnrn";
  $mail_body .= " $messagern";
  $mail_body .= "--$mail_boundaryrn";

  $filename = basename($db_file);

  $mail_body .= "Content-type: application/octet-stream; name="$filename"rn";
  $mail_body .= "Content-transfer-encoding:base64rn";
  $mail_body .= "Content-Disposition: attachment; filename="$filename"rnrn";
  $mail_body .= $file. "rnrn";
  $mail_body .= "--$mail_boundary--";

  mail($mailto, $subject, $mail_body, $mail_head);
}

?>

Step 2: Modify the Script Variables

The variables define how to access the database and also where to mail the backup. The most important variables are as follows:
  • $dbhost Specifies the database host. It can be left blank if the database is hosted on your own domain. This value should be in the DBHost line in your mt.cfg. If there is no DBHost line, then it should be left blank.
  • $database Specifies the database name. This value is required and should be in the Database line in your mt.cfg.
  • $username Specifies the user with access to the database. This value is required and should be in the DBUser line in your mt.cfg.
  • $password Specifies the password for the username. This value is required and should be in your mt-db-pass.cgi.
  • $backupto Specifies the location where the backup should be created. This value is required. Running mt-check.cgi should give you a clue as to what the path structure of your site is. I would suggest placing the database in a directory called tmp just of the public_html.
  • $mailto Specifies the location where the backup should be sent. This value is required if $sendemail is set to '1'. It should contain the e-mail address you want the backup e-mailed to.
  • $send_email Determines if the backup should be e-mailed or not. '1'=send e-mail, '0'=do not send.
  • $delete_local Determines if the local copy should be deleted after it is e-mailed. '1'=delete local copy; '0'=leave local copy. This option is a good way to save space, but if you are not e-mailing the backup anywhere, then you do not want to delete the local copy.
  • $compress_archive Determines if the backup should compressed or not. '1'=compress backup; '0'=no compression. If your server does not have gzip installed, this must be set to '0'.
  • $date_stamp Determines if the backup filename should have a date-stamp in it. '1'=use date in filename; '0'=standard backup name.

Step 3: Load the Script onto Your Server

The script needs to be loaded onto your system. Because this script does contain your MySQL database password, you should put it in a non-public directory.

Step 4: Create a Cron Job for the Backup

A 'cron job' is a script which the server executes based on a schedule. Hopefully your host has a nice control panel based cron manager. If not, then you need to read up on the crontab command. My job is defined as follows:
30 23 * * * php -q /home/userdir/scripts/backup_db.php > /dev/null
The first five numbers define the minute, hour, day-of-month, month-of-year, day-of-week. The remainder of the definition specifies the shell command to execute. An astrisk means all values are permitted. Thus, my backup is performed at 11:30pm every evening.

Other Notes

I had some difficulty getting this script to work properly on a site hosted by 1and1. I finally got it functional using the following workaround:

I had to make a shell script which called the backup script. Here it is:

PATH=/usr/local/bin:/usr/bin:/usr/X11R6/bin:/bin:/usr/local/msql/bin
export PATH
cd /homepages/99/d99999999/htdocs/cron

echo "Executing Backup Script..."
echo "==========================n"
php4 -q backup_db.php
echo "n==========================n"
echo "Backup Script Complete!"
If you use this script, the path specified in the cd command will require modification. The cron command I used for this script is as follows:
50 23 * * * sh /homepages/99/d99999999/htdocs/cron/backup.sh > /dev/null
Again, the path is generic and would require modification.
The only part left to cover is how to use this backup file to recover from a blogtastrophe. That, I'm afraid, will have to be the subject of some future post.

If you want automatic backups but this looks impossible to pull off by yourself, I would be willing to set it up for you (for a small fee). Contact me via e-mail if you are interested.

Categories
Archives
July 2009
S M T W T F S
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Complete Archives

Tools
Search:
  Advanced Search

Mailing List:



Currently Reading
Recently Read
Animal Farm

Animal Farm
George Orwell

Life of Pi

Life of Pi
Yann Martel

The Fourth K

The Fourth K
Mario Puzo

Catch 22

Catch 22
Joseph Heller

the Sicilian

the Sicilian
Mario Puzo

The Quantum Rose

The Quantum Rose
Catherine Asaro

Members
Sponsors
Blogroll
Links
Stats
Entries: 2147
Comments: 2925
Trackbacks: 665
Members: 92

Most Recent:
  Entry: 11/09/08 9:38
  Comment: 11/17/08 12:27
  Visitor: 07/04/09 7:56

Powered by:
  ExpressionEngine

Extreme Tracking