It is both inconvenient and less safe to keep backup volumes mounted when not in use. A mounted volume is more prone to corruption, and open file dialogs needlessly spin up mounted volumes, contributing to a user experience latency that is most apparent if the primary drive is solid state.
By creating an /etc/periodic/hourly directory modeled after the existing /etc/periodic/daily directory, and adapting com.apple.periodic-daily.plist, there is a place to put a script that handles mounting and unmounting the Time Machine backup disk, and launching Time Machine.
Scripts that implement nightly tasks can be placed in the /etc/periodic/daily directory, which is run at 3:15 AM by /System/Library/LaunchDaemons/com.apple.periodic-daily.plist. Using sudo in Terminal, create the parallel directory /etc/periodic/hourly and the parallel file /System/Library/LaunchDaemons/com.apple.periodic-hourly.plist.
Then clone the file com.apple.periodic-hourly.plist from com.apple.periodic-daily.plist, replacing each instance of daily by hourly, and replacing the StartCalendarInterval by a StartInterval of 3600 seconds. Specifically, replace:
<key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>3</integer> <key>Minute</key> <integer>15</integer> </dict>with
<key>StartInterval</key> <integer>3600</integer>The syntax for this file is documented by the man page launchd.plist(5). Now, after restart, any script placed in /etc/periodic/hourly will be run every hour as super user.
#!/usr/bin/env python
# place in /etc/periodic/daily to run nightly (or hourly if set up):
#
# sudo cp -f timemachine.py /etc/periodic/daily/667.timemachine
# sudo cp -f timemachine.py /etc/periodic/hourly/667.timemachine
import re
import datetime
import time
from subprocess import Popen, PIPE
# do executes a shell command, returning stdout and stderr
def do(cmd):
p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
(fi, fo, fe) = (p.stdin, p.stdout, p.stderr)
fi.close()
msg = fo.read()
fo.close()
fe.close()
return msg
# mounted checks if vol is mounted
def mounted(vol):
pat = r'/Volumes/(\S+)\s+[(](.+)[)]'
msg = do('/sbin/mount')
vols = dict(re.compile(pat).findall(msg))
return vol in vols.keys()
# disks returns a dictionary associating e.g. 'Flash' to 'disk0s2'
def disks():
pat = r'Apple_HFS\s+(\S+)\s+.+(disk\d+s\d+)'
msg = do('/usr/sbin/diskutil list')
vols = re.compile(pat).findall(msg)
return dict(vols)
# sleep waits a bit
def sleep():
time.sleep(30)
# mount_volume returns pair (now mounted?, was mounted?),
# after attempting to mount volume for "name" as needed
def mount_volume(vol, logfile):
if mounted(vol):
(now, was) = (True, True)
else:
was = False
diskID = disks()
mountable = diskID.keys()
if vol in mountable:
do("/usr/sbin/diskutil mount %s >>'%s' 2>&1" % (diskID[vol], logfile))
sleep()
now = mounted(vol)
return (now, was)
# unmount_volume unmounts volume
def unmount_volume(vol, logfile):
do("/usr/sbin/diskutil unmount '/Volumes/%s' >>'%s' 2>&1" % (vol, logfile))
# run Time Machine
# Time Machine has finished when 'backupd' is no longer running
logdir = '/Local/Backup Logs'
backupd = "/System/Library/CoreServices/backupd.bundle/Contents/Resources/backupd-helper >>'%s' 2>&1"
def start_backupd(logfile):
do("echo 'starting Time Machine' >>'%s'" % logfile)
do(backupd % logfile)
def backupd_running(logfile):
return do("ps -ax | grep [b]ackupd >>'%s' 2>&1" % logfile)
def timemachine(vol):
now = datetime.datetime.now().strftime("%F %H-%M-%S")
logfile = "%s/%s (Time Machine)" % (logdir, now)
(tnow, twas) = mount_volume(vol, logfile)
if tnow:
start_backupd(logfile)
sleep()
while backupd_running(logfile):
sleep()
if not twas:
sleep()
for i in range(1,8):
sleep()
unmount_volume(vol, logfile)
if not mounted(vol):
break
# execute (replace 'Time' by name of Time Machine volume)
timemachine('Time')
Mac OS X Hints
http://hints.macworld.com/article.php?story=20100712085231232