WordPress Backup Script

After my server was down for a couple days (because I was physically moving the machine to another location) I decided to check if there were any updates. Sure enough, there just happened to be an update for 3.2 and it had the usual warning of “be sure to backup your database and files” which I either ignore or partially ignore. I do typically do a mysqldump before running any of the updates, but I almost never backup the WordPress directory itself. For whatever reason, today I thought it’d be a good idea to do that.

Rather than doing it manually I thought I’d like for a script to do it for me so I can more easily do it in the future. Now, this could have been a pretty simple script with about 20 lines to include parsing arguments and running mysqldump and/or tar to backup the database and the WordPress directory, respectively. Of course, if that were the case, there wouldn’t be much to write about would there? Instead, I thought it’d be nice, if I were ever to get a faster server, to be able to change some things easily like what compression method to use, or if I’m ever to change server/reorganize this one to be able to quickly/easily change the WordPress directory, database name, etc. So, the script grows a bit. After that, I thought it’d be easier to control the flow if I were to put the logic into functions. Now the script grows a bit more. I then decide to use some Bash substring logic to try to figure out the file extension, and the script grows some more. Just because I was at it, I thought, why not show the compression ratio and before/after file sizes? So the script nearly doubles in size!

After all that, and learning a clever way to return non-numeric values from functions, I have a backup script that seems to work quite well for me. It’ll likely break on other systems, and likely break on non-Bash shells, but I’ll post it here anyway because it may be of use or at least interest to others.


#!/bin/bash

WPDIR="/srv/www/wp"
# Must be dumped to stdout, usually -c for bzip2, gzip, and xz
COMPRESS="bzip2 -c9"
# If DB_NAME is left blank, we'll try to fill these in from the WP config
DB_NAME=""
DB_USER=""
DB_PASSWORD=""

DATE="`date +%Y-%m-%d`"

# This function tries to get the database info from the WordPress config
# TODO write me 🙂
function get_database_info() {
echo "To be written"
exit 1
}

# "Returns" the extention based on the COMPRESS variable
# Keep in mind that only one echo can be in this function, and it's used with
# a call to it via: `file_ext`
function file_ext() {
local EXT="${COMPRESS:0:2}"
if [ "$EXT" = "bz" ]; then
EXT="bz2"
fi

# our "return" value
echo $EXT
}

# $1 is the old file/dir and $2 is the new file/dir
function compressed_stats() {
if [ "$#" -ne 2 ]; then
echo "Incorrect amount of arguments passed to compressed_stats"
exit -1
fi

local OLDSIZE
local NEWSIZE

# Check if the passed argument is already a number.
# This fails if the file passed in is only numeric.
# This check also fails if $1 is a decimal number, lucky for us, it's a byte
# count
# Credit goes to jilles on StackOverflow for pointing this out.
case $1 in
# It's most likely a file
''|*[!0-9]*)
OLDSIZE="`du -b $1 | cut -f1`"
;;
# Otherwise, it's a number, just set the size
*)
OLDSIZE=$1
;;
esac

case $2 in
''|*[!0-9]*)
NEWSIZE="`du -b $2 | cut -f1`" ;;
*)
NEWSIZE=$2 ;;
esac

# We were most likely given filenames that don't exist
if [ "$OLDSIZE" = "" -o "$NEWSIZE" = "" ]; then
echo "Not giving stats on files that don't exist!"
return
fi

# Give some statistics about how much the database was compressed
if [ "`which awk`" ]; then
echo -n "compression ratio: "; echo - | awk "{ print $OLDSIZE/$NEWSIZE }"
echo -n "Old size: "; echo - | awk "{ print $OLDSIZE/1024 }"
echo -n "New size: "; echo - | awk "{ print $NEWSIZE/1024 }"
elif [ "`which bc`" ]; then
echo compression ratio: `bc $OLDSIZE/$NEWSIZE`
echo Old size: `bc $OLDSIZE/1024`
echo New size: `bc $NEWSIZE/1024`
fi
}

# Dump the database
function dump_db() {
local FILENAME="wp_backup_$DATE"
local EXT="`file_ext`"

if [ "$DB_NAME" = "" ]; then
get_database_info
fi

echo "Dumping mysql database to $FILENAME"
mysqldump -p"$DB_PASSWORD" -u$DB_USER $DB_NAME > $FILENAME

echo "Compressing $FILENAME with $COMPRESS"

$COMPRESS $FILENAME > $FILENAME.$EXT

compressed_stats $FILENAME $FILENAME.$EXT

rm "$FILENAME"
}

# Backup the WordPress directory
function backup_wp() {
local EXT="`file_ext`"
local FILENAME="wp-$DATE.tar.$EXT"
local CURPWD="$PWD"

echo "Backing up $WPDIR to $FILENAME"

(cd $WPDIR/..; tar -cf - -X $CURPWD/$0.excludes "`basename $WPDIR`") | $COMPRESS > $FILENAME

compressed_stats `(cd $WPDIR/..; tar -cf - -X $CURPWD/$0.excludes "$(basename $WPDIR)") | wc -c` $FILENAME
}

# Help dummy
function print_help() {
echo "Usage: $0 [-d] [-b] [-h]"
echo " -d Dump wordpress database"
echo " -b Backup WordPress directory"
echo " -h This message"
}

# tar fails without this file, make sure it exists
[ -e $0.excludes ] || touch $0.excludes

# Parse our arguments
if [ $# -ne 0 ]; then
while getopts ":dbh" Opt; do
case $Opt in
d) dump_db ;;
b) backup_wp ;;
h) print_help ;;
*) echo "Unknown option"; exit ;;
esac
done
else
# Default to backing up the database and directory
dump_db
backup_wp
fi

As I mentioned, I learned a way to pass non-numeric values back from shell functions. Normally, when you return a value from a shell function you do something like this:
function f() {
[ -e "$1" ] || return 0
return 1
}
In this silly example, I return 0 if a filename passed in exists, and 0 if it does not. If you wanted to return something like “This is a returned string” with “return”, it’d fail because “return” returns a numeric value only. There’s a trick you can use that I hadn’t thought of before (and one I found while browsing online, but I can’t seem to find the link again.) Basically you have a single (executed) “echo” in the function. Then you can call the function with `func`. So the above example would be written as:
function f() {
if [ -e "$1" ]; then
echo "Exists"
else
echo "Failed"
fi
}
v=`f "test.file"`
echo $v
echo `f "file.test"`

This allows for some interesting possibilities. For example, in my backup script, I used it to return the extension for the compressed file. How the script is currently implemented, this is a totally unneeded function as the code could be executed once, and be done with it. I mostly did it to try out this new technique, but also, in the future, I may want to use different compression methods for the two steps. E.G. I’d use xz -c9 for the database compression, but bzip2 -c9 for the wp directory compression. I’d want that, because xz uses a lot of RAM (which my server doesn’t have).

— Updated 2011-12-13 to make script more fail proof for numeric test —