background image

Content tagged with: backup

Eric's picture

In this tutorial, I'll show how you can use awk, grep, and sed (my favorite command line tools) to backup and archive your MySQL databases. This can be useful to schedule a cron job, transfer your databases to another server, or any other type of scripting.

First, you'll have to get acquainted with connecting to and dumping your database on the command line. Depending on your user, credentials, and where the databases are located, your command might look something like this. Please note, there is no space between the password and the "-p" flag.

$ mysqldump -u user -pPASSWORD -h hostname database > database.sql

To simplify my example, I'm going to shorten the mysqldump command to the follow.

$ mysqldump database > database.sql

Now that we're MySQL command line pros, I'll break down each command. I'll start by showing all the databases.

Eric-Londons-MacBook-Pro:backup Eric$ mysql --execute="show databases"
+---------------------+
| Database            |
+---------------------+
| customers           |
| db_pics_ericlondon  |
| db_thedrupalblog_d6 |
| drupal              |
| drupal-pics         |
| drupalmusicproject  |
| itunes              |
+---------------------+

Now, I'll "pipe" the output from the previous command into awk to show the first column data.

Eric-Londons-MacBook-Pro:backup Eric$ mysql --execute="show databases" | awk '{print $1}'
Database
customers
db_pics_ericlondon
db_thedrupalblog_d6
drupal
drupal-pics
drupalmusicproject
itunes

And use grep to remove the first line that says "Database".

Eric-Londons-MacBook-Pro:backup Eric$ mysql --execute="show databases" | awk '{print $1}' | grep -iv ^Database$
customers
db_pics_ericlondon
db_thedrupalblog_d6
drupal
drupal-pics
drupalmusicproject
itunes

And use sed to build the mysqldump command. This one is kinda tricky, sorry. As you can see, I also embedded the date command in there to generate today's date in the format: YYYYMMDD.

Eric-Londons-MacBook-Pro:backup Eric$ mysql --execute="show databases" | awk '{print $1}' | grep -iv ^Database$ | sed 's/\(.*\)/mysqldump \1 > \1.'$(date +"%Y%m%d")'.sql/'
mysqldump customers > customers.20100825.sql
mysqldump db_pics_ericlondon > db_pics_ericlondon.20100825.sql
mysqldump db_thedrupalblog_d6 > db_thedrupalblog_d6.20100825.sql
mysqldump drupal > drupal.20100825.sql
mysqldump drupal-pics > drupal-pics.20100825.sql
mysqldump drupalmusicproject > drupalmusicproject.20100825.sql
mysqldump itunes > itunes.20100825.sql

Lastly, if everything looks good, you can pipe the output back to the command line.

Eric-Londons-MacBook-Pro:backup Eric$ mysql --execute="show databases" | awk '{print $1}' | grep -iv ^Database$ | sed 's/\(.*\)/mysqldump \1 > \1.'$(date +"%Y%m%d")'.sql/' | sh

Eric-Londons-MacBook-Pro:backup Eric$ ls -1
customers.20100825.sql
db_pics_ericlondon.20100825.sql
db_thedrupalblog_d6.20100825.sql
drupal-pics.20100825.sql
drupal.20100825.sql
drupalmusicproject.20100825.sql
itunes.20100825.sql

You could even take this one step further and pipe the output through gzip to compress the dumps :)

Eric's picture

Recently, I had to work on a few Drupal sites and only had FTP access to the webservers. One thing is for certain: FTP is slow and painful. I prefer SSH access so I can interact with Subversion, compress files, dump mysql databases, and transfer files securely. I tried to copy the entire remote docroot to my local development environment (using my FTP client, CyberDuck), and the time estimate to copy all the files individually was ridiculous. There are over 500 files in Drupal core alone, not to mention all the 3rd party modules and uploaded files. I decided to upload a tiny PHP file to execute once to backup the filesystem outside the docroot, so I could copy a single compressed file. Before you attempt something like this, you MUST understand the security risk and vulnerability of exposing site archives and having PHP scripts like this on your server. For instance, if someone was able to get a hold of your settings.php file, they'll have access to your MySQL DNS (connection string). Hopefully, your webserver does not have MySQL and other important services exposed through your firewall, but that is a different topic altogether. I'm already having doubts sharing this PHP snippet. I uploaded the following code to a file in the docroot of Drupal, browsed to the web path once, then promptly removed it from existence. Afterward, I was able to copy the entire filesystem as one file (one transfer), maintain my sanity, and saved myself hours of slow FTP transfers.

<?php
// define a list of valid IPs that can access this file
// yes, I know, this will not prevent spoofers, etc
$validIPs = array(
 
'MY-IPADDRESS',
 
'MY-OTHER-IPADDRESS'
);

// ensure the request is coming from a valid IP address
if (!in_array($_SERVER['REMOTE_ADDR'], $validIPs)) die;

// define a path to the archive to create
// VERY IMPORTANT: you must prefix the file path with "../" to ensure the archive is created outside the docroot path!
// NOTE: you may need to update the file path to work in your hosting situation
$filePath = "../backup.tar.gz";

// ensure the file does not already exist
if (file_exists($filePath)) die;

// define the command to execute to compress the site
// NOTE: you may need to specify the full path to the tar command
$command = "tar -czf $filePath .";

// execute the command to compress the entire site
exec($command);

echo
"done."
?>

After this code is executed, be sure to remove the PHP script and archive!

Eric's picture

Here's a quick code snippet I just wrote to dump and compress (gzip) all mysql databases on a server into separate files using PHP and PEAR.

<?php
// include PEAR DB library
require_once('DB.php');

// define the DSN in an array
// NOTE: user must have access to all databases
$dsn = array(
 
'phptype' => 'mysql',
 
'username' => 'YOURUSER',
 
'password' => 'YOURPASSWORD',
 
'hostspec' => 'HOSTNAME', // localhost?
);

// instantiate a PEAR DB object
$DB =& DB::connect($dsn);

// check for an error
if (DB::isError($DB)) die($DB->getMessage());

// set the DB fetch mode to associative
$DB->setFetchMode(DB_FETCHMODE_ASSOC);

// define sql statement
$sql = "show databases";

// fetch sql result
$databases = $DB->getAll($sql);

// loop through results
foreach ($databases as $index => $result) {

 
// define the mysqldump command
 
$command = "mysqldump -u " . $dsn['username'] . " "
   
. "-p" . $dsn['password'] . " "
   
. "-h " . $dsn['hostspec'] . " "
   
. $result['Database'] . " | gzip > "
   
. $result['Database'] . ".sql.gz";

 
// execute command
 
`$command`;
   
}
?>

Eric's picture

It's important to realize that making changes to Drupal (configuration changes, uploading a file, etc) affect both the file system and the database. That's why I feel it's important to integrate your MySQL database with your subversion file system. For instance, if you upgrade a Drupal module, you should commit a backup of your database with the file system changes to ensure you have a definitive snap snot of your project, and can revert to a previous revision as necessary.

All of my Drupal projects are subversion integrated for proper version control. A typical subversion tree resembles:

client
    project
        trunk
            httpdocs
                [all drupal files go here]
            archive
        branches
        tags

When I checkout projects to my Linux file system, I use the following command to ensure I checkout everything parallel to the httpdocs folder. I use the "archive" folder for items (like database dumps) that I'd like to keep outside of the httpdocs, so they are not public.

$ cd /var/www/vhosts
$ mkdir PROJECTNAME.erl.dev
$ cd PROJECTNAME.erl.dev
$ svn checkout https://SVNPATH/client/project/trunk .

Now that my file system is integrated with subversion, I can backup my MySQL database and check it into subversion before I make a critical change to the Drupal configuration, such as upgrading a module:

$ cd /var/www/vhosts/PROJECTNAME.erl.dev/archive
$ mysqldump -u USER -pPASSWORD -h HOST DATABASENAME > DATABASENAME.sql
$ svn stat
?      DATABASENAME.sql
$ svn add DATABASENAME.sql
$ svn commit DATABASENAME.sql -m "backup'd database prior to database change"

NOTE: if you've already checked your database into subversion and you're creating another backup, your svn commands (and output from the svn stat command) will be slightly different. A question mark represents a new file, while a capital "M" represents a modified file:

$ cd /var/www/vhosts/PROJECTNAME.erl.dev/archive
$ mysqldump -u USER -pPASSWORD -h HOST DATABASENAME > DATABASENAME.sql
$ svn stat
M      DATABASENAME.sql
$ svn commit DATABASENAME.sql -m "backup'd database prior to database change"

Let's say you upgrade a module (or Drupal) and get an unexpected error, you can now revert your database:

$ cd /var/www/vhosts/PROJECTNAME.erl.dev/archive
$ mysql -u USER -pPASSWORD -h HOST
mysql> drop database DATABASENAME;
mysql> create database DATABASENAME;
mysql> exit
$ mysql -u USER -pPASSWORD -h HOST DATABASENAME < DATABASENAME.sql

Eric's picture

Here's a quick procedure on how to upgrade a Drupal site that's integrated with subversion (a checked out local repository). NOTE: you'll have to update paths, users/group, etc. I wrote this from memory, hopefully there are no errors.

# backup db
cd /var/www/vhosts/SITEHOSTNAME
cd archive
mysqldump DATABASE | gzip > DATABASE.sql.gz
cd ..

# get new version of drupal, unpack, & rename
wget http://ftp.drupal.org/files/projects/drupal-6.7.tar.gz
tar -xzf drupal-6.7.tar.gz
mv drupal-6.7 httpdocs_new

# TODO: Place the site in "Off-line" mode

# copy sites directory
cp -pr httpdocs/sites/* httpdocs_new/sites

# remove old subversion files
cd httpdocs_new
find . -type d -name "\.svn" -exec rm -rf {} \;
cd ..

# set permissions (as necessary)
chown -R apache.GROUP httpdocs_new
chmod -R 2770 httpdocs_new

# swap directories
svn mv httpdocs httpdocs_bak
svn commit -m "renamed site per upgrade"
mv httpdocs_new httpdocs

# upgrade db
# login in as userID 1
# http://SITEHOSTNAME/update.php

# test site, revert as necessary

# TODO: Place the site in "Online" mode

# check in new files
svn add httpdocs
svn commit -m "upgraded drupal"

# remove backup folder
svn del httpdocs_bak
svn commit -m "removed old version of site"