Using Unison with Android over USB

For some time, I've been happily using Unison in conjunction with my Android phone's USB mass storage function to synchronize files between my phone and my desktop. It was simple: I'd plug in my phone with USB and enable the SD card to be used as a mass storage device, then mount it in Linux and run Unison as if the phone was a local folder (with appropriate tweaks to support the FAT filesystem).

Alas, my phone was getting on in years (or months, as it is in tech), and with support long dropped and capacity nigh exhausted, I had to upgrade. With my new phone I've been promoted to the “new hotness” that is Android 6 Marshmallow, but one of the functions that was dropped along the way was the ability to expose the SD card as mass storage over USB. Admittedly it wasn't a perfect solution, requiring unmounting the SD card within Android itself while using it over USB, but the current approaches introduce their own deficiencies. In any case, my previous workflow wasn't going to work anymore.

Of course I didn't want to stop using Unison, or have Unison re-copy everything, or even merely re-scan everything. I wanted to do the sync using Unison's existing database (because all the files are unchanged—it's literally the same SD card) over the USB connection for speed, and for such a short, direct connection, encrypting it is overkill (and an unnecessary overhead). The solution that I came up with was to expose the Android filesystem via FTP over Ethernet-over-USB tethering, mount that as a remote filesystem on Linux, and tell Unison where to look.

There are two key programs I've found to work well with this setup: Primitive FTPd on the Android side and GVfs on the desktop side. Primitive FTPd is quite straightforward, is open-source, and available in Google Play or in the F-Droid repositories. I found GVfs to be the better choice in Linux after having issues with curlftpfs failing on filenames containing percent-sign (%) characters. (If I was interested in employing encryption here, using sshfs might've also been a valid alternative.) The procedure goes like this:

  1. Connect the USB cable to the phone and enable USB tethering in Android settings (under More/Tethering & protable hotspot). Depending on what network management subsystem you have running, the desktop will automatically connect to the new network, or you'll have to enable it.

  2. Open Primitive FTPd and start the FTP server.

  3. On the desktop, mount the phone's remote FTP filesystem using gvfs-mount (for convenience, start by defining some variables), and then cd into the mount location:

    FTPIP=<IP of phone on the USB-tether network>    # E.g.
    FTPPORT=<Port the FTP server is listening on>    # E.g. 12345
    FTPUSER=<Username for FTP login>
    WORKINGDIR=<Folder on phone to sync>             # E.g. sdcard/Documents
    gvfs-mount ftp://${FTPUSER}@${FTPIP}:${FTPPORT}
    cd /run/user/`id -u`/gvfs/ftp\:host\=${FTPIP}\,port\=${FTPPORT}\,user\=${FTPUSER}/"${WORKINGDIR}"
  4. Run Unison. The trick to getting it to use the prior database even though the mount point has changed (and avoid retransmitting everything for comparison) is to make use of the -rootalias option. In my case, it's invoked something like this:

    unison -log=false -fat -fastcheck true ~/<Folder on desktop to sync> `pwd` -rootalias "//`hostname`/`pwd` -> //`hostname`/<Old path to sync>"

    In my case, <Old path to sync> was a folder under /media/brendon/PHONESD (note the preceding slash indicating the root folder is necessary). Unison should behave like everything's normal.

  5. After synchronization is done, close Unison and unmount the remote FTP filesystem:

    gvfs-mount -u ftp://${FTPUSER}@${FTPIP}:${FTPPORT}
  6. Stop the FTP server on the phone, and disconnect.

This has worked pretty well for me. I can even use the Ethernet-over-USB tethering optionally—the procedure also works fine over wifi connections. (Do note, though, that all file data is transmitted unencrypted, so you need to be confident that your local wifi is secured before trying this.)

Oh, and one other benefit of this approach: it doesn't need root.

Comment to add? Send me a message: <>

← Previous | Next →