Persistent Remote Filesystems With MacFUSE and sshfs

This article looks at a simple solution to a minor, but very annoying problem -- how to keep remote filesystems mounted across network outages, sleeps and reboots, with no input from the user. The discussion is tied to Apple's OS X operating system, but the principles apply to just about anything with a unix brain.

The Trouble With Samba

One can mount remote filesystems all sorts of ways these days -- samba probably being the most common, at least in mixed operating environments. But to my mind, samba has some drawbacks:

It's Big:  Samba is a very big, very flexible package, ideal for file-sharing solutions in enterprise environments. But what if you're not an enterprise? What if you're just a home user with a simple consumer storage wotsit attached to your home network? Configuration and management of samba servers can be a pain in the ass.

It's Slow Not-So-Fast:  In part because of the flexibility that samba provides, it can often be difficult to tune a samba server for maximum performance, especially when you have samba clients with various operating systems, each requiring their own special tweaks. The result? Samba servers are reliable, but throughput suffers.

It Re-Invents The Wheel:  I'm a big fan of single-solution solutions. In other words, I prefer using one piece of software to solve multiple problems. It cuts down on administration time, limits exposure to security vulnerabilities, upgrade problems, and so forth. And in this case, the alternative (ssh) is ubiquitous, so why not use it?

The Client Perspective

Apple makes mounting samba fileshares pretty simple: hit command-K in the Finder and enter the server location, and voila. Keychain handles your authentication, if any, and for the most part, it just works. However, if (like me) you're on a laptop, and frequently moving around from home to random public wifi access points, you'll quickly get tired of the "Server Connection Interrupted" dialog you get whenever you change APs (or reconnect to the flakey one at your local café). And it also makes the software that relies on those server connections being present quite unhappy; iTunes in particular really doesn't like it when the network share that hosts its library file disappears.

What we really need is a way to keep those remote filesystems mounted that can survive those network outages, and, ideally, know if it's at home or not so it can keep traffic on the local network for better performance. Fortunately, most of the work has been done for us.

Enter Google

Google has released MacFUSE, an implementation of the FUSE specification for OS X. While this alone is excellent, they've also helpfully released an sshfs (that's SSH Filesystem) implementation to go along with it. This allows us to use ssh, something we've got already in OS X, to mount a remote filesystem just like we do with samba. The key difference here is sshfs will detect network outages and remount the remote filesystem when networking is restored. How awesome is that? Very awesome.  [ Note: There are other implementations of sshfs for OS X floating around; having tried a couple of them, I have found google's to be the most stable, feature-rich and well-documented. ]

The Set-Up

To make our network shares completely transparent, we'll first need to configure ssh on the remote server to allow us to log in without a password. This is a piece of cake. On our OS X client computer, we'll open a terminal window and type this:
ssh-keygen -t dsa
This will create what's called a public key, which we'll send to the remote server so it can identify us when we try to log in. Here's how we do that, again at the terminal window command-prompt:
cat ~/.ssh/id_dsa.pub | ssh greg@example.automagick.us " cat - >> ~/.ssh/authorized_keys2"
Remember to replace 'greg' and 'example.automagick.us' with your login name and the name of your file server. :)
Now we can test our login, and we should not longer be prompted for a password:
ssh greg@example.automagick.us
The other thing we need to do is make sure this server is accessable from the Internet, so we can mount the filesystem when we're not at home. There are various ways to go about this, all of them beyond the scope of this article. For our purposes, we'll assume the server is directly accessable from the internet.

Install MacFUSE and sshfs

Now that ssh is ready, we can install the MacFUSE and sshfs packages. You can get them from Google here. Be sure to poke through the FAQ, as there are many helpful tidbits to be found there. Once we've got sshfs installed, we can move straight on to:

The Mount Script

#!/bin/sh 

# where to find the airport utility
AIRPORT=/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport

# the sshfs executable
SSHFS=/Applications/sshfs.app/Contents/Resources/sshfs-static-10.5

# Your home network's SSID, signalling that internal hostnames should resolve
HOMESSID=automagick

# function remount( mount_point, user, local_host, remote_host, path, volume_name )
# 
# Uses sshfs to mount a remote filesystem, using either internal or external hostnames
# depending on what SSID airport is reporting.  
#
function remount() {
	VOLNAME=$1
	USER=$2
	LOCAL=$3
	REMOTE=$4
	REMOTEPATH=$5
	TARGET="/Volumes/$VOLNAME"
	if [ -d $TARGET ] 
	then
		if [ "`/sbin/mount|/usr/bin/grep $TARGET`" ]
		then
			/usr/bin/hdiutil eject $TARGET
			sleep 1
		else 
			/bin/rmdir $TARGET
		fi
	fi
	/bin/mkdir $TARGET
	SSID=`$AIRPORT --getinfo| /usr/bin/sed -ne 's/^.*[[:space:]]SSID:[[:space:]]*\(.*\)/\1/p'`
	if [ $SSID != "$HOMESSID" ]
	then 
		HOST=$REMOTE
	else 
		HOST=$LOCAL
	fi
	# XXX: We cannot use 'noappledouble' here if we want to host an iTunes library!
	$SSHFS $USER@$HOST:$REMOTEPATH $TARGET -oreconnect,noappledouble,volname=$VOLNAME
}

remount 'automagick.us' 'greg' 'fileserv' 'example.automagick.us' '/usr/local/www'
You can download this script here. Save it somewhere handy, like /usr/bin, or in your Applications folder.

So what's going on here? Let's take it line-by-line. First, some configuration stuff:

# where to find the airport utility
AIRPORT=/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport

# the sshfs executable
SSHFS=/Applications/sshfs.app/Contents/Resources/sshfs-static-10.5

# Your home network's SSID, signaling that internal hostnames should resolve
HOMESSID=automagick
The AIRPORT and SSHFS variables are set to the location of the two command-line utilities our script will require. The first, airport, is a handy program that comes with Leopard. It displays information about the current state of the wireless networking client; we'll use it to figure out what access point we're connected to when the script runs. The second, SSHFS, is the command-line executable that comes as part of Google's sshfs package (mentioned in the FAQ; see?). Finally, we specify the name of our own access point, so the script will know to connect to the file server with it's internal server name instead of it's fully-qualified domain name when we're at home.

Next comes the remount() function, where most of the magic happens. Briefly, the function will figure out if the remote filesystem is already mounted, unmount it if it is, and prepare a mount point for sshfs to use. After that, it decides whether to use the internal network hostname or the fully-qualified domain name to reach the server. Finally, it uses sshfs to mount the filesystem.

At the bottom of the script is the line that calls the remount() function:
remount 'remote_files' 'greg' 'fileserv' 'example.automagick.us' '/usr/local/www'
This says, "mount the directory '/usr/local/www', which is on the server named 'fileserv', using the login name 'greg'." The directory will be accessable on our mac as /Volumes/remote_files/. If we're not at home, we'll mount the directory from example.automagick.us, which is how the file server is known on the internet. And we can add as many such lines as we like.

A Note About Internal Vs. External Networks

Unfortunately, there's no simple solution for handling when we mount a filesystem at home, and then go out, or vice versa. That's because our location is only determined once -- when the script is run. Fortunately all we have to do is re-run the script when we transition in or out of our home network, and the filesystem will be remounted with the appropriate server name for our new location. [Note: There is a way to handle this situation as well; watch for updates.]

Putting It All Together

So now we have a script that will mount our website's directory from our file server, regardless of where we are in the world. All that's left is to run the script. Presuming you saved the script to /usr/bin:
/usr/bin/sshfs_automount
Now we have persistent, transparent access to our file server's website files, in the directory /Volumes/remote_files. It even shows up in the Finder. :) Pretty sweet, right? But as it is now, we still have to run the script manually. Better to launch the script when we login, so that the filesystem will be mounted automagickally. Simply open up System Preferences, click on Accounts, and go to your acount's Login Items, and add the script. Done!

So now we never have to mess with remote network shares again. As soon as we log in to our mac, the filesystems we specify at the bottom of our script will connect. If our network goes down, or we move access points, the filesystems will reconnect. And if we're at home, we'll keep all our traffic on our local network, making things even faster. Oh, and all our data is encrypted coming to and from the server.

License

Creative Commons License
All source code, tools and scripts on http://automagick.us is Copyright © 2007 - 2010 Greg Boyington, and licensed under a
Creative Commons Attribution-Share Alike 3.0 United States License, except where otherwise noted.