Added by unreal
Introduction
The idea behind this tutorial is to backup a Mac OS X box to an offsite *nix server as securely as possible.
Here is what I aimed to accomplish:
To achieve all this, we are going to use some interesting technology, mainly MacFuse, SSHFS and Rsync, as per the following diagram:
This method offers a number of security advantages, for example:
Requirements
The backup server can be running any kind of *nix OS (Linux, FreeBSD, OpenSolaris...) as long as the SFTP protocol and certificate-based authentication are enabled. We'll be using a FreeBSD 8.0 machine during this article.
On the OS X side, we'll need to install MacFuse and the sshfs binary command line utility for MacFuse:
Download the sshfs binary and make it executable:
The SSH Keypair
The client needs to connect automatically to the backup server. The usual way to obtain this is to generate a SSH keypair and copy the public key to the backup server.
On the client:
And after that, on the server:
The encrypted image
Mac OS X has a very useful tool to manage disk images that is called hdiutil. We are going to use this tool to create an encrypted password-protected disk image and scp to copy the image to the backup server.
Make sure you use a strong password to protect the image and that it is not necessary to enter a password to copy the file with scp.
The sparse image will start off as a few hundred megabyte file, and will grow as necessary to hold up to 200GB.
The backup script
Copy the following script to the /usr/local/bin folder on your Mac and edit its configuration, mainly the bold lines in the example below:
Run the script with the root account and the backup should start. The script has been designed in such a way as to detect errors and adopt a intelligent response should they occur, and send warning e-mails if a fatal error occurs.
Normal operation should look something like this:
The first backup could take to some if you have lots of data to send or a slow Internet connection. The daily backups should be quite quick thank's to rsync that only sends new and updated files.
Automation and conclusion
Using cron we can automate the backup (to run at 5am, once a day for example):
From now on, you have and a automatic and secured backup of your data to an offsite backup server.
Should you have any questions, please leave a comment below or send me a private message.
Changelog
- 20101019 - first version
The idea behind this tutorial is to backup a Mac OS X box to an offsite *nix server as securely as possible.
Here is what I aimed to accomplish:
- The backup image on the server must be encrypted
- The backup must be started by the client
- The communication channel between the Mac client and the server must be encrypted too
- The backup must be automatic (no user intervention)
- After the initial backup, the differential daily backups must be completed quickly
To achieve all this, we are going to use some interesting technology, mainly MacFuse, SSHFS and Rsync, as per the following diagram:
This method offers a number of security advantages, for example:
- This offsite disk image will be encrypted using AES 128 or 256 bit; it cannot be used if the backup server is compromised
- As the backup procedure is started by the client, it would be very hard to compromise the client from the server
- The SSHFS stack prevents man in the middle attacks as the client knows the SSH fingerprint of the server and will refuse to connect if it changes in any way
Requirements
The backup server can be running any kind of *nix OS (Linux, FreeBSD, OpenSolaris...) as long as the SFTP protocol and certificate-based authentication are enabled. We'll be using a FreeBSD 8.0 machine during this article.
On the OS X side, we'll need to install MacFuse and the sshfs binary command line utility for MacFuse:
- http://code.google.com/p/macfuse/downloads/list
- http://code.google.com/p/macfuse/wiki/MACFUSE_FS_SSHFS
Download the sshfs binary and make it executable:
cp ~/sshfs-static-leopard /usr/local/bin/sshfs
chmod +x /usr/local/bin/sshfs
chmod +x /usr/local/bin/sshfs
The SSH Keypair
The client needs to connect automatically to the backup server. The usual way to obtain this is to generate a SSH keypair and copy the public key to the backup server.
On the client:
# mkdir ~/.ssh
# chmod 700 ~/.ssh
# ssh-keygen -q -f ~/.ssh/id_rsa -t rsa
# scp ~/.ssh/id_rsa.pub username@hostname:~/id_rsa.pub
# chmod 700 ~/.ssh
# ssh-keygen -q -f ~/.ssh/id_rsa -t rsa
# scp ~/.ssh/id_rsa.pub username@hostname:~/id_rsa.pub
And after that, on the server:
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
$ mkdir backups
$ chmod 700 ~/.ssh
$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
$ mkdir backups
The encrypted image
Mac OS X has a very useful tool to manage disk images that is called hdiutil. We are going to use this tool to create an encrypted password-protected disk image and scp to copy the image to the backup server.
Make sure you use a strong password to protect the image and that it is not necessary to enter a password to copy the file with scp.
# hdiutil create -size 200g -encryption AES-128 -fs HFS+J -type SPARSE -volname Offsite_Backup Offsite_Backup
Enter a new password to secure "Offsite_Backup.sparseimage": <yourstrongpassword>
# scp Offsite_Backup.sparseimage username@hostname:~/backups/
Enter a new password to secure "Offsite_Backup.sparseimage": <yourstrongpassword>
# scp Offsite_Backup.sparseimage username@hostname:~/backups/
The sparse image will start off as a few hundred megabyte file, and will grow as necessary to hold up to 200GB.
The backup script
Copy the following script to the /usr/local/bin folder on your Mac and edit its configuration, mainly the bold lines in the example below:
#!/bin/bash
############################################################
# Filename: securebackup.sh #
# Description: secure push backup script for OS X using #
# encrypted AES sparse disk images and sshfs #
# file transfers. #
# Last update: 20100929 #
# Changelog: v1.0 - initial version #
# v1.1 - improved rsync error handling #
# v1.11 - various bug fixes #
# v1.12 - return value bugfix in senderror #
# v1.13 - fixed PATH #
############################################################
#<Config>
BACKUPHOST=backup.server.com
EMAILTO=root@localhost
SSHFSBIN=/usr/local/bin/sshfs
SSHFSMOUNT=/Volumes/$BACKUPHOST
SSHFSPARAM="someusername@$BACKUPHOST:/home/backups"
SPARSEIMGPW=yourstrongpassword
SPARSEIMGPATH=$SSHFSMOUNT/Offsite_Backup.sparseimage
# Space separated list, no trailing slashes.
SYNC_DIR="/net/nas.localdomain/usr/home/exports/backup \
/net/nas.localdomain/usr/home/exports/Web \
/net/nas.localdomain/usr/home/exports/work \
/private/etc \
/private/var/cron/tabs \
/private/var/www \
/Users/unreal \
/usr/local"
#</Config>
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
# Functions
function senderror {
MSG=$1
SPARSEMOUNT=$2
echo "The following error occured:"
echo " $MSG"
echo "Backup quitted with the following error: $MSG" | mail -s "Backup error on $HOSTNAME" $EMAILTO
echo
# We'll try to unmount the filesystems anyway.
echo -n "-> Trying to unmount filesystems ... "
[ "$SPARSEMOUNT" ] && hdiutil detach "$SPARSEMOUNT" 2>/dev/null >/dev/null
sleep 2
umount "$SSHFSMOUNT" 2>/dev/null
RV=$?
[ $RV -ne 0 ] && echo "Fail"
[ $RV -eq 0 ] && echo "OK"
exit 1
}
# Check SSHFS isn't already mounted
echo -n "-> Checking mounts ... "
CHECK=`mount | grep $SSHFSMOUNT`
[ "$CHECK" ] && echo "Fail" && senderror "SSHFS already mounted."
echo "OK"
# Mount SSHFS share
echo -n "-> Mounting SSHFS to $SSHFSMOUNT ... "
[ ! -d "$SSHFSMOUNT" ] && mkdir $SSHFSMOUNT
$SSHFSBIN $SSHFSPARAM $SSHFSMOUNT 2>/dev/null
RV=$?
sleep 2
[ $RV -ne 0 ] && echo "Fail" && senderror "sshfs command returned error $RV."
echo "OK"
# Mounting crypted image file
echo -n "-> Mounting $SPARSEIMGPATH ... "
[ ! -f "$SPARSEIMGPATH" ] && echo "Fail" && senderror "Sparse image file not found."
CHECK=`echo -n "$SPARSEIMGPW" | hdiutil attach -stdinpass $SPARSEIMGPATH 2>/dev/null`
RV=$?
[ $RV -ne 0 ] && echo "Fail" && senderror "hdiutil returned error $RV."
# Work out mount path
SPARSEMOUNT=`echo -n "$CHECK" | grep "Apple_HFS" | awk '{ print $3; }'`
echo "OK (mounted to $SPARSEMOUNT)"
# Backup
for ONEDIR in $SYNC_DIR; do
if [ ! -d "$SPARSEMOUNT/$HOSTNAME$ONEDIR" ]; then
mkdir -p "$SPARSEMOUNT/$HOSTNAME$ONEDIR"
fi
echo -n "-> Backing-up $ONEDIR ... "
rsync --archive --delete --compress $ONEDIR `dirname "$SPARSEMOUNT/$HOSTNAME$ONEDIR"` 2>/dev/null >/dev/null
RV=$?
[ $RV -ne 0 ] && [ $RV -ne 23 ] && [ $RV -ne 24 ] && echo "Fail" && senderror "rsync returned error $RV." "$SPARSEMOUNT"
echo -n "OK"
[ $RV -eq 23 ] && echo -n " (partial)"
[ $RV -eq 24 ] && echo -n " (partial, some files vanished before they were copied)"
echo
done
# Unmounting
echo -n "-> Unmounting $SPARSEMOUNT ... "
hdiutil detach "$SPARSEMOUNT" 2>/dev/null >/dev/null
RV=$?
sleep 2
[ $RV -ne 0 ] && echo "Fail" && senderror "hdiutil returned error $RV."
echo "OK"
echo -n "-> Unmounting $SSHFSMOUNT ... "
umount "$SSHFSMOUNT" 2>/dev/null
RV=$?
[ $RV -ne 0 ] && echo "Fail" && senderror "umount command returned error $RV."
echo "OK"
exit 0
############################################################
# Filename: securebackup.sh #
# Description: secure push backup script for OS X using #
# encrypted AES sparse disk images and sshfs #
# file transfers. #
# Last update: 20100929 #
# Changelog: v1.0 - initial version #
# v1.1 - improved rsync error handling #
# v1.11 - various bug fixes #
# v1.12 - return value bugfix in senderror #
# v1.13 - fixed PATH #
############################################################
#<Config>
BACKUPHOST=backup.server.com
EMAILTO=root@localhost
SSHFSBIN=/usr/local/bin/sshfs
SSHFSMOUNT=/Volumes/$BACKUPHOST
SSHFSPARAM="someusername@$BACKUPHOST:/home/backups"
SPARSEIMGPW=yourstrongpassword
SPARSEIMGPATH=$SSHFSMOUNT/Offsite_Backup.sparseimage
# Space separated list, no trailing slashes.
SYNC_DIR="/net/nas.localdomain/usr/home/exports/backup \
/net/nas.localdomain/usr/home/exports/Web \
/net/nas.localdomain/usr/home/exports/work \
/private/etc \
/private/var/cron/tabs \
/private/var/www \
/Users/unreal \
/usr/local"
#</Config>
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
# Functions
function senderror {
MSG=$1
SPARSEMOUNT=$2
echo "The following error occured:"
echo " $MSG"
echo "Backup quitted with the following error: $MSG" | mail -s "Backup error on $HOSTNAME" $EMAILTO
echo
# We'll try to unmount the filesystems anyway.
echo -n "-> Trying to unmount filesystems ... "
[ "$SPARSEMOUNT" ] && hdiutil detach "$SPARSEMOUNT" 2>/dev/null >/dev/null
sleep 2
umount "$SSHFSMOUNT" 2>/dev/null
RV=$?
[ $RV -ne 0 ] && echo "Fail"
[ $RV -eq 0 ] && echo "OK"
exit 1
}
# Check SSHFS isn't already mounted
echo -n "-> Checking mounts ... "
CHECK=`mount | grep $SSHFSMOUNT`
[ "$CHECK" ] && echo "Fail" && senderror "SSHFS already mounted."
echo "OK"
# Mount SSHFS share
echo -n "-> Mounting SSHFS to $SSHFSMOUNT ... "
[ ! -d "$SSHFSMOUNT" ] && mkdir $SSHFSMOUNT
$SSHFSBIN $SSHFSPARAM $SSHFSMOUNT 2>/dev/null
RV=$?
sleep 2
[ $RV -ne 0 ] && echo "Fail" && senderror "sshfs command returned error $RV."
echo "OK"
# Mounting crypted image file
echo -n "-> Mounting $SPARSEIMGPATH ... "
[ ! -f "$SPARSEIMGPATH" ] && echo "Fail" && senderror "Sparse image file not found."
CHECK=`echo -n "$SPARSEIMGPW" | hdiutil attach -stdinpass $SPARSEIMGPATH 2>/dev/null`
RV=$?
[ $RV -ne 0 ] && echo "Fail" && senderror "hdiutil returned error $RV."
# Work out mount path
SPARSEMOUNT=`echo -n "$CHECK" | grep "Apple_HFS" | awk '{ print $3; }'`
echo "OK (mounted to $SPARSEMOUNT)"
# Backup
for ONEDIR in $SYNC_DIR; do
if [ ! -d "$SPARSEMOUNT/$HOSTNAME$ONEDIR" ]; then
mkdir -p "$SPARSEMOUNT/$HOSTNAME$ONEDIR"
fi
echo -n "-> Backing-up $ONEDIR ... "
rsync --archive --delete --compress $ONEDIR `dirname "$SPARSEMOUNT/$HOSTNAME$ONEDIR"` 2>/dev/null >/dev/null
RV=$?
[ $RV -ne 0 ] && [ $RV -ne 23 ] && [ $RV -ne 24 ] && echo "Fail" && senderror "rsync returned error $RV." "$SPARSEMOUNT"
echo -n "OK"
[ $RV -eq 23 ] && echo -n " (partial)"
[ $RV -eq 24 ] && echo -n " (partial, some files vanished before they were copied)"
echo
done
# Unmounting
echo -n "-> Unmounting $SPARSEMOUNT ... "
hdiutil detach "$SPARSEMOUNT" 2>/dev/null >/dev/null
RV=$?
sleep 2
[ $RV -ne 0 ] && echo "Fail" && senderror "hdiutil returned error $RV."
echo "OK"
echo -n "-> Unmounting $SSHFSMOUNT ... "
umount "$SSHFSMOUNT" 2>/dev/null
RV=$?
[ $RV -ne 0 ] && echo "Fail" && senderror "umount command returned error $RV."
echo "OK"
exit 0
Run the script with the root account and the backup should start. The script has been designed in such a way as to detect errors and adopt a intelligent response should they occur, and send warning e-mails if a fatal error occurs.
Normal operation should look something like this:
-> Checking mounts ... OK
-> Mounting SSHFS to /Volumes/backup.server.com ... OK
-> Mounting /Volumes/backup.server.com/Offsite_Backup.sparseimage ... OK (mounted to /Volumes/Offsite_Backup)
-> Backing-up /net/nas.localdomain/usr/home/exports/backup ... OK
-> Backing-up /net/nas.localdomain/usr/home/exports/Web ... OK (partial)
-> Backing-up /net/nas.localdomain/usr/home/exports/work ... OK
-> Backing-up /private/etc ... OK
-> Backing-up /private/var/cron/tabs ... OK
-> Backing-up /private/var/www ... OK
-> Backing-up /Users/unreal ... OK
-> Backing-up /usr/local ... OK
-> Unmounting /Volumes/Offsite_Backup ... OK
-> Unmounting /Volumes/backup.server.com ... OK
-> Mounting SSHFS to /Volumes/backup.server.com ... OK
-> Mounting /Volumes/backup.server.com/Offsite_Backup.sparseimage ... OK (mounted to /Volumes/Offsite_Backup)
-> Backing-up /net/nas.localdomain/usr/home/exports/backup ... OK
-> Backing-up /net/nas.localdomain/usr/home/exports/Web ... OK (partial)
-> Backing-up /net/nas.localdomain/usr/home/exports/work ... OK
-> Backing-up /private/etc ... OK
-> Backing-up /private/var/cron/tabs ... OK
-> Backing-up /private/var/www ... OK
-> Backing-up /Users/unreal ... OK
-> Backing-up /usr/local ... OK
-> Unmounting /Volumes/Offsite_Backup ... OK
-> Unmounting /Volumes/backup.server.com ... OK
The first backup could take to some if you have lots of data to send or a slow Internet connection. The daily backups should be quite quick thank's to rsync that only sends new and updated files.
Automation and conclusion
Using cron we can automate the backup (to run at 5am, once a day for example):
0 5 * * * /usr/local/bin/securebackup.sh
From now on, you have and a automatic and secured backup of your data to an offsite backup server.
Should you have any questions, please leave a comment below or send me a private message.
Changelog
- 20101019 - first version
Posted on 19/10/10 at 17:08 - 0 Comments...