(* Simple multi-folder local backup script with rsync by Philippe Niquille March 12, 2007 (Switzerland) philippe at niquille dot com version 0.1 You need ----------- - rsyncX (http://archive.macosxlabs.org/rsyncx/rsyncx.html) - growl (http://growl.info/documentation/applescript-support.php) and - "Do something when" http://www.azarhi.com/Projects/DSW/ This app executes your script when a drive gets mounted. Notes ------- - syncs local and remote folders (no security check) to any locally available folder (like external HD's) - uses rsync for fast and reliable syncing - no incremental backups BUT - checks folder size difference for minimal security - interacts with growl for stats This script is not the fastest, especially due to several do shell script calls. Todo ----- *) property appName : "My RSync Backup" (* the name of your mounted volume (HD, Thumbdrive, whatever is writable) *) property myDrive : "/Volumes/320GB" (* source folders on your local harddrive or remote server - never add a / or /* as suffix - it won't sync your .* files - use "-e ssh user@mydomain.com:/home" for remote to local backups with SSH. Be sure to configure your SSH-Keys. *) property backupList : {"/Users//Desktop", "/Applications"} (* destination folders (in the same order as backupList items !) on myDrive - leave blank if you want the last folder in the source path to be the backup directory in myDrive root (ex. /Volumes/320B/mydest) - do NOT add a / as prefix - if you add a / as suffix (ex. sample/) your source directory will be copied into ex. mydest (ex. /Volumes/320GB/sample/mydest) *) property destinationList : {"test/", ""} property simulate : false -- for debugging or testing (executes the rsync command with --dry-run) property askFirst : true -- security question, prevents destruction of a backup ready for recovery property logger : true -- log to system.log? property deviation : 0.05 -- security check. Ask if source directory is more than 5% smaller than your current backup dir, prevents destruction -- stop editing here -- set countM to count (backupList) -- how many sources do we have? set countS to count (destinationList) -- how many custom destinations do we have? if countM is not equal to countS then display dialog "Check your configuration! Your source and destination paths are setup incorrectly." with icon 0 end if if askFirst then -- security dialog, aks wether to sync or abort display dialog "Do you want to backup " & countM & " sources? " & implode(" ", backupList) with icon 2 --buttons {"OK", "Abort"} default button 1 end if tell application "System Events" set growlIsRunning to (count of (every process whose name is "GrowlHelperApp")) > 0 end tell if growlIsRunning then tell application "GrowlHelperApp" set the allNotificationsList to {"Done", "Start"} set the enabledNotificationsList to {"Done", "Start"} register as application appName all notifications allNotificationsList default notifications enabledNotificationsList icon of application "Script Editor" end tell end if delay 10 -- sleep and wait for the drive to mount if isMounted(myDrive) then -- is myDrive mounted (does the folder exist in /Volumes) tellGrowl("Start", appName, "Starting backup for " & countM & " sources.. Do NOT disconnect until completed!") repeat with counter from 1 to countM -- the main loop for sources and destinations (should have the same count) set source to get item counter of backupList -- get the current source folder path set dest to get item counter of destinationList -- get the current backup folder path if source does not contain "-e ssh" then -- if the source is not a remote filesystem, we do our security check set sizeSource to round getFolderSize(source) -- get size of local source folder (in MB) if dest is equal to "" then -- do we have a custom destination folder or should we use the default "last" folder in the source path? set sourceFolder to explode("/", source) -- get the last folder in the source path (it will be the backup folder in myDrive) set sourceFolder to get last item of sourceFolder set sizeDest to round getFolderSize(myDrive & "/" & sourceFolder) -- get size of destination folder on myDrive set dest to myDrive else set sizeDest to getFolderSize(myDrive & "/" & dest) -- get size of destination folder on myDrive set dest to myDrive & "/" & dest end if if sizeDest is not equal to false then -- did we get a size or is this the first backup (folder doesn't exist yet -> false) set sizeDest to round sizeDest if sizeDest > sizeSource then -- only do security check if the source got smaller than the destination (backup) if (sizeDest - sizeSource) > (deviation * sizeSource) then -- do we have more than x% deviation meaning that source is more than x% smaller than destination display dialog "Source folder: " & sizeSource & "MB Backup Folder: " & sizeDest & "MB " & source & " is more than 5% smaller than your backup folder. Proceed with backup?" with icon 0 -- warn the user and ask for permission to backup, otherwise abort end if end if end if else -- do we have a ssh transfer? if dest is equal to "" then set dest to myDrive else set dest to myDrive & "/" & dest end if end if if simulate then -- do we need to do a dry run (simulation)? do shell script ("rsync -a --dry-run --delete " & source & " " & dest) tellGrowl("Done", "Dry-run of " & source & " to " & dest & " complete.", "") else set output to do shell script ("rsync -a --stats --delete " & source & " " & dest) tellGrowl("Done", "Backed up " & source & " to " & dest, output) end if if logger then do shell script "logger 'Backed up " & source & " to " & dest & "'" end if end repeat else tellGrowl("Start", appName, "Could not find " & myDrive & " - aborting.") -- myDrive could not be mounted end if on isMounted(drive) -- check the existance of a folder (drives are mounted in /Volumes) try do shell script "ls -d " & drive return true -- drives are like folders on error return false end try end isMounted on tellGrowl(myid, myTitle, myMessage) -- the growl notification routine global growlIsRunning if growlIsRunning then -- is growl running? tell application "GrowlHelperApp" notify with name myid title myTitle description myMessage application name appName with sticky end tell end if end tellGrowl on getFolderSize(folder) -- get the folder size with "du" try set sizeFolder to (do shell script "du -sk " & folder & " | awk '{print $1}' ") -- returns the folder size in 1kb blocks set sizeFolder to (sizeFolder / 1024) return sizeFolder -- folder size in MB on error return false end try end getFolderSize on implode(separator, pieces) -- got from http://bbs.applescript.net/viewtopic.php?pid=65358 local separator, pieces set ASTID to AppleScript's text item delimiters try set AppleScript's text item delimiters to {separator} set pieces to "" & pieces end try set AppleScript's text item delimiters to ASTID return pieces --> text end implode on explode(separator, input) -- got from http://bbs.applescript.net/viewtopic.php?id=18377 local separator, input if separator is "" then return false set ASTID to AppleScript's text item delimiters try set AppleScript's text item delimiters to {separator} set input to text items of input end try set AppleScript's text item delimiters to ASTID return input --> list end explode