Pleasant Automatic Directory Backups with rsync
I’ve been trying to get a few things squared away on my computer recently: photos and backups. Two things that will probably never really be done. I’ve been keeping multiple backups since your data doesn’t exist unless it’s in at least two or three places:
- Arq (recommended) pointing at S3/Glacier
- Time Machine (good enough but not as your only backup)
- Carbon Copy Cloner (recommended) for a bootable backup
I had a bunch of different photo libraries, because Aperture let you do that easily, but Photos.app doesn’t so I really wanted them all merged. I’ll save the rant about accomplishing this for another time, but PowerPhotos made it possible at all and wasn’t the cause of the issues. Because I don’t want to lose all of my photos I figured why not make another backup of just the library. Couldn’t hurt (hasn’t yet, who knows with computers really), and I had a newly-extra hard drive that all of those extra Aperture libraries were on.
So I wanted to sync my Photos.app library (which is really just a directory) to an external hard drive. And I’d like to not need a special app in order to backup and restore the data in case I need to restore it to a fresh OS X install. And I’d like to not remember the exact sync command to do this so I don’t mess it up. And I’d like it to happen automatically when I plug in the hard drive, like Time Machine does. It turns out this is all possible, and I learned a few neat tricks while setting it up.
Backing up the library
From what I’d read, the most straightforward way to backup a directory to an external hard drive was with
rsync, which comes included with OS X. I installed a newer version with Homebrew so that I could try the
--info=progress2 option to get overall progress in addition to per-file progress, but that’s entirely optional. This script asks you if you’d like to backup first, so it doesn’t just happen when what you really wanted was to restore the copy on the computer from the backup. This part does that, as well as ensuring that the photo library is in the expected place:
read -r -p "Do you want to backup $file to the Photo Storage volume? [y/N] " response if [[ $response =~ ^([yY][eE][sS]|[yY])$ && -d "$path" ]]; then
If both of these things are the case, then it’ll perform the backup with
#!/bin/bash # # ~/bin/backup-photos file="Photos Library.photoslibrary" path="/Users/brandon/Pictures/$file" read -r -p "Do you want to backup $file to the Photo Storage volume? [y/N] " response if [[ $response =~ ^([yY][eE][sS]|[yY])$ && -d "$path" ]]; then rsync -av --delete --info=progress2 "$path" "/Volumes/Photo Storage/" fi
Automatically starting the backup
I wanted backups to happen just as I plugged in the drive, and it turns out that
launchd provides a configuration option that can help with this.
launchd is a program that comes with OS X and performs a lot of tasks during bootup, as well as being a replacement for
cron. launchd.info is a good resource on it, but in short: A “launch agent” will run the backup script from above, and it’ll be configured to be triggered by new devices being mounted.
The agent is configured with a plist configuration file. It’s pretty simple: a label (reverse DNS style), program (path to the script) and setting the StartOnMount value to true. The
StartOnMount key will start an agent when any device is mounted.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>ca.brandonevans.photobackup</string> <key>Program</key> <string>/Users/brandon/bin/spawn-photo-backup</string> <key>StartOnMount</key> <true/> </dict> </plist>
spawn-photo-backup script in the Program value still needs to be written. It needs to do two things: check if the device that was mounted is the backup drive and then run the backup script.
Disk drives get mounted at
/Volumes on OS X, so
if [[ -d "$volume" ]]; then will check to see if
$volume exists and is a directory.
open -a Terminal.app ~/bin/backup-photos will open Terminal and run the backup script written earlier.
#!/bin/bash # # ~/bin/spawn-photo-backup volume="/Volumes/Photo Storage" if [[ -d "$volume" ]]; then open -a Terminal.app ~/bin/backup-photos fi
Now, this will work, but there’s a few things I added to make this work really smoothly.
open -Wna Terminal.app ~/bin/backup-photos
open command has two useful options for this case:
W tells the open command to wait until the application it opens finishes its work and exits before it exits too. This allows doing more work only after the backup completes instead of right after it began.
n will create a new instance of the opened application. I used this since I almost always have Terminal.app open doing other work already, and I don’t want to interfere with that when a backup begins. Using these two options together means a new Terminal window opens to run the backup, and when I quit that instance when the backup is complete the next lines in the script will run.
diskutil unmount "$volume"
Once the backup is complete I want to be able to unplug the disk right away without needing to eject it manually first.
diskutil is basically the command line version of Disk Utility, and
diskutil unmount does exactly what we need.
/usr/bin/osascript -e 'display notification with title "Photo Library Backup Complete"'
I also wanted a notification posted when the backup completed, since it may take a while and I could be off doing something else in another space. Turns out this is possible with AppleScript! I had no idea. You don’t get to customize the icon with this method (look at terminal-notifier if you want this) but it works without any extra software.
Putting it all together:
#!/bin/bash # # ~/bin/spawn-photo-backup volume="/Volumes/Photo Storage" if [[ -d "$volume" ]]; then open -Wna Terminal.app ~/bin/backup-photos diskutil unmount "$volume" echo "Photo Library Backup Complete" /usr/bin/osascript -e 'display notification "You can now disconnect the backup drive." with title "Photo Library Backup Complete"' fi
It should really check the result of unmounting the disk before saying it’s alright to disconnect it, but that’s something I’m okay adding later.
And that’s it! Not too much to set up, and it works without installing any new software. At the very least I learned a few new tricks to use on the command line. Hopefully you did too, and if you have any questions or comments send ‘em my way on Twitter or by email. I’ve put all three of the files together in a gist as well.