Archive for September, 2011

Backing up WordPress with Git

Thursday, September 29th, 2011

You can never back up too often.  I’ve been bitten by WordPress bugs (both when upgrading the core or plugins, and I’ve had sites hacked at least once).  I haven’t found a good, automated backup mechanism for WordPress that keeps old history and allows an easy roll-back to an older (presumably working) version.  I’ve become a huge fan of Git in the last year, so I whipped up some scripts to do what I needed.

I’ll assume you’re starting with an existing WordPress install, and it lives on the filesystem at ~/www/blog, and that git is in your path.

  1. $ cd ~/www/blog
  2. create a new repo. $ git init .
  3. add everything. $ git add .
  4. make the inital commit. $ git commit -m 'initial commit'
  5. prevent access to the .git directory via http; create ~/www/blog/.git/.htaccess with the following contents:
            Order allow,deny
            Deny from all

I don’t recommend automating the backup of the WordPress directory; the files in there shouldn’t change automatically. Instead, I like to make sure there are no changes before upgrading the WordPress core, or a plugin, and then explicitly create a new commit with the changes immediately after an upgrade. I do these pretty granularly, so that I can roll back a single plugin.

  1. $ cd ~/www/blog

  2. ensure git status shows no changes

  3. do your thing in the WordPress admin UI

  4. commit the changes:
    $ git add -A && git commit -m 'automatically upgraded to WordPress 3.2.1'

I do automate database dumps, and I like the system I came up with. I create a new “orphan” branch in Git that contains only the database dump. This allows a single Git repository to have two completely separate trees, making it one logical entity containing everything you need to manage the WordPress instance. I keep these repository clones in a different directory, for security reasons.

Here’s how you set that up. First, create a script (I call it do_dump.sh) to dump the mysql database to a file; it will also commit the dump after a commit. Collect the database name, username and password from wp-config.php, then put them into your script. Fill in the blanks from the sample below;
    #!/bin/bash

    # provide location of git binary, if it's not in your path
    git=/path/to/git

    # db info from wp-config.php
    dbname="wordpress_db"
    dbuser="wp"
    dbpassword="rAnd0m^pa55w0rd"

    cd $(dirname $0)

    do_commit=0
    if [ "commit" = "$1" ]; then
        do_commit=1
    fi

    mysqldump
        --skip-dump-date
        --skip-extended-insert
        --single-transaction
        -h localhost
        -u "${dbuser}" "-p${dbpassword}"
        "${dbname}" > "${dbname}.sql"

    if [ $do_commit -eq 1 ]; then
        if [ ! -z "$($git status -s)" ]; then
            echo -e "** autocommit **nn$(date) @ $(hostname)" | $git commit --all --quiet -F -
        fi
    fi

  1. $ cd ~/db_repos/

  2. clone the existing wordpress repo:
    $ git clone ~/www/blog blog

  3. $ cd ~/db_repos/blog

  4. create the new orphan branch:
    $ git checkout --orphan wordpress_db

  5. remove the wordpress files from the index:
    $ git rm --cached -r .

  6. remove the wordpress files from the directory:
    $ git ls-files --other | tr 'n' '' | xargs -0 rm

  7. remove the empty directories:
    $ find . -depth -type d ! -path '*/.git/*' -empty -exec rmdir {} ;

  8. move the dump script into place:
    $ mv /path/do/do_dump.sh .

  9. run the script the first time:
    $ ./do_dump.sh

  10. create a new commit containing the dump file and script:
    $ git add -A && git commit -m 'initial commit on wordpress_db'

Now, automate the database backup. I do this via cron, every 6 hours:
    0 */6 * * * $HOME/db_repos/blog/do_dump.sh commit
When run from cron, the dump script will automatically commit the changes to the local repository. I actually have a slightly different version of the script that also pushes the changes to a remote repository (so my backups aren’t all on one server), but that’s a little more complex.

Hopefully someone finds this useful!