Backing up Android with BorgBackup

July 9, 2020 - July 10, 2020

Is there a simple way to backup non-rooted Android phone to a Borg repo? Since the phone is non-rooted, this greatly limits the kind of files that can be accessed and backed up. For instance, if you want to backup a Google Authenticator database, root is needed to access/data/data/com.google.android.apps.authenticator2/databases/databases (see this Android Stack Exchange question). But if you want to only backup data from device user storage, then root is not needed.

In my case, I realized that the only type of data that I really care about on my phone is pictures; they are stored in /storage/self/primary/Pictures and /storage/self/primary/DCIM on my phone. These directories are accessible without root.

The easiest way to use Borg to backup these directories is to mount the smartphone storage to a local filesystem and then use Borg as usual. simple-mtpfs seems to work well for this purpose.

SIMPLE-MTPFS (Simple Media Transfer Protocol FileSystem) is a file system for Linux (and other operating systems with a FUSE implementation, such as Mac OS X or FreeBSD) capable of operating on files on MTP devices attached via USB to local machine. On the local computer where the SIMPLE-MTPFS is mounted, the implementation makes use of the FUSE (Filesystem in Userspace) kernel module. The practical effect of this is that the end user can seamlessly interact with MTP device files.

Backup script

Below is the script that I use to backup smartphone pictures to a remote storagebox. See the previous post about the details of using BorgBackup on macOS.

The overall logic is as follows:

  1. Check that the expected device is connected: simple-mtpfs --list-devices.
  2. Mount device to an existing empty directory: simple-mtpfs --verbose --device 1 /Users/mmxmb/phone_mnt.
  3. Use Borg to backup pictures from /Users/mmxmb/phone_mnt/Pictures and /Users/mmxmb/phone_mnt/DCIM.
#!/bin/sh

export BORG_REPO='ssh://username@username.your-storagebox.de:23/./backup/phone-repo'
export BORG_RSH='ssh -i /Users/mmxmb/.ssh/storagebox_key'
export BORG_PASSPHRASE='VERY_LONG_PASSPHRASE'

# directory where device is mounted
MOUNTPOINT=/Users/mmxmb/phone_mnt
# <number>: <device_name> returned by simple-mtpfs --list-devices
EXPECTED_DEVICE_ID="1: MyDeviceName"

# some helpers and error handling:
info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; }
trap "kill -9 $SIMPLE_MTPFS_PID; echo $( date ) Backup interrupted >&2; exit 2" INT TERM

# if mount directory does not exist, create it
if [ ! -d "$MOUNTPOINT" ]; then
  /bin/mkdir $MOUNTPOINT
fi

# check that the directory is empty
if [ "$(/bin/ls -A $MOUNTPOINT)" ]; then
  info "Can't mount on a non-empty directory $MOUNTPOINT. Exiting."
  exit 1
fi

if [ ! -f /usr/local/bin/simple-mtpfs ]; then
  info "simple-mtpfs is not installed. Exiting."
  exit 1
fi

ACTUAL_DEVICE_ID=$(/usr/local/bin/simple-mtpfs --list-devices | /usr/bin/head -n 1)

if [ "$EXPECTED_DEVICE_ID" != "$ACTUAL_DEVICE_ID" ]; then
  info "Expected device is not connected. Exiting."
  exit 1
fi

# start simple-mtpfs in background
/usr/local/bin/simple-mtpfs --verbose --device 1 $MOUNTPOINT &
SIMPLE_MTPFS_PID=$!

info "Starting backup"

# create a daily backup
/usr/local/bin/borg create \
    --verbose \
   --list --filter=AME \
   --stats --show-rc \
   --compression auto,lzma,6 \
    '::phone-repo-weekly-{now}' \
    "$MOUNTPOINT/Pictures" \
    "$MOUNTPOINT/DCIM"

backup_exit=$?

info "Pruning repository"

# prune the repo
/usr/local/bin/borg prune \
    --list --stats \
    --prefix 'phone-repo-weekly-' \
    --show-rc \
    --keep-weekly 4 \
    --keep-monthly 6

prune_exit=$?

# use highest exit code as exit code
global_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit ))

if [ ${global_exit} -eq 1 ];
then
    info "Backup and/or Prune finished with a warning"
fi

if [ ${global_exit} -gt 1 ];
then
    info "Backup and/or Prune finished with an error"
fi

# send interrupt to simple-mtpfs process
kill -9 $SIMPLE_MTPFS_PID

exit ${global_exit}

The script has to be ran manually since the smartphone has to be connected to the computer in USB file transfer mode (with USB debugging on). It’s not super convenient but manageable.

A more convenient solution for a remote backup would be to build BorgBackup and dependencies on the smartphone and setup a service to do the backup on the schedule. But for a local backup the above solution seem to be pretty optimal.