Some OSS with my Mac, Part 2: Unison

(This is Part 2 of a two-part series on some open source software I’ve incorporated into my primarily Mac-based home office setup. Read Part 1.)

In Part 1 of this series, I talked about how I’d leveraged Synergy to provide a shared mouse and keyboard across the three computers (two Macs and one Linux laptop) in my home office. In this part, I will discuss the solution I employ for keeping files synchronized between the two Macs in my home office. I’ll give you a hint: It’s not Dropbox.

No, it’s another open source application. It’s called Unison (not to be confused with the Mac Usenet reader of the same name), and it provides intelligent two-way synchronization of files between multiple computers across multiple platforms. In my case, I use it only to keep files synchronized between my two Mac laptops, but this is not due to a limitation in Unison; this is only because my Linux laptop is used for other purposes and I don’t really benefit from having all these documents available on it.

Specifically, I used a pre-compiled version of Unison available here for Mac OS X. While this provides a pretty GUI for Unison, there are some oddities to getting it to work as expected that I wanted to document here.

First, let’s take a quick look at “the big picture” for getting this running:

  1. Configure public key SSH authentication between the computers involved. (More on this in a moment.)
  2. Install Unison. (This is as simple as mounting the .DMG and copying Unison into the destination of your choice. I put it in /Applications/Utilities.)
  3. Create the Unison profiles. The Unison GUI only gets you part of the way there—this is one of the oddities.

Configuring SSH Public Key Authentication

I’m not going to go into a lot of detail here; there are tons of sites that provide excellent instructions on how to create a public/private key pair and use that key pair for SSH authentication (here’s one). The primary goal for which we are striving is passwordless logins for Unison, so that it can leverage SSH to encrypt the connections between the computers during file synchronization operations. (You do want secure file synchronization, right?)

In my case, I’m leveraging what’s known as a “star” topology. That is, my two Mac laptops (a 2011 13″ MacBook Pro and a 2009 15″ MacBook Pro) don’t synchronize directly with each other; they synchronize through a third computer, which acts as the “server” in the center of my three-node “star.” Why this topology? I didn’t/don’t want to have to enable SSH connections into either of my Mac laptops, but I already have SSH connections established to the server. (In this case, the server is a Mac Mini running Snow Leopard Server.)

For my setup, then, I needed to configure public key authentication between the laptops and the Mac Mini. For your setup, it will depend upon your topology, and that will drive the specific steps you must take.

Installing Unison

As I mentioned above in the overview, installing Unison is like installing any other Mac app: mount the .DMG, copy the application to the desired location. The prebuilt Mac binaries of Unison that I used are no different, so I’ll skip any more details on that step and move instead to creating the Unison profiles.

Creating the Unison Profiles

While the Unison GUI provides a very sparse UI for creating profiles, you will most assuredly need to supplement the GUI with editing the profile yourself by hand. Profiles created by Unison are stored in ~/Library/Application Support/Unison as *.prf files that can be edited by any text editor (I use TextMate).

Follow this general procedure to get a Unison profile set up properly:

  1. Launch Unison and create the profile. Supply the necessary information (the local root, remote username, remote host, and remote root).
  2. Quit Unison.
  3. Navigate to ~/Library/Application Support/Unison and open the .PRF file that corresponds to the profile you just created.
  4. At the very least, you must add a servercmd statement to the profile, or Unison won’t work at all (you’ll get “fatal error”-type message about not being able to connect to the server). This is where I was stuck for quite some time—see below for more information.
  5. Launch Unison, select the profile you just edited, and open it. It should now work.

Now, let’s talk about the servercmd statement. This statement tells Unison where to find the Unison executable on the remote host. With the prebuilt binaries of Unison that I use (and these are the binaries to which most Google searches will point you, found here), it will prompt you to install a command-line tool. If you opt to install this command-line tool, it places an executable at /usr/bin/unison. Naturally, you would think that this would be the value you’d specify for servercmd, right? You’d be wrong.

The executable at /usr/bin/unison simply launches the Unison GUI. If you try to run that via SSH (as Unison will attempt to do when you try to open the profile), it will report an error—you can’t launch a GUI app on a remote system via SSH. This is the root cause of the “unable to connect” or “fatal error” or “server not responding” error messages with Unison: it doesn’t know how/where to find the server-side executable.

The fix is to specify the full path the Unison executable that is buried inside the Mac application package. Let’s say you install Unison (which is an application bundle; bundles look like folders at the command line) to /Applications/Utilities, like I did. In that case, the right binary to point to is this one:

/Applications/Utilities/Unison.app/Contents/MacOS/Unison

When you specify that value for the servercmd statement in the Unison profile, everything works (assuming that your public key authentication via SSH is working as expected).

Therefore, you have two options to make Unison work. Both options involve editing the .PRF file and adding a servercmd statement:

  1. In Option 1, you specify the full path to the Unison executable inside the application bundle (like specified above) in the servercmd statement.
  2. In Option 2, you create a symlink between /usr/bin/unison and the full path to the Unison executable, and specify /usr/bin/unison for the servercmd statement. This is the route I chose.

I highly recommend you read this page on the Unison Wiki, as it provides a wealth of other information on what should or should not be included in your Unison profile. It also reminds you that you must edit your .PRF file in order to make Unison work.

Here’s a sanitized version of the Unison profile that I’m using to keep files synchronized:

root = /Local/Path/To/Files
root = ssh://username@remote.host.com//Remote/Path/To/Files
servercmd = /usr/bin/unison
fastcheck = true
sortnewfirst = true
confirmbigdeletes = true
ignore = Name .FBCIndex
ignore = Name .FBCLockFolder
ignore = Name .Apple*
ignore = Name *.tmp

There are a few more ignore statements in there, but you get the idea. Refer to the Wiki page for a more detailed listing of suggested ignore statements. Oh, and the double forward slash in the second root statement is intentional, not a typo (this is the right syntax).

Keep in mind that this profile assumes that you’ve created a symlink (using ln -s) to the Unison executable inside the application bundle, as I described earlier. If you didn’t create the symbolic link, the servercmd statement must have the full path all the way inside the application bundle.

Once you have your profile configured correctly, you can run the Unison GUI app, open the profile, and you should be off to the races. (In other words, it should work just fine.) In my case, I did an initial synchronization of around 6GB of data in a matter of minutes, and subsequent synchronization operations have been astonishingly fast.

If there are any questions, tips, clarifications, or corrections, please speak up in the comments. Thanks, and I hope you find this useful!

Tags: ,

  1. Andrew Miller’s avatar

    Very cool…I must admit I’m curious as to the underlying reasons to not use either SugarSync (my preference – 6 devices in play with it right now) or Dropbox.

  2. slowe’s avatar

    Andrew, good question! I thought about putting more content in the post about why not Dropbox, but didn’t want to bore the readers. :-) I’m not familiar with SugarSync, but I can tell you give you two reasons why not Dropbox for *this* particular need: 1) not enough space in Dropbox (free plan); and 2) speed. While I do use Dropbox, I wanted a file synchronization solution that would scale to pretty much any amount of storage I needed, and I wanted a file synchronization solution that didn’t require copying out across the Internet to someone’s servers and then back again. With Unison, the traffic stays on my local LAN, leverages my local storage, and is blisteringly fast.

    Does that help? ;-)

  3. seb’s avatar

    Hi,

    I’ve tried both servercmd options, and none works for me. I’m on OS 10.7.2 and have tried most version found on their website.

    I always end up with “Fatal error: Lost connection with the server” and this (GUI started from the CLI) :
    bash: /usr/bin/unison: No such file or directory

    $ ls -l /usr/bin/unison
    -rwxr-xr-x@ 1 root wheel 9344 Feb 1 00:02 /usr/bin/unison

    /usr/bin is first in my $PATH.

    I’m coming from the Linux world and find it absolutely stupid that you can run “Unison” or “unison” from the CLI and it will start the GUI no matter what.

    I must be missing something. Any hint would be greatly appreciated :-)

  4. slowe’s avatar

    Seb, the binary found at /usr/bin/unison only serves to launch the GUI, and therefore can’t be used with the servercmd parameter in the profile. You’ll need to use the binary that’s found inside the Unison application bundle. Depending on where you installed Unison, this would be something like /Applications/Utilities/Unsion.app/Contents/MacOS/Unison. Or, as I suggested in the article, you can symlink that long path to the shorter /usr/bin/unison and then use the shorter path with the servercmd option in the profile.

    Good luck!

  5. seb’s avatar

    Hi,

    Guess this is what happens when you’re trying to hard fixing stuff at 1AM :-)

    Anyway..

    I symlinked as advised :

    ls -l /usr/bin/unison
    lrwxr-xr-x 1 root wheel 46 Feb 1 09:06 /usr/bin/unison -> /Applications/Unison.app/Contents/MacOS/Unison

    Here’s my profile :

    cat Library/Application\ Support/Unison/Unison\ \@\ Serveur.prf
    root = /Users/x/UNISON
    root = ssh://x@x//home/x/UNISON
    servercmd = /usr/bin/unison
    fastcheck = true
    sortnewfirst = true
    confirmbigdeletes = true
    ignore = Name .FBCIndex
    ignore = Name .FBCLockFolder
    ignore = Name .Apple*
    ignore = Name *.tmp

    Still get this :

    2012-02-01 09:11:20.442 Unison[2108:307] Connecting to Unison @ Serveur…
    RSA host key for IP address ‘x.x.x.x’ not in list of known hosts.
    bash: /usr/bin/unison: No such file or directory

    If I try to run /usr/bin/unison manually :

    $ /usr/bin/unison
    2012-02-01 09:14:48.097 unison[2115:307] No Info.plist file in application bundle or no NSPrincipalClass in the Info.plist file, exiting

    Odd..

  6. slowe’s avatar

    Seb, if you symlink as suggested, then you can no longer use /usr/bin/unison to launch the GUI application (as you’ve discovered). If you want to be able to use /usr/bin/unison to launch the GUI application, then undo the symlink and then use the longer path on the servercmd line in your profile.

    As for the SSH error, did you set up public key authentication to your host? That is a required prerequisite to making this work (note step #1 in the article). I don’t go into details here, but there are plenty of other sites that describe how to make this work.

    Good luck!

  7. Seb’s avatar

    Hi, I was definitely too tired :-) I got it to work between two Linux boxes in a breeze, but couldn’t figure out why it didn’t work between my Macbook et another Linux box.. “/usr/bin/unison not found” was simply due to the fact I didn’t install on the target Linux box.. I figured it out immediately after having some sleep :-)

    Sorry for the annoyance, thanks for this post, it works great now :-)

  8. Zach’s avatar

    Great writeup about using Unison on the Mac. Nice work!

    I’ve been using Unison to sync macs to an Ubuntu backend for several years and have come across a number of things that have made it work even more smoothly for me. Maybe some of these will be useful?

    One thing I was worried about was multiple unison sessions attempting to sync at the same time. I’m not sure how unison handles synchronization on the backend, and there are some folders that I definitely wanted updated by only one sync session at a time (such as an iPhoto library). I addressed this using the flock command and wrapped the unison server command in a script that does file locking. You can see it here:
    http://pastebin.com/AHqzdWdk
    And I set my servercmd in the .prf file to “/usr/local/bin/unison-lock.sh zach.lck”
    Subsequent sessions will wait patiently for their turn.

    Another that’s working great for me is running unison in non-interactive mode as a scheduled task. I create a scheduled task in ~/Library/LaunchAgents that runs unison every 20 minutes. That job kicks off a script that invokes unison like this:
    /usr/bin/unison home -batch -prefer newer -ui text -times;

    This runs unison non-interactively favoring newer files whenever there are conflicts.

    I have another folder where automatic resolution of conflicts won’t work for me so I run unison like this:
    http://pastebin.com/2PvSuXg1
    In that case if there are conflicts, the text-mode unison failed and the GUI pops up prompting me to resolve conflicts manually. This rarely happens though.

    Although my server-side unison is on Linux and has no GUI, I did notice that on the Mac, if you run unison with the -server option, it appears to run as a server and without the GUI. You may be able to do that instead of messing around with symlinks and whatnot.

    Hope this is helpful! Nice blog. Keep up the great writing!