Previously, I wrote about backing up files to Dropbox with rsync. I automated the process with cron the ancient UNIX “time-based job scheduler”. While OS X ships with cron, newer versions of the operating system favor launchd. launchd is much more than a cron replacement, on OS X it also fills the rolls of init, init.d/rc.d scripts, and inetd. It can be told to watch for changes in directories or drives to be mounted and run processes when those events happen.

launchd is powerful, but it’s use of XML config files makes it overkill for simple tasks that can be handled by cron. However, if the task isn’t so simple…

The rsync/cron setup I described works well if the files you want to backup are always available, but what if they aren’t? I have a external drive attached to my Mac that I use for storing files I don’t want cluttering up my internal SDD drive. Because this is an external drive, it’s only attached to my computer when I’m sitting at my desk. The cron job:

1
@hourly /usr/bin/rsync -a /Volumes/External ~/Dropbox/Backups

will fail (and generate a warning email).

It’s easy enough to wrap the rsync in a script that tests if the drive is mounted:

1
2
3
4
5
6
7
8
9
#!/bin/sh
if [ -d  /Volumes/External ]
then
    /usr/bin/rsync -a /Volumes/External ~/Dropbox/Backups
    # Notify us that the backup is complete
    /usr/bin/osascript -e 'display notification "Carry On" with title "rsync Backup Complete"'
    # Or use Growl
    # /usr/local/bin/growlnotify -m 'rsync Backup Complete'
fi

and run that from cron.

However, lets try a different approach using launchd. I’m not particularly worried about these backups, if I was I’d use a full blown system like Time Machine. I do want some backups, however. What I’m going to do is watch for a drive to be mounted and run the backup when one is.

launchd is configured using XML plist like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>net.stuff-things.backup-to-dropbox</string>
    <key>ProgramArguments</key>
    <array>
      <string>/Users/spike/bin/backup-to-dropbox</string>
    </array>
    <key>StartOnMount</key>
    <true/>
  </dict>
</plist>

After the initial boilerplate, <dict> contains the settings in the form of key/value pairs. The first being the Label, the name of the launchd job, which used in logs and display by launchctl list. As you can see, Label uses reverse domain name notation. Since you’re not going to distribute this it really doesn’t matter what you call it. If you don’t have a domain, you should be safe with “com.example”.

ProgramArguments is the command we are going to run. It’s an array because you can pass multiple arguments:

1
2
3
4
5
<key>ProgramArguments</key>
<array>
   <string>/usr/bin/say</string>
   <string>Boo</string>
</array>

Would run /usr/bin/say Boo.

Finally, we need to define the conditions under which the script is run. For this backup we want StartOnMount, which we set to “true”. StartOnMount causes the program to run anytime a drive is mounted.

man launchd.plist will give you a long list of other keys. The details are beyond that post, but for example:

1
2
<key>StartInterval</key>
<integer>60</integer>

would run every 60 seconds.

To use this script, install it (you’ll need “sudo”) as:

1
/Library/LaunchDaemons/your-label.plist

i.e.

1
/Library/LaunchDaemons/net.stuff-things.backup-to-dropbox.plist

Then run:

1
launchctl load -w /Library/LaunchDaemons/your-label.plist

Now whenever you mount a drive, your script will be run. Pretty cool, eh?

If you wish to disabled the script, run:

1
2
launchctl unload -w /Library/LaunchDaemons/your-label.plist
sudo rm /Library/LaunchDaemons/your-label.plist

This strategy is a bit odd, it would make more sense to backup the disk before it’s ejected (which you can’t actually detect) as that’s when it’s most likely to have changes. Also, the if the drive is already mounted then another drive is connected, the backup will run. Despite this, this isn’t a contrived example, I actually use it. And it points the way to other interesting uses, drive based location detection anyone?

Comments