Skip to content Skip to navigation

Automated Snapshots on Mac OS X Lion Server

« previous next »

The problem: creating "TimeMachine-like" snapshots of changed files to a NAS device that is not TimeMachine-compatible.

Caveats with this solution: file permissions will not be maintained in the backup copies.

Overview: uses find and rsync in a shell script called with a launchd daemon.

1. The script

The script uses a combination of find and rsych to find files altered in the last four hours and to copy them to a NAS device with the directory structure intact. One very slight drawback is the whole path is recreated including /Volumes/[drivename]/...etc.

#!/bin/bash

#create a folder to mount the NAS share to. If the folder already exists, this will do nothing
mkdir /Volumes/Backup

#mount the NAS share to the above folder. Replace NASxxx as appropriate for your configuration
mount -t smbfs //NASuser:NASpass@NASaddress/NASsharename /Volumes/Backup

#this loop will maintain 20 snapshots numbered 0 to 19, suitable for four daily snapshots, five days a week
if [ -d /Volumes/Backup/snapshot.19 ] ; then
 rm -rf /Volumes/Backup/snapshot.19
fi
for i in {18..0}
do
 j=$[i+1]
 if [ -d /Volumes/Backup/snapshot.$j ] ; then
  rm -rf /Volumes/Backup/snapshot.$j  
 fi
 mv /Volumes/Backup/snapshot.$i /Volumes/Backup/snapshot.$j
done

#using find to scour various paths for files changed within the last four hours and rsync
#them to the mounted NAS
find  /Volumes/DRIVE1/PATH1/ -mmin -360 -type f -exec rsync -aR {} /Volumes/Backup/snapshot.0/ \;
find  /Volumes/DRIVE1/PATH2/ -mmin -360 -type f -exec rsync -aR {} /Volumes/Backup/snapshot.0/ \;
find  /Volumes/DRIVE2/PATH1/ -mmin -360 -type f -exec rsync -aR {} /Volumes/Backup/snapshot.0/ \;

2. The launchd plist file

I will not be covering how exactly to create a launchd plist file, there is plenty of documentation for creating launch daemons and agents. What I found lacking, however was how to create a launchd plist to run a script at specific intervals. I found plenty of stuff on running at regular intervals, say every X minutes, but nothing good on how to launch at specific times, in this case 8 a.m., 12 p.m., 4 p.m. and 8 p.m. every Monday through Friday, i.e. the launchd plist equivalent to a cron task as such:

0 8,12,16,20 * * 1-5 /usr/local/bin/snapshot.sh

So here is the equivalent launchd plist (and, as a sarcastic aside--Gee, thanks, Apple. This is so much simpler and straightforward.) This is set to run using the "administrator" account. It is the <array> of <dict> that defines the individual run times.

<?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>AbandonProcessGroup</key>
    <true/>
    <key>Debug</key>
    <true/>
    <key>Label</key>
    <string>local.snapshot</string>
    <key>Program</key>
    <string>/usr/local/bin/snapshot.sh</string>
    <key>RunAtLoad</key>
    <false/>
    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>12</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>20</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>2</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>12</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>2</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>2</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>20</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>2</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>3</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>12</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>3</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>3</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>20</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>3</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>4</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>12</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>4</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>4</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>20</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>4</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>5</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>12</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>5</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>5</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>20</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>5</integer>
        </dict>
    </array>
    <key>UserName</key>
    <string>administrator</string>
</dict>
</plist>