Backup scripting for the Mac
Monday, January 25th, 2010There are a variety of solutions for performing backup on the Mac that aim to make things easy for you through the use of great user interfaces. For those of us that are more interested in setting up fully scripted solutions, the Mac provides all the tools you need to do sophisticated backup tailored to exactly what you need. The great news is that everything you need is all based on free and open source solutions already installed on your Mac.
I’ll cover two key types of data you’re likely to need to backup: databases and files. I’ll assume that your databases are in MySQL, but there are similar tools for working with other popular databases. This same technique can also be used for Linux systems, but I’ll focus on the particulars for the Mac. I’m also assuming that you have root permission (sudo) to perform these commands.
First you’ll need to setup a special database user that we’ll only use for backups. You’ll need to enter your MySQL root password at the prompt, and then replace BACKUP_PASSWORD with a unique and secure password for this new user.
$ mysql -u root -p
mysql> grant select, reload, lock tables on *.* to backup@localhost identified by 'BACKUP_PASSWORD';
mysql> flush privileges;
You’ll then want to create an excludes file to avoid backing up data files that don’t make any sense. My install of MySQL is in /usr/local, but if yours is elsewhere you’ll need to change the last line to the location of the MySQL data files. There’s no need to backup these files as we’ll backup all your databases in a later step.
$ cat >> /usr/local/etc/backup_excludes.txt << EOF
/tmp/
/Network/
/Volumes/
/cores/
/afs/
/automount/
/private/tmp/
/private/var/run/
/private/var/imap/socket/
/private/var/imap/proc/
/private/var/launchd/0/sock*
/private/var/spool/postfix/
/private/var/vm/
/Previous Systems.localized
.Trash/
.Trashes/
.Spotlight-*/
/usr/local/mysql/data/
EOF
Next you'll need to mount the backup drive using the diskutil command. The exact disk name is likely different from my device name disk1s6. You can figure out the device name for your backup drive using the diskutil list command.
$ diskutil mount disk1s6
You then have to turn on ownership settings for the backup volume to ensure you get an exact copy of the files on your source drive. The default for external drives doesn't maintain original file ownership. We'll use the mdutil command.
$ mdutil enableOwnership disk1s6
Now we'll use the powerful rsync command to mirror an exact copy of all your files. There are a lengthy set of confusing parameters you can pass to rsync, but those you need are shown below. Archive mode (-a) makes sure everything is copied exactly as it is on your source drive. Only your source drive will be backed up, it will not cross filesystem boundaries (-x). Sparse files (-S) will be handled efficiently. Extended attributes (-E) and resource forks will be copied as well. Files that have been deleted on the source drive will also be deleted from the backup (--delete). The directories and files specified in the excludes listing we created earlier (--exclude-from) will be skipped.
$ rsync -axSE --delete --exclude-from /usr/local/etc/backup_excludes.txt / /Volumes/Backup/
It's also a good idea to turn off Spotlight on the backup as it's not really needed and only takes up space and time.
$ mdutil -i off /Volumes/Backup
$ mdutil -E /Volumes/Backup
Let's make the backup bootable while we're at it using the bless command.
$ bless -folder /Volumes/Backup/System/Library/CoreServices
Now let's backup all your MySQL databases using mysqldump. You'll need to replace BACKUP_PASSWORD with the backup database username we created earlier. The backup will be archived and compressed using the gzip command. The backup filename will be created using the current date for easy reference if you need to restore things to a previous date. You can read all about doing backups for MySQL here.
/usr/local/mysql/bin/mysqldump -u backup -pBACKUP_PASSWORD --all-databases --lock-all-tables --flush-logs | gzip > /Volumes/Backup/usr/local/mysql/backup/all.`date +%u`.sql.gz
Finally you can unmount the backup drive now that we're done.
$ diskutil unmount /Volumes/Backup
You can take all these steps and turn them into a single script for doing your backups. You can also schedule the script to run at whatever frequency you like. Other steps and ideas you can take next include:
- Rotate multiple backup drives and keep at least one physically at another location.
- Make application specific database backups available to your teams for easy download. Use the mysqldump command and replace DATABASE_NAME with your application's database name:
- Make compressed archives of specific directories available to your teams for easy download. Use the tar command and replace directory paths and file names with appropriate values.
- Use the asr command to clone an exact copy of your disks as an alternative.
/usr/local/mysql/bin/mysqldump -u backup -pBACKUP_PASSWORD DATABASE_NAME --lock-all-tables --flush-logs | gzip > /usr/local/apache2/htdocs/backups/DATABASE_NAME.`date +%u`.sql.gz
$ tar -C /usr/local/apache2/htdocs -czf /usr/local/apache2/htdocs/backups/appdata.tar.gz application/data
You can find more information on each of the commands I've talked about in the Mac OS X Manual Pages section of Apple's developer site. The Mac is an excellent UNIX platform for building powerful server platforms for all your infrastructure needs.
