10.4: Back up files in the background using launchd

Apr 03, '06 05:59:00AM

Contributed by: arb

Tiger only hintHaving benefited from this site many times, here is my first contribution.

Apple provides an example of how to automatically back up things using a Folder Action. This solution did not meet my needs, however, since:

  1. It doesn't work with FileVault turned on.
  2. It always copies everything in the set, whether or not is has changed since the last run.
  3. It only runs once when the backup disk is mounted.
Playing with launchd, bash, and perl, I came up with a solution that meets the following requirements:
  1. Continually creates a backup copy of all files in the backup set when and while a certain external medium is mounted.
  2. Updates only changed files.
  3. Runs in the background.
  4. Looks in one directory for files to be backed up.
  5. Follows aliases.
  6. Uses Spotlight to find additional files to back up.
So when I want to protect some file or directory, I either place an alias to it in ~/Auto Backup, or simply assign a color label to it. Every ten minutes, and also whenever a new Volume is mounted, launchd calls the ~/bin/autobackup script. If a specific Volume is present, it copies these files to it.

This may not be pretty code, but it works for me and also is quite flexible. The core is the following shell script:

#!/bin/bash

# This is ~/bin/autobackup

# All files matching this spotlight query will be backed up
# This example means: Everything that has a color label

QUERY="kMDItemFSLabel != '0'"

# Check if destination mounted and target folder available. If not, exit.
# DEST_FOLDER and BACKUP_FOLDER environment vars are passed to this script by launchd.

if [ ! -d $DEST_FOLDER ]; then exit 0; fi

# Find all items in BACKUP_FOLDER and pass them to 'autobackupitem.pl' perl scriptfind "$BACKUP_FOLDER" -regex "$BACKUP_FOLDER/[^/]*" -print0 | xargs -0 -n 1 -J/// $HOME/bin/autobackupitem.pl /// $DEST_FOLDER 2>/dev/null

# Find files matching $QUERY and pass them to 'autobackupitem.pl' perl script

mdfind -0 "$QUERY" | xargs -0 -n 1 -J/// $HOME/bin/autobackupitem.pl /// $DEST_FOLDER 2>/dev/null
exit 0

I want to follow aliases in $BACKUP_DIR to files I want to include in the backup set, so I need to use some Perl tricks to de-reference the alias. This is the Perl script called from the shell script above:

#!/usr/bin/perl

# This is ~/bin/autobackupitem.pl

# Thanks to http://use.perl.org/~pudge/journal/10437

use Mac::Errors;
use Mac::Files;
use Mac::Resources;

# Source path (may be an Alias)
my $path = $ARGV[0];

# Where to copy it
my $dest = $ARGV[1];

# If $path is an Alias, dereference it
my $link = $path;
my $alis;
my $res  = FSpOpenResFile($path, 0) or die $Mac::Errors::MacError;
# get resource by index; get first "alis" resource
my $alis = GetIndResource('alis', 1) or die $Mac::Errors::MacError;
if (! $?){
  $link = ResolveAlias($alis);
}
# Use rsync to actually copy it. Filenames may contain blanks, so use escaped quotes.
END { system "rsync -avuE --inplace \"$link\" \"$dest\""; }

Finally, in order to call ~/bin/autobackup, we need to configure launchd. Create a file containing the following code, and place it as Autobackup.plist in ~/Library/LaunchAgents:

<?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>EnvironmentVariables</key>
        <dict>
                <key>BACKUP_FOLDER</key>
                <string>/Users/ar/Auto Backup</string>
                <key>DEST_FOLDER</key>
                <string>/Volumes/CFBackup/Backup</string>
                <key>HOME</key>
                <string>/Users/ar</string>
        </dict>
        <key>GroupName</key>
        <string>ar</string>
        <key>Label</key>
        <string>de.nvmr.autobackup</string>
        <key>Nice</key>
        <integer>12</integer>
        <key>OnDemand</key>
        <true/>
        <key>Program</key>
        <string>bin/autobackup</string>
        <key>RunAtLoad</key>
        <false/>
        <key>ServiceDescription</key>
        <string>Backup flagged files to external medium</string>
        <key>StartInterval</key>
        <integer>600</integer>
        <key>UserName</key>
        <string>ar</string>
        <key>WatchPaths</key>
        <array>
                <string>/Volumes</string>
        </array>
        <key>WorkingDirectory</key>
        <string>/Users/ar</string>
</dict>
</plist>

I use Lingon to create the launchd configuration and run it.

Comments (5)


Mac OS X Hints
http://hints.macworld.com/article.php?story=20060328080757903