Drupal Multisite Backup Script

The following script I am using now in production over a few weeks, and it seems to be successfully back up my Drupal multisite installation. Some key facts:

  • it backups all sites in the given DRUPAL_ROOT folder to a given FTP_HOST
  • the database (currently only mysql is supported!) content of each site is dumped, compressed and stored to the ftp site
  • all files of the DRUPAL_ROOT folder are saved via Duplicity to the ftp site

Comments, suggestions and fixes are welcome!

#!/bin/bash

######################################
# Simple Drupal multi-site backup file
######################################
# Author: Matthias Hub (drupal@matthiashub.de)
# Version: v1.2 2010/01/02
#
# What does it?
# - backups all sites in the given DRUPAL_ROOT folder to a given FTP_HOST
# (database is dumped and compressed, files are stored via duplicity)
# - removes backup files older than LIMIT days
#
# What is needed?
# - bash as shell
# - Drupal installation
# - Duplicity (e.g. on a Debian system install it with
# sudo apt-get install duplicity)
#
# Tested with a multi-site installation of Drupal 6
#
# References:
# - Drupal: http://www.drupal.org/
# - fullsitebackup script: http://drupal.org/node/129202
# - Duplicity: http://www.nongnu.org/duplicity/
#
#
# Version history:
#
# Version 1.2 2010/01/02
# - fixed debug output
#
# Version 1.1 2009/12/30
# - added function to print the reason if ncftpget fails
# - added check if ncftpget fails
#
# Version 1.0
# - backups up a drupal multi site installation to a remote ftp using
# duplicity for files and tar and gzip for the databases

############################ CONFIGURATION #####################

# root folder of your Drupal installation (which contains the sites/ folder)
DRUPAL_ROOT=/var/www/drupal

# time stamp format of the tar file
# default: "%m-%d-%Y will lead to files called sites-12-12-2009.tgz
DIR_FORMAT="%m-%d-%Y"
# start day
# default: 0
START=0
# which time interval to backup, 7 means seven days back
# default: $(expr $START + 7)
LIMIT=$(expr $START + 7)

# ftp user name
FTP_USER=backup
# ftp password
FTP_PASSWORD=password
# ftp host
FTP_HOST=backuphost
# ftp remote backup directory
FTP_DIR=/

# GNU Passphrase for duplicity
PASSPHRASE=duplicity-drupal-backup-passphrase
# remote backup folder (usually different than the FTP_DIR)
# default: files
DUPLICITY_BACKUP_FOLDER=files
# excludes files from backup
# can be a pattern like **mp3
# BEWARE: only specify either EXCLUDE_PATTERN or EXCLUDE_FILE
# default is empty, which means no exclude pattern is used
DUPLICITY_EXCLUDE_PATTERN=
# excludes files listed in the given file from backup
# (value is a filename, please give the absolute path)
# BEWARE: only specify either EXCLUDE_PATTERN or EXCLUDE_FILE
# default is empty, that means no exclude file is used
DUPLICITY_EXCLUDE_FILE=
# more parameters for duplicity, directly passed
# default: --remove-older-than 5D
DUPLICITY_PARAMETERS="--remove-older-than 5D"

# debug level of the logging
# 0 means no debug
# 4 means full debug
DEBUG=4
# working directory
# default: `pwd`
startdir=/var/tmp
# logfile
# default: $startdir"/backup-all-sites.log"
logfile=$startdir"/backup-all-sites.log"

################### NO MORE CONFIGURATION BEYOND THIS LINE ####################

export PASSPHRASE=${PASSPHRASE}
export FTP_PASSWORD=${FTP_PASSWORD}

function debug {
if [ "$DEBUG" -ge $1 ];
then
if [ $1 -eq "1" ];
then
echo -n "INFO: " >> $logfile
elif [ $1 -eq "2" ];
then
echo -n "FINE: " >> $logfile
elif [ $1 -eq "3" ];
then
echo -n "FINER: " >> $logfile
elif [ $1 -eq "4" ];
then
echo -n "FINEST: " >> $logfile
fi
echo "$2" >> $logfile
fi
}

function getNcftpgetReason {
REASON=
case $1 in
1 )
REASON="Could not connect to remote host.";;
2 )
REASON="Could not connect to remote host - timed out.";;
3 )
REASON="Transfer failed.";;
4 )
REASON="Transfer failed - timed out.";;
5 )
REASON="Directory change failed.";;
6 )
REASON="Directory change failed - timed out.";;
7 )
REASON="Malformed URL.";;
8 )
REASON="Usage error.";;
9 )
REASON="Error in login configuration file.";;
10 )
REASON="Library initialization failed.";;
11 )
REASON="Session initialization failed.";;
* )
REASON="No reason";;
esac
}

today=`date +"${DIR_FORMAT}"`

# init log file
echo "Starting multi site backup script at `date`" > $logfile

DRUPAL_SITES="${DRUPAL_ROOT}/sites"

if [ ! -d "$DRUPAL_SITES" ]
then
echo "ERROR: Cannot find drupal sites directory at ${DRUPAL_SITES}! Stop processing now!" >> $logfile
exit 1;
fi

SITES="$(find ${DRUPAL_SITES} -maxdepth 1 -mindepth 1 -type d -printf %Pn)"

sites_string=
for i in ${SITES[*]}
do
sites_string="${sites_string} ${i}"
done

debug 2 "Will process following sites: ${sites_string}"
debug 3 "Getting file list from backup server ${FTP_HOST}"

ncftpls -u ${FTP_USER} -p ${FTP_PASSWORD} -x "-t" ftp://${FTP_HOST}${FTP_DIR} > /tmp/ftp.out 2>> $logfile

backup_dir=$startdir/backup-temp
mkdir $backup_dir 2>> $logfile
cd $backup_dir

for site in ${SITES}
do

# settings file
settingsfile="${DRUPAL_SITES}/${site}/settings.php"
if [ ! -f "$settingsfile" ]
then
debug 3 "No settings.php found in ${site}, will not process it!"
continue
fi

debug 1 "Processing ${site}"

LIST="$(grep ${site} /tmp/ftp.out)"
debug 1 "STEP 1: Starting removal of old backup files..."

debug 3 "files for this site found on the server: ${LIST}"

# search for the backup files for this site
DAYS=$(for d in $(seq $START $LIMIT);do date --date="$d days ago" +"${DIR_FORMAT}"; done)
FILENAMES=
for d in $DAYS
do
FILENAMES="${FILENAMES} ${site}-${d}.tgz"
done

debug 4 "filenames which were created in the last 7 days: ${FILENAMES}"

for serverfile in ${LIST}
do
IS_FOUND=false
for f in ${FILENAMES}
do
if test "${f}" = "${serverfile}"
then
IS_FOUND=true;
fi
done
if ! $IS_FOUND;
then
debug 2 " - deleting ${serverfile} on the server as it is not in the last 7 days"
debug 4 "ncftpget command line is ncftpget -c -DD -u ${FTP_USER} -p ... ftp://${FTP_HOST}${FTP_DIR}${serverfile}"
ncftpget -c -DD -u ${FTP_USER} -p ${FTP_PASSWORD} ftp://${FTP_HOST}${FTP_DIR}${serverfile} > /dev/null >> /dev/null 2>> /dev/null
if [ $? -ne 0 ] ; then
getNcftpgetReason $?
debug 2 "delete failed, reason = ${REASON}"
fi
fi
done

debug 1 "STEP 2: create temporary directory for site..."
site_backup_dir=$backup_dir/${site}
mkdir $site_backup_dir 2>> $logfile

if [ ! -d $site_backup_dir ];
then
echo "ERROR: backup directory ${site_backup_dir} does not exist!";
exit 1;
fi

cd $site_backup_dir

debug 1 "STEP 3: backup up database..."

# get username and password from settings php
db_url=$(grep "^$db_url" $settingsfile)
db_user=$(expr "$db_url" : '.*mysqli?://([^:]*).*')
db_pass=$(expr "$db_url" : '.*mysqli?://[^:]*:([^@]*).*')
db_name=$(expr "$db_url" : ".*mysqli?://[^@]*@[^/]*/([^']*).*")

debug 4 "mysqldump command line is mysqldump --user=${db_user} --password=... --add-drop-table ${db_name} > dbcontent.sql"
mysqldump --user=$db_user --password=$db_pass --add-drop-table $db_name > dbcontent.sql 2>> $logfile

# compress
tarname=${site}-${today}.tgz
tar czf $tarname dbcontent.sql

# save to ftp
ncftpput -u ${FTP_USER} -p ${FTP_PASSWORD} ${FTP_HOST} ${FTP_DIR} ${tarname} >> $logfile 2>> /dev/null

debug 1 "STEP 4: clean up temporary directory..."
cd $backup_dir
rm -rf $site_backup_dir 2>> $logfile
done

debug 1 "STEP 5: backup files..."

# save to ftp
duplicity_parameters="${DUPLICITY_PARAMETERS}"
if [ -n "${DUPLICITY_EXCLUDE_PATTERN}" ];
then
duplicity_parameters="${duplicity_parameters} --exclude '${DUPLICITY_EXCLUDE_PATTERN}'"
elif [ -n "${DUPLICITY_EXCLUDE_FILE}" ];
then
duplicity_parameters="${duplicity_parameters} --exclude-filelist ${DUPLICITY_EXCLUDE_FILE}"
fi
debug 4 "duplicity command line is duplicity ${duplicity_parameters} ${DRUPAL_ROOT} ftp://${FTP_USER}@${FTP_HOST}/${DUPLICITY_BACKUP_FOLDER}"
duplicity ${duplicity_parameters} ${DRUPAL_ROOT} ftp://${FTP_USER}@${FTP_HOST}//${DUPLICITY_BACKUP_FOLDER} >> $logfile 2>> $logfile

debug 1 "cleaning up backup dir"
rmdir $backup_dir 2>> $logfile

debug 1 "finished multi site backup."

User login

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
7 + 10 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
Powered by Drupal