I have a web server with a number of clients’ websites on it. It’s necessary to backup these websites every day, since clients use a content management system to make changes regularly. These changes can be updates to a website’s MySQL database, or they can be changes to the files stored within these websites. What I’d like is to backup the MySQL database and the filesystem for each website, every day, at a specific time. The backups must rotate: when there are, say, five backups, I want the oldest one to be removed as the newest one is written. Also, I’d like the backup solution to send me an email every day after it’s completed the backups with a summary of the procedure.
So, in summary, my needs are these:
- Define a list of websites to back up
- For each site, backup (dump) the MySQL database
- For each site, backup the website’s file structure
- Send an email to one or more people with a summary of the backup process.
It’s possible to do this with a shell script (like AutoMySQLBackup does). However, AutoMySQLBackup does not backup file systems or send email. Also, shell scripting tends to be messy code, so I decided to use Ruby.
First off, I’d like to store the list of websites to backup in a separate configuration file so that I can edit this list easily. Also, for reusability, I’ll store database access credentials and email addresses there too. The simplest way of making a configuration file to be read by Ruby is to actually write the configuration file in Ruby, like so:
This file stores a variable
ROTATE which indicates the number of backups to keep before throwing away the oldest one. For each website, I specify the path to the files to be backed up, and the name of the MySQL database. The configuration file will be included and parsed automatically by the backup script, since it is plain Ruby code.
The backup script begins by requiring SMTP support, so that we can send emails later. It also starts an output buffer (“output”) where we will store all messages generated by the script to be included in the email. Before starting the backup procedure, we start a
begin...rescue block so that me may catch any exceptions thrown by Ruby, in order to include these in the email as well.
The script now loops through the list of websites defined in the configuration file, creating a backup directory with the name of the website for each if it doesn’t already exist:
Next, the script enumerates the subdirectories that already exist in the website’s backup directory. This is because we will create a subdirectory with date backup’s date for each backup (e.g. 20110810-105535, for 10 August 2011, 10:55:35). These directories are then sorted alphabetically, so that the least recent backup of the website is first in the list.
The total number of backups found is compared to the value of
ROTATE. If there are too many backups, the latest one(s) (first in the list) are removed.
Having cleaned up excess backups, the script now creates a fresh folder, naming it with the current date and time:
If a website has a database defined in the configuration file, the script now calls
mysqldump to create a backup of the database inside the newly created backup subdirectory. The backup is gzipped as well. Note that a full path to mysqldump must be provided, since cron, which we will use later to run our script at specific times, does not include a path to mysqldump in the shell that it runs in.
If a website has a path to files defined in the configuration file, the script now uses tar/gzip to create a tarball of the entire website file structure, recursing into subdirectories.
This completes the loop that backs up all the websites. We now end our rescue clause in order to catch any exception thrown by Ruby during this process. The exception text is appended to the running log (output) as well as written to standard output.
All that is left to do is to send the output off through email. This is easy to do (any one reason we’re using Ruby):
Adding the script to cron
We can now add the script to the system’s
crontab in order to run at regular times. We’ll write a small shell script that launches the script using the bash shell, to make sure that
cron has access to a powerful shell to run in:
The following entry is added to the system crontab (/etc/crontab). This will make sure that the script runs every day at 22:00.