BackupRestoreUsers script. Automatically backup user home folders during a Casper erase and image!

loceee
Contributor

Hey friendos,

Here's a little doozy we've been using for a while.

You have problem.You environment uses local accounts, or just cached mobile directory accounts directed to the local HD. You want to re-image but have to manually backup the user's home. That sucks.

Welcome BackupRestoreUsers.sh (worst title ever).

https://interpublic.app.box.com/s/wu5zud1j2yyd0mqpcq8y

It can be copied to a sharepoint or external hard drive and run interactively via the .command wrapper scripts without modification to backup all homes on a Mac.

OR

You can make two copies of the script, one for backup and one for restore. Then hard code the mode in the script (caspermode=true, mode="restore"/"backup")

Configure cdpmode=true, and set your options and credentials. (It will search mount points when netbooted for your CasperShare, then use that server to backup user homes to). Make a sharepoint on your CDPs for backing up user homes (maybe called UserBackup)... the script will mount it. (we just use inexpensive USB drives connected to the MacMinis at each location as we don't do bulk imaging often).

Upload the scripts via Casper Admin, and set them to...

BackupRestoreUsers-backup.sh - run before
BackupRestoreUsers-restore.sh - run after

Add them to a imaging configuration that erases and reimages. The workflow has timeout which defaults to backup all users (apart from the excluded ones hardcoded in the script).

Netboot your CEO's Macbook Air *, run an erase imaging workflow, follow the prompts and backup and restore their user data without out leaving CasperImaging!

* not a great idea

-- DISCLAIMER - use at your own risk! No warranty! Code is ugly disclaimer!

I do plan on opensourcing this, along with my patch management system very soon so it can be refined.

#!/bin/bash
version="0.98"
#
# BackupRestoreUsers
#
# INTERACTIVE MODE
# ----------------
# usage:
# 
# configure your backuprepo as below (default: backuproot="$(dirname $0)/_UserBackups")
#
# copy the script folder to a share, then launch script via the xxx.command wrappers.
#
# CASPER IMAGING MODE
#--------------------
# usage:
# as casper imaging is a little difficult with it's scripting workflows it goes like this.
#
# configure your backuprepo as below (either backuproot=/Volume/LocalHD, afpurl, smburl)
# or
# use the network segment section so you can find the nearest backupserver automatically.
#
# set 'caspermode=true'
# uncomment 'mode="backup"'
#   save as 'BackupRestoreUsers-backup.sh'
# do the same for 'mode="restore"'
#   save as 'BackupRestoreUsers-restore.sh'
#
# you should now have 2 scripts, a backup and restore mode script.
#
# upload both Casper Admin repo.
#
# allocate it to a configuration
#
# get info set the -backup.sh to run BEFORE, and -restore.sh to run AFTER
#
#
# bugs. GUI can get stuck on osascript. sometimes crashes? more testing.
#
# more info lachlan.stewart@interpublic.com
##############################################################
#
# 0.1 casperimaging spin off - BackupRestoreUsers.sh was getting to big for it's boots
# 0.2 added initial gui and user exclusion
# 0.3 merged functionality from old script -- can now run interactively 
# 0.9 testing release
# 0.91 added functionality to deal with alternate user paritions that are mounted with fstab, and flags for /Users/.nobackup (automated wipe)
# 0.92 changed to ditto due to some bugs in tar - enabled compression by default
# 0.93 compression off, faster without
# 0.94 another day, another method.. now will create a spareimage and rsync. this should be most robust
# 0.95 fixed flush users bug, discovers caspermount server to do away with network segment code. assumes you are using cdp for userbackup location

###############################
#
# configure from here
#
###############################

caspermode=false            # enable casper imaging mode (auto - nooptions to restore a different backup, logs log and doesn't recognise command switches)
#mode="backup"                # set mode for use with casper - set priority to run before
#mode="restore"               # set mode for use with casper - set priority to run after

userfolder="/Users"
altuserpart="/Volumes/Data"   # watch for this user partition/path and use if seen (good if using fstab method to mount a userpartion eg. "/Volumes/UserHD/Users")
excludedusers="^.|Shared|Deleted Users|admin|Library"    # first line excludes all files starting with "." add anymore here...
restoreto="$userfolder"

showlog=true            # open the log during

rsyncexcludes=( ".Spotlight-V100" ".fseventsd" ".TemporaryItems" "**/Library/Application Support/Google/Chrome/Safe Browsing Download/**" ) # exclude any know troublesome files from copy

debug=false             # dump to debuglog - very verbose - just for me.

###############################
#
# backup repo config
#
###############################

#
# cdp mode
#

cdpmode=false
caspersharename="CasperShare" # share name to grep mount for, find your cdp mount
userbackupcredentials="userbackup:userbackup" # username:password to mount userbackup share from mounted cdp
userbackupshare="UserBackup"

#
# manual backuproot
#

backuproot="$(dirname $0)/_UserBackups"       # backup to ./_UserBackups/ in this script folder

# single network destinations
# ----------------------------
#    afp://[username:password]@rhost[:port]/volume
#afpurl="afp://username:password@servername/backup"
#   //[domain;][user[:password]@]server[/share] path
#smburl="//username:password@servername/backup"

# network segment specific destinations
# --------------------------------------
# edit the xxx.xxx.xxx and associated xxxurl=
# 
# or (comment out networksegment to disable)

#networksegment=$(ipconfig getifaddr en0 | cut -d. -f1-3)   # ip and cut last quad for network segment

if [ "$networksegment" != "" ]
then
    case $networksegment in
        10.132.11 )
            # site: BNE SMT
            afpurl="afp://userbackup:userbackup@10.132.11.18/UserBackup"
            ;;
        10.128.15 )
            # MEB 520BST 
            afpurl="afp://userbackup:userbackup@10.128.15.19/UserBackup"
            ;;
        10.132.9 )
            # MEB McCann    
            afpurl="afp://userbackup:userbackup@10.132.9.21/UserBackup"
            ;;
        10.132.108 | 10.128.20 | 10.132.3 | 10.132.12 )
            # SYD McCann/CMG
            afpurl="afp://userbackup:userbackup@10.128.20.120/UserBackup"
            ;;
        *)
            # default fallback
            echo "Unhandled network segment -- $networksegment - using default"
            afpurl="afp://userbackup:userbackup@mainserver/UserBackup"
            ;;
    esac
fi

scriptname="BackupRestoreUsers"


#####################################
#
#  End of configurable settings
#
#####################################

#
# some common fuctions
#

secho()
{
    # superecho - writes to log and will display a dialog to gui with timeout (a kludge for UI feedback in CI)
    message="$1"
    dialogtimeout="$2"
    echo "$message"
    echo "$message" >> "$logto/$log"
    [ "$dialogtimeout" != "" ] && osascript -e "tell app "System Events"" -e "activate" -e "display dialog "$message" buttons {"..."} giving up after $dialogtimeout" -e "end tell" 
}

errorHander()
{
    # Error function
    message="$1"
    secho "ERROR: $message"
    osascript -e "tell app "System Events"" -e "activate" -e "display dialog "$message" buttons {"OK"} default button {"OK"} with title "Error"" -e "end tell"
    cp "$logto/$log" "$backuppath/$log.ERROR.log"
    rm "$logto/$log"
    killall "Casper Imaging"
    exit 1
}

askQuestion()
{
    message=$1
    button1=$2  #default
    button2=$3
    timeout=$4  #will return default after
    # we'll follow apple convention... default button should be right bottommost (button1)
    if [ "$timeout" != "" ]
    then
        buttonreturn=$(osascript -e "tell app "System Events"" -e "activate" -e "display dialog "$message


(Default: $button1 in $timeout secs)" buttons {"$button2","$button1"} default button {"$button1"} giving up after $timeout" -e "end tell")
    else
        # no timeout.. important questions
        buttonreturn=$(osascript -e "tell app "System Events"" -e "activate" -e "display dialog "$message" buttons {"$button2","$button1"} default button {"$button1"}" -e "end tell")
    fi  
    [ "$(echo $buttonreturn | grep "gave up:true")" != "" ] && result=1   # timeout, return default 1
    [ "$(echo $buttonreturn | grep -w "$button1")" != "" ] && result=1
    [ "$(echo $buttonreturn | grep -w "$button2")" != "" ] && result=2
    echo $result
}

#
# core brains
#

findServer()
{
    server=$(mount | grep $caspersharename | cut -d'@' -f2- | cut -d':' -f-1)
    # if there was no mount with caspershare, don't set afp url
    [ "$server" != "" ] && afpurl="afp://$userbackupcredentials@$server/$userbackupshare"
}


mountShare()
{
    # this code is for automounting network shares, if running the script via ARD / Casper Imaging / DeployStudio
    # lets try to mount network volume, no error checking here... expecting properly formed urls to be passed to mount_afp/smb
    secho "Mounting the network share..." 1
    share=$(echo "$networkbackuppath" | cut -d'/' -f 4-)
    mountpath="/Volumes/$share"
    mkdir "$mountpath"
    # check if afp or smb
    if [ "$afpurl" != "" ]
    then
        mount_afp "$afpurl" "$mountpath"
        mounterror="$?"
    fi
    if [ "$smburl" != "" ]
    then
        mount_smb "$smburl" "$mountpath"
        mounterror="$?"
    fi
    if [ "$mounterror" != "0" ]
    then
        errorHander "There was a problem mounting the network share $share - mount_afp/smb returned $mounterror"
        exit 1
    fi
    backuproot="$mountpath/_UserBackups"
    [ ! -d "$backuproot" ] && mkdir "$backuproot"
    # end network mount
}

unmountShare()
{
     #if we are using a network share, unmount it
    cd /
    secho "Unmounting $mountpath" 1
    sleep 1
    umount "$mountpath"

    if [ "$?" != "0" ]
    then
        echo "Forcing unmount"
        diskutil unmount force "$mountpath"
        if [ "$?" != "0" ]
        then
            errorHander "There was a problem unmounting $backuproot"
        fi
    fi
}

excludeUsers()
{
    while true
    do
        # generate a dialog message for exlusions
        message=$(
            echo -ne "Would you like to exclude any users?
"
            echo -ne "
"
            echo -ne " Users to backup
"
            echo -ne "-------------------------
"
            echo -ne "
"
            for user in $(ls $userfolder | grep -vE "$excludedusers")
            do
                echo -ne "$user "
                if [ -f "$userfolder/.$user-excluded" ]
                then
                    echo -ne " - EXCLUDED
"
                else
                    echo -ne "
"
                fi
            done
        )
        answer=$(askQuestion "$message" "Start Backup" "Exclude Users" 15)
        if [ "$answer" == "2" ]
        then
            for user in $(ls $userfolder | grep -vE "$excludedusers")
            do
                answer=$(askQuestion "User:

$user
" "Include" "Exclude")
                if [ "$answer" == "1" ]
                then
                    # remove exclusion if it exists
                    [ -f "$userfolder/.$user-excluded" ] && rm "$userfolder/.$user-excluded"
                else
                    # flag them as excluded
                    touch "$userfolder/.$user-excluded"
                fi
            done
        else
            # don't exclude anymore
            break
        fi
    done
}

chooseUserfolder()
{
    # in case you User partition isn't found, GUI to select a difference backup source
    answer=$(askQuestion "Do you want to backup from:


$userfolder" "Yes" "Choose different..." 20)
    if [ "$answer" == "2" ]
    then
        newuserfolder=$(/usr/bin/osascript << EOT
            tell application "Finder"
                activate
                set theFolder to (choose folder with prompt "Choose another /Users folder to backup:")
                return (POSIX path of theFolder)
            end tell
            EOT)
    fi
    # if a new location is chosen, use it
    [ ! -z "$newuserfolder" ] && userfolder="$newuserfolder"
}

chooseBackup()
{
    while true
    do
        # generate a dialog message for backuplist
        message=$(
        echo -ne " Available Backups
"
        echo -ne "-------------------------
"
        echo -ne "
"
        [ -f "$backuproot/.DS_Store" ] && rm "$backuproot/.DS_Store" # just in case someone has been poking around in Users)
        for backupid in $(ls "$backuproot")
        do
            echo -ne "$backupid
"
        done
        )
        answer=$(askQuestion "$message" "Select a Backup..." "Quit")
        if [ "$answer" == "1" ]
        then
            for backupid in $(ls "$backuproot")
            do
                answer=$(askQuestion "BackupID: $backupid" "Next" "Select")
                if [ "$answer" == "2" ]
                then
                    # set new backup
                    uniqueid="$backupid"
                    backuppath="$backuproot/$uniqueid"
                    backupsettings="$backuppath/BackupSettings"
                    return 0                    
                fi
            done
        else
            # jump out 
            return 2
        fi
    done
}

backupUsers()
{
    [ -d "$altuserpart" ] && userfolder="$altuserpart" # if we find alternate user partion, point at it

    if $caspermode
    then
        # if we wish to run an automated image, skipping backup... touch /Users/.nobackup before executing script
        # but double check before skpping the backup.
        if [ -f "$userfolder/.nobackup" ]
        then
            answer=$(askQuestion "### This Mac has been flagged NOT to Backup ###

($userfolder/.nobackup exists)" "Don't Backup - WIPE IT ALL" "Remove flag and continue..." 30)
            if [ "$answer" == "1" ]
            then
                # flag to save us checking for backups to restore post imaging
                touch "/tmp/.nobackup-$uniqueid"
                return 0
            else    
                # remove the flag and run as normal
                rm "$userfolder/.nobackup"
            fi
        fi

        answer=$(askQuestion "Do you want to backup user data on this Mac?" "Backup" "Don't Backup" 20)
        if [ "$answer" == "2" ]
        then
            answer=$(askQuestion "Are you really sure?

All user data will be LOST!" "Backup" "Wipe it ALL!")
            if [ "$answer" == "2" ]     
            then
                # flag to save us checking for backups after restore
                touch "/tmp/.nobackup-$uniqueid"
                return 0
            fi
        fi
    fi

    # Check to see if there is an existing backup for this Mac
    if [ -d "$backuppath" ]
    then
        # check to see if it's complete
        if [ "$(defaults read $backupsettings BackupComplete)" != "1" ]
        then
            # if there is an incomplete backup, prompt... default to overwrite.. or give option to abort and sort it out.
            answer=$(askQuestion "There is an INCOMPLETE backup, would you like to overwrite it?" "Backup and Overwrite" "Abort" 20)
            [ "$answer" == "2" ] && errorHander "There is already a backup @ $backuppath ... Aborting..."
        else
            # if there is an COMPLETE backup, prompt... default to abort.. or give option to overwrite...
            if [ "$(defaults read $backupsettings HasBeenRestored)" == "1" ]
            then
                answer=$(askQuestion "There is an existing COMPLETE backup which HAS been restored, would you like to overwrite it?" "Backup and Overwrite" "Abort" 20)
                [ "$answer" == "2" ] && errorHander "There is already a backup @ $backuppath ... Aborting..."
            else
                answer=$(askQuestion "WARNING! There is an existing COMPLETE backup which HAS NOT been restored, would you like to overwrite it?" "Abort"  "Backup and Overwrite" 60)
                [ "$answer" == "1" ] && errorHander "There is already a backup @ $backuppath ... Aborting..."
            fi
        fi
        # remove the old / existing backup
        rm -R "$backuppath"
    fi

    chooseUserfolder

    excludeUsers

    answer=$(askQuestion "Would you like to flush user Trashes?" "Flush" "Don't Flush" 5)
    if [ "$answer" == "1" ]
    then
        flushtrash=true
    else
        flushtrash=false
    fi

    #answer=$(askQuestion "Would you like to flush user Caches?" "Flush" "Don't Flush" 5)
    #if [ "$answer" == "1" ]
    #then
    #   flushcache=true
    #else
    #   flushcache=false
    #fi

    # make paths and copy the logs to the server so we don't run out of space
    mkdir "$backuppath"
    mkdir "$backuppath/logs"
    cp "$logto/$log" "$backuppath/logs/$log"
    rm "$logto/$log"
    logto="$backuppath/logs"

    secho
    secho "Backing up $uniqueid"
    secho
    secho "Scanning and sizing home folders ..." 2
    secho "--------------------------------------"
    secho

    # open the log in console for more feedback
    [ $showlog ] && open "$logto/$log"

    usercount=0
    totalsize=0

    # get all the user folders in /User, process them and get size in bytes
    for user in $(ls $userfolder | grep -vE "$excludedusers")
    do
        if [ ! -f "$userfolder/.$user-excluded" ] # check if they are excluded
        then

            if $flushtrash
            then
                secho "Flushing Trash for $user..."
                rm -R "$userfolder/$user/.Trash"
            fi

            #if $flushcache
            #then
            #   secho "Flushing Trash for $user..."
            #   rm -R "$userfolder/$user/Library/Caches/"
            #fi

            secho "Sizing home for $user..."
            size=$(( $(du -sk "$userfolder/$user" | awk '{print $1}') * 1024 ))
            secho "$user - $(($size  / 1024 / 1024 )) MB" 2 
            userarray[$usercount]="$user"
            sizearray[$usercount]="$size"
            totalsize=$(( $totalsize + $size ))
            (( usercount ++ ))
        else
            secho "$user - EXCLUDED"
        fi
    done

    # get free space on back up disk
    backupfree=$(( $(df -k "$backuproot" | tail -n1 | awk '{ print $4 }') * 1024 ))

    secho "----------------------------------------------------------------------------------"
    secho "Total for $usercount user(s) is $(( $totalsize / 1024 / 1024 )) MBs - Free space on backup volume is $(( backupfree / 1024 / 1024 )) MBs" 2
    secho "----------------------------------------------------------------------------------"
    if [ $totalsize -gt $backupfree ]
    then
        answer=$(askQuestion "There may not be enough free space on Backup volume" "Continue" "Stop" 30)
        if [ "$answer" == "2" ]     
        then
            # error out ...
            errorHander "There is not enough space on the backup volume."
        else
            secho "WARNING: Ignoring free space error... I sure hope you know what you are doing!"
        fi
    fi

    before=$(date +%s)
    backuptogo=$totalsize
    for (( i = 0 ; i < $usercount ; i++ ))
    do
        user="${userarray[$i]}"
        usersize="${sizearray[$i]}"
        usersettings="$backuppath/Users/$user/UserSettings"
        # write some details about the user
        defaults write $usersettings UserSize -string $usersize
        # this reads the local ds via localonly (reads ds on target disk - not the system we are booted from)
        networkusertest=$(dscl -f "$dslocal" localonly  -read "/Local/Default/Users/$user" AuthenticationAuthority | grep LocalCachedUser) 
        if [ "$networkusertest" != "" ]
        then
            defaults write $usersettings NetworkUser -bool true
            usertype="Network User"
        else
            defaults write $usersettings NetworkUser -bool false
            usertype="Local User"
        fi
        secho
        secho "Backing up ($(( $i + 1 ))/$usercount): $user - $((  $usersize / 1024 / 1024 ))/$((  $backuptogo / 1024 / 1024 )) MBs - $usertype" 2
        secho "-------------------------------------------------------------------"
        mkdir -p  "$backuppath/Users/$user"
        imagesize=$(( $usersize + (( $usersize / 4 )) )) # add 25% of padding to vol size
        # create a sparse image on the backup store
        secho "Creating image for $user of size $(( $imagesize / 1024 / 1024 )) MBs..." 1
        hdiutil create -size ${imagesize} -type SPARSE -fs HFS+J -volname _backup_$user "$backuppath/Users/$user/$user.sparseimage" 2>> "$logto/$log"
        error="$?"
        if [ "$error" != "0" ]
        then
            errorHander "Error creating backup image for $user - hdiutil returned error: $error"
        fi
        # attach our new image
        hdiutil attach -owners on "$backuppath/Users/$user/$user.sparseimage" 2>> "$logto/$log"
        if [ "$error" != "0" ]
        then
            errorHander "Error attaching backup image for $user - hdiutil returned error: $error"
        fi
        # write our rsync exclude file
        for rsyncexclude in ${rsyncexcludes[@]}
        do
            echo "$rsyncexclude" >> /tmp/rsync-excludes.txt     
        done
        # rsync all data
        rsync $rsyncopts -aE --log-file="$logto/$log" --exclude-from="/tmp/rsync-excludes.txt" "$userfolder/$user/" "/Volumes/_backup_$user/$user/"
        error="$?"
        rm /tmp/rsync-excludes.txt  

        # rsync error handler - we can ignore a couple of common errors
        if [ "$error" != "0" ]
        then
            case $error in
                23)
                    secho "rsync error 23 - some files were inaccessible (probably in use)"
                    ;;
                24)
                    secho "rsync error 24 - some files disappeared during rsync"
                    ;;
                12)
                    errorHander "Problem backing $user, out of disk space"
                    ;;
                *)
                    errorHander "Problem backing up $user, rsync returned error: $error"
                    ;;
            esac
        fi
        secho "Ejecting /Volumes/_backup_$user ..."
        diskutil eject "/Volumes/_backup_$user"
        [ "$?" != "0" ] && errorHander "There was a problem ejecting /Volumes/_backup_$user"
        # decrement our backupdata to go...
        backuptogo=$(( $backuptogo - $usersize ))   
    done
    after=$(date +%s)
    totalsecs=$(( $after - $before ))
    #  echo "Completed in $(($diff / 60)):$(($diff % 60)) min:secs"
    totaltime="$(date -r $totalsecs +%M:%S) secs"

    [ $showlog ] && killall Console

    defaults write $backupsettings BackupDate -string "$(date)"
    defaults write $backupsettings TotalTime -string "$totaltime"
    defaults write $backupsettings TotalSize -string "$totalsize"
    defaults write $backupsettings HasBeenRestored -bool false
    defaults write $backupsettings BackupComplete -bool true

    secho
    secho "=========================================="
    secho " Completed $(( $totalsize / 1024 / 1024 )) MBs in $totaltime" 3
    secho "=========================================="
    secho "finished: $(date)"
    secho
    secho "NOTE: this script only backs up User folders in $userfolder"
    secho "      any files outside of this have NOT been backup up."
    secho
}


restoreUsers()
{
    # check for no backup flag
    if [ -f "/tmp/.nobackup-$uniqueid" ]
    then
        secho "No backup performed for $uniqueid..." 2
        rm "/tmp/.nobackup-$uniqueid"
        return 0
    fi

    while true
    do
        if [ -d "$backuppath" ] # if there is a backup for this mac restore
        then
            #if its already been restored, error out
            if [ "$(defaults read $backupsettings HasBeenRestored)" == "1" ]
            then
                answer=$(askQuestion "Backup $uniqueid has already been restored" "Continue with Restore" "Choose another backup..." )
                if [ "$answer" == "2" ]
                then
                    # to choose another... reset backup path and reloop
                    backuppath=""
                    continue
                fi
            fi
            backupdate=$(defaults read $backupsettings BackupDate)
            totaltime=$(defaults read $backupsettings TotalTime)
            totalsize=$(defaults read $backupsettings TotalSize)
            if ! $caspermode
            then
                # if we are in interactive mode.. present options
                # read in backup details and make a dialog message
                message=$(
                    echo -ne "Backup Details
--------------------
$uniqueid
Totalsize: $(( $totalsize / 1024 / 1024 )) MBs
Completed in: $totaltime"
                    echo -ne "

"
                    echo -ne "Users
"
                    echo -ne "------
"
                    #
                    # read in all the backups on the store
                    #
                    [ -f "$backuppath/Users/.DS_Store" ] && rm "$backuppath/Users/.DS_Store" # just in case someone has been poking around in Users
                    for backupuserfolder in $(ls "$backuppath/Users/")
                    do
                        user=$(basename $backupuserfolder)
                        usersize=$(defaults read "$backuppath/Users/$backupuserfolder/UserSettings" UserSize)
                        echo -ne "$user - $(( $usersize / 1024 / 1024 )) MBs
"
                    done
                    echo -ne "

"
                )
                answer=$(askQuestion "$message" "Restore" "Choose another backup..." )
                if [ "$answer" == "2" ]
                then
                    # to choose another... reset backup path and reloop
                    backuppath=""
                    continue
                fi
            fi

            # ask to remove backup, do it before we start the restore so it can be unattended
            answer=$(askQuestion "Would you like to delete the backup once it has been restore successfully?" "Delete Backup" "Keep" 20)
            if [ "$answer" == "1" ]
            then
                removebackup=true
            else
                removebackup=false
            fi

            # do the restore
            cp "$logto/$log" "$backuppath/logs/$log"
            rm "$logto/$log"
            logto="$backuppath/logs"

            # open the log in console for more feedback
            [ $showlog ] && open "$logto/$log"

            secho "$uniqueid - Restore size: $(( $totalsize / 1024 / 1024 )) MBs" 2
            secho   
            before=$(date +%s)
            backuptogo=$totalsize
            [ -f "$backuppath/Users/.DS_Store" ] && rm "$backuppath/Users/.DS_Store" # just in case someone has been poking around in Users
            for userroot in "$backuppath/Users/"*
            do
                user=$(basename $userroot)
                usersettings="$backuppath/Users/$user/UserSettings"
                usersize=$(defaults read $usersettings UserSize)
                if [ "$(defaults read $usersettings NetworkUser)" == "1" ]
                then
                    usertype="Network"
                else
                    usertype="Local"
                fi
                secho
                secho "Restoring: $user  $(( $usersize / 1024 / 1024 ))/$(( $backuptogo / 1024 / 1024 )) MBs - $usertype account" 2
                secho "-------------------------------------------------------------------"

                hdiutil attach -owners on "$userroot/$user.sparseimage" 2>> "$logto/$log"
                error="$?"
                if [ "$error" != "0" ]
                then
                    errorHander "Error attaching backup image for $user - hdiutil returned error: $error"
                fi
                rsync $rsyncopts -aE --log-file="$logto/$log" "/Volumes/_backup_$user/$user/" "$restoreto/$user/" 
                error="$?"                
                # rsync error handler
                if [ "$error" != "0" ]
                then
                    case $error in
                        23)
                            secho "rsync error 23 - some files were inaccessible (probably in use)"
                            ;;
                        24)
                            secho "rsync error 24 - some files disappeared during rsync"
                            ;;
                        12)
                            errorHander "Problem backing $user, out of disk space"
                            ;;
                        *)
                            errorHander "Problem backing up $user, rsync returned error: $error"
                            ;;
                    esac
                fi
                secho "Ejecting /Volumes/_backup_$user ..."
                diskutil eject "/Volumes/_backup_$user"
                [ "$?" != "0" ] && errorHander "There was a problem ejecting /Volumes/_backup_$user"
                # decrement data
                backuptogo=$(( $backuptogo - $usersize ))
            done
            after=$(date +%s)
            totalsecs=$(( $after - $before ))
            totaltime="$(date -r $totalsecs +%M:%S) secs"
            defaults write $backupsettings HasBeenRestored -bool true
            secho
            secho "=========================================="
            secho " Completed $(( $totalsize / 1024 / 1024 )) MBs in $totaltime" 3
            secho "=========================================="
            secho "finished: $(date)"
            cp "$logto"/*.log "$target/Library/Logs/"   # copy logs to target when done

            [ $showlog ] && killall Console

            # remove them when done
            if $removebackup
            then
                # delete the backup         
                secho "Deleting $backuppath" 2
                while [ -d "$backuppath" ]
                do
                    # network shares have some locks / trashes that hold up proceedings, just keep hitting it until it dies
                    rm -R "$backuppath"
                    sleep 1
                done
            else
                defaults write $backupsettings HasBeenRestored -bool true
            fi
            break
        else
            # no backups found for restore
            if $caspermode
            then
                secho "There are no backups for $uniqueid" 4
                break
            else
                if [ ! "$(ls $backuproot)" ] # check if the backup folder is empty
                then
                    secho "There are no backups on $backuproot"
                    break
                fi
                # interactive mode choose another backup and loop back
                chooseBackup
                # we want to quit?
                [ "$?" == "2" ] && break 
            fi
    fi
    done
}


################################
# Begin!
################################


datestamp=$(date "+%F_%H-%M-%S")

logto="/tmp"
log="$scriptname-$datestamp.log"

if $debug
then
    # dump debug logs to /
    set -xv; exec 1>/BackupRestoreUsers-DEBUG-$datestamp.log 2>&1
fi

dscl="/usr/bin/dscl"  ### test - we should run dscl from current booted os. assumed that netboot system will be > system if upgrading.
dslocal="/var/db/dslocal/nodes/Default"   # path to local directory data

target="$1"                           # casper passes .. $1 - target, $2 - computername, $3 username --- we only need target path.
[ "$target" == "/" ] && target="" # if we are targetting / - set target to "" so we don't end up with //Users
userfolder="$target$userfolder"       # eg. /Volumes/MacHD/Users
restoreto="$target$restoreto"     #
dslocal="$target$dslocal"         # etc..

# if there we are in cdpmode, find the cdp server, and set url
[ $cdpmode ] && findServer

# just drop afp or smb url into this variable
networkbackuppath="$afpurl$smburl"

# uniqueid - serial or MAC address of en0
uniqueid=$(ioreg -c "IOPlatformExpertDevice" | awk -F '"' '/IOPlatformSerialNumber/ {print $4}')
[ "$uniqueid" == "" ] && uniqueid=$(ifconfig en0 | awk ' /ether/ {print $2}') # if no serial, fallback to MAC address

# set the IFS to cr
OLDIFS=$IFS
IFS=$'
' 

#
# Lets Go!
# 
secho
secho "$scriptname $mode $version - $uniqueid" 1
secho "================================================="
secho "started: $(date)"
secho

# if we have a network path, mount it
[ "$networkbackuppath" != "" ] && mountShare

[ ! -d "$backuproot" ] && errorHander "I can't find $backuproot"

backuppath="$backuproot/$uniqueid"
backupsettings="$backuppath/BackupSettings"

#
# Core handler .. parse mode line etc
#

if $caspermode
then
    # mode is hard coded
    case $mode in
        backup )
            backupUsers
            ;;
        restore )
            restoreUsers
            ;;
    esac
else
    # interactive mode called by wrappers
    case $2 in
        --backup )
            backupUsers
            ;;
        --restore )
            restoreUsers
            ;;
        * )
            usage
            ;;
    esac
fi

# unmount network share
[ "$networkbackuppath" != "" ] && unmountShare

IFS=$OLDIFS
exit
15 REPLIES 15

RaulSantos
Contributor

this is good did you ever look at @rustymyers script https://github.com/rustymyers/BackupRestore

loceee
Contributor

I sure did. Rusty's scripts are great I borrowed a few ideas..!

sean
Valued Contributor

Do you realise you can create a separate partition and use /etc/fstab to mount this as /Users? This would mean you can reinstall as many times as you like without having to move any data!

man fstab

franton
Valued Contributor III

I'm sure he does. However the fstab option fails completely with CoreStorage based drives, such as Fusion drives.

bentoms
Release Candidate Programs Tester

loceee
Contributor

I am across fstab method, but as we moved away from imaging towards thin / package based deployment it's not the method I wanted to go with.

In fact, you will notice the altuserpart variable in the script, it's there to undo our fstab user partitions. If the script finds the Volume path you specify, it will backup /Users from there instead of /Users.

loceee
Contributor

FYI - I've updated the link in the original post to one that actually works. Download that, it comes with .command wrapper scripts that launch it in interactive mode.

https://interpublic.app.box.com/s/wu5zud1j2yyd0mqpcq8y

RaulSantos
Contributor

@loceee So i tested the back up part today nice work. Would but nice to wrap with coco dialog to have backup and restore location asked for. Good Job sir. thanks for sharing.

loceee
Contributor

Hey Raul, I had decided to not use CocoaDialog for this as I didn't want it to have any external dependancies when used in a Casper Imaging netboot environment, hence the use of native applescript. It means the UI is pretty kludgey, but it gets the job done.

By design it was to be used in Casper Imaging mainly to undo our ftstab dual partitioned Macs with minimal interaction, for our L1 guys to be able to reimage Macs to eliminate system software issues... and to migrate to other Macs using the interactive mode.

Make two copies, set one to backup, one to restore mode, run before and after... put them in all your imaging workflows.

It WILL prompt to confirm where you are backing up from, and allow you to select a different user folder.. but with the correct config it should reliably hit the internal volume when netbooted, or a the user partition if your computer was imaged in a dual partition config.

I hadn't had a use case where I would need a user home restored to anywhere else but /Users, so I hadn't coded it... selfish me :) Glad it might be useful to someone!

loceee
Contributor

I've had a few people come back to me, as my instructions are a bit vague.

Here's a couple of screenshots that might help picture how to use it.

https://interpublic.app.box.com/s/r7tfjhgdjxce7zje1xoz

loceee
Contributor

https://interpublic.box.com/s/quqoz45h54v7k1y8ktrj

Link updated as I am going to put some more scripts in here soon.

BGoldman
New Contributor

Hi Loceee,

I have tried to pull up your links to see how you scripted the backup and restore along with repartitioning to a single partition drive. Could you forward the information to me if you still have it? We are also exploring backup to network drives using self service for our level 1 techs. Sounds like everything worked for you.

Thanks, Brad

loceee
Contributor

@Goldman - howdy, since I don't working at IPG anymore those links will be dead. Sorry! I have moved the scripts I share to my github. http://github.com/loceee

ArielN
New Contributor

Hi Loceee,

I was wondering if you still have your instruction on how to setup your BackupRestoreUsers.sh script?

Thank you,
ArielN

Allenhiko
New Contributor

@loceee 5 years on and i am still learning from your scripts Locky!

keep up the good work mate!