The long lost art of automatic file synchronization with unison via ssh

I guess one of the reasons why many people use dropbox is, that it makes file synchronization trivially easy for the end-user. However, it only provides limited space and you have to trust all your data to a third party (or use some kind of encrypted files, which makes it non-trivial). But thanks to unison and ssh, you can easily implement automated and secure file-synchronization across several computers.

Here is a little how-to for a simple desktop-laptop-setup. First make sure that you have installed the same version of unison on both computers. Then create the directory ~/.unison and a file named default.prf in it; this is the profile unison will use when invoked without a specified profile. This is how it could look like:

# .unison/default.prf Unison preferences file

# First define the roots for synchronization; all further paths will be
# relative to these.

# my home directory
root = /home/deaddy
# the home directory on my laptop dh0
root = ssh://dh0/home/deaddy

logfile = /home/deaddy.unison/unison.log

# now include another preference file, containing a list of directories you
# want to synchronize
include common

And here is what my common-file looks like:

# .unison/common containing paths to synchronize in default profile
# every file or folder you want to have synchronized is just appended by
# path = folder or path = file. Remember that these are relative to your root
# specified in the .prf-File

path =.code
path = work
path = documents
path = .vimrc
path = .surf/bookmarks.txt

# I keep some scripts in .local/bin
path = .local
# However, webkit does save some data in .local which I don't want to have
# synchronized
ignore = Path .local/share/webkit

# since I have the same unison setup on my laptop (just a different hostname in
# the remote root) I want to be able to change this list on both computers and
# have it changes propagated to the other one

path = .unison/common

Actually my list is a bit longer, but this should be enough to give you the idea. Now you have everything set up to run unison for the first time. It will notice you, that there were no archive files found, which is normal for the first run, so just confirm with return. Now unison will ask you which file you want to have synchronized in which direction; however, this is kind of cumbersome if most files are identically, so you can reduce the noise by aborting and invoking unison -auto in order to see only conflicting files.

When you have synchronized the folders, you might want to automate this task. So first we generate a new ssh-key for this task:

ssh-keygen -t rsa -f ~/.ssh/unison_rsa

When asked for a passphrase, just hit return, since you won't be able to enter a passphrase in the cronjob. Now copy the contents of ~/.ssh/ to your ~/.ssh/authorized_keys on the remote host. You should be able to login on the remote machine via ssh -i '~/.ssh/unison_rsa' . If this fails, you should debug ssh (Is public key authentication enabled? Is the public key correctly copied?), else we can continue. Since this key is not password-protected, edit the ~/.ssh/authorized_keys again and prepend command="unison -server" to the newly added key. This will restrict logins to this command. The -server argument is necessary so unison on the remote host can act as an server for your client. As my friend Stefan pointed out, one can improve the restrictions further, i.e. one could prepend

command="/usr/bin/unison -server",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty.

Now we're almost done. Enter crontab -e into your shell and add a cronjob like this (here it synchronizes every 10 minutes):

*/10    *    *    *    *    unison -batch -dumbtty -sshargs '-i /home/deaddy/.ssh/unison_rsa' 1>/dev/null 2>&1

Replace /home/deaddy/.ssh/unison_rsa with the path to the newly generated private key, and you're good to go. Somehow my crondaemon does not like output, so I redirect standard out to /dev/null, and standard error to standard out. Thanks to dasjoe for this hint. Also I ran into trouble without -dumbtty which tells unison, that the terminal (i.e. the crondaemon) does not support special characters. Finally -batch tells unison to synchronize all trivial changes, so if you're editing a file only on one computer, the changes will be propagated to the other one without your interaction. However conflicting changes still need to be resolved manually. For this just use unison -auto .

I now have unison running for years, however I never came around to automate it, because the cronjob won't load environment variables like your ssh-agent. However, by using the -sshargs argument I now can use a specific key. But now I have finally a fully automated synchronization of all my important files between my laptop and desktop, and thanks to dyndns and ssh I can also synchronize them when I'm not at home, without worrying that the connection is unsafe. Also, unison uses the rsync-algorithm, so after the first run it's quite fast (searching about 50G of files for changes takes only a few seconds) and uses little bandwidth.