Recently I was entrusted with the task of creating a simple backup solution for a client’s X-Serve. All I needed to do was mirror one directory to a secondary drive each night, as well as once on the weekend to an alternate location. While there are many fine utilities out there for performing backups, such as ChronoSync, and the continually diminishing Retrospect, I opted to take advantage of OS X’s included services.
rsync seemed like the natural choice. I thought about using cp at first but rsync is much more efficient, especially when only a few sub-directories need to be updated, versus having cp rewrite the entire backup directory each time.
Now that cron is deprecated in 10.4, I turned to launchd to run rsync on a schedule. This was the first time that I had tried to use launchd. I was pleased to learn that it’s much easier to configure and use then cron. launchd uses human readable .plist configuration files, which can be loaded on demand with launchctl. To make things even easier there is a utility for editing the launchd plist files called Lingon.
Putting rsync and launchd together proved to be a little more of a challenge then I first suspected. Initially I had tried running rsync directly from launchd but kept getting “launchd: com.firefallpro.daily_backup: 8 more failures without living at least 60 seconds will cause job removal” and “launchd: com.firefallpro.daily_backup: exited with exit code: 12”. A quick look in man rsync informed me that an exit code of 12 is a “Error in rsync protocol data stream.” Not that I know what that means.
After a bunch of research and checking out some examples on the web, most which seem to be incomplete, or untested, I was left with no choice but to read man pages and tinker. The only way to get the rsync to run properly was to trigger it from shell script versus directly from launchd.
The following is my final implementation for your approval, dissection, and hopefully, feedback.
launchd plist placed in “/Library/LaunchDaemons/com.firefallpro.daily_backup”:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/
DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.salesgraphics.daily_backup</string>
<key>OnDemand</key>
<true/>
<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>12</integer>
<key>ProgramArguments</key>
<array>
<string>/Users/admin/Documents/daily_backup.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>3</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>
Shell script placed in “/Users/admin/Documents/daily_backup.sh”:
#!/bin/bash
/usr/bin/rsync -qaEu /Library/WebServer/www/ /Volumes/Backup\ HD/daily;
Note: I ran "chown root /Users/admin/Documents/daily_backup.sh" on the shell script to make sure it would execute rsync as root, and "chmod u+x /Users/admin/Documents/daily_backup.sh" to make it executable.
rsync is being run with “-qaEu”:
- q: Run quietly, seeing we’re running through launchd.
- a: Enables a whole bunch of options needed for grabbing a directory and everything inside.
- E: Extended attributes (get Mac OS resource forks).
- u: Update forces rsync to skip files for which the destination file already exists and has a date later than the source file.
Once the files were in place I ran "launchctl load /Library/LaunchDaemons/", to load the new configuration files without rebooting. The console still logs some minor errors from launchd and rsync but the shell script executes successfully.
I hope this helps someone, and if I revise my strategy I’ll make sure to update this post.
Thank you! I’ve been struggling for hours trying launchd to just do it without a script (I like it when I don’t need an extra script), then I tried a Ruby script (which brings its own problems). But your bash script is clean, so I used it.
By the way, I use the script to backup my USB drive each time I connect it. I use the WatchPaths key to monitor /Volumes/MyUSBDrive; instead of a calendar interval.
P.S.: It’s my first time I actively create a script for myself and the first time I use launchd. Never thought it would that hard to do.