diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a764a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +env diff --git a/README.md b/README.md new file mode 100644 index 0000000..b879ec5 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# borg-remote-android +A utility for easy android backups using rsync (locally) and borg (on a remote). + +## Motivation +I was having trouble installing borg (even though there was [borgbackup_on_android](https://github.com/ravenschade/borgbackup_on_android)) and running borg was slow since I was using lzma compression. + +I created this tool so that the copy to the server could be quick, then the server could perform the heavy lifting of encryption/compression. This also allows for less required time on WiFi, since the only network-intensive operations are on the resumable rsync transfer. + +## Usage +In Termux, run: +```bash +./backup-android.sh +``` + +and it will notify you when complete. + +Alternatively, you can add a Termux widget to your home screen (`./setup.sh` auto-installs in the `.shortcuts` directory), so backups can be made with a single tap from the home screen. + +## Installation +Quick, 4 line installation: +```bash +apt update +apt install -y git rsync openssh +git clone https://gitea.austenwares.com/stonewareslord/borg-remote-android +env bash borg-remote-android/setup.sh +``` + +Same installation as a one-liner +```bash +apt update && apt install -y git rsync openssh && git clone https://gitea.austenwares.com/stonewareslord/borg-remote-android && env bash borg-remote-android/setup.sh +``` + +## Updating +The entire config is stored in `env`, which is in `.gitignore`, so running a simple `git pull` will update without losing custom changes. diff --git a/backup-android.sh b/backup-android.sh new file mode 100755 index 0000000..1f13941 --- /dev/null +++ b/backup-android.sh @@ -0,0 +1,57 @@ +#!/data/data/com.termux/files/usr/bin/bash +# Initialize +ABSPATH="$(readlink -f "$BASH_SOURCE")" +cd "${ABSPATH%/*}" || { echo "Cannot change directory" >&2; exit 1; } +test -f ./env && source ./env || { echo "Unable to source user variables in $PWD"; exit 1; } + +# From https://serverfault.com/a/799198 +# Since Termux has no uuidgen command, we need to create one randomly +function uuidgen() { + od -x /dev/urandom | head -1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +# From https://unix.stackexchange.com/a/27014 +function time_diff { + # Find the time difference between the two arguments (in seconds) + local T="$(( "$1" - "$2" ))" + local D=$((T/60/60/24)) + local H=$((T/60/60%24)) + local M=$((T/60%60)) + local S=$((T%60)) + (( $D > 0 )) && printf '%dd ' $D + (( $H > 0 )) && printf '%dh ' $H + (( $M > 0 )) && printf '%dm ' $M + (( $D > 0 || $H > 0 || $M > 0 )) && printf 'and ' + printf '%ds\n' $S +} + +function backup() { + # First, we need to rsync our changes to the host + echo "Running rsync..." + $RSYNC_COMMAND "$SOURCE_LOCATION" "$REMOTE_HOST:$DESTINATION_LOCATION" || return 1 + + # Next, instruct the host to create a borg + echo "Running borg..." + ./run_remote.sh nohup sh -c "cd \"$DESTINATION_LOCATION\"; $BORG_COMMAND \"$BORG_REPO::$HOSTNAME-$(uuidgen)\" \"$DESTINATION_LOCATION\"" || return 2 +} + +# Take wakelock so we don't fall asleep +termux-wake-lock +START_TIME="$(date +%s)" + +# Perform the backup +backup +RESULT=$? +DURATION="$(time_diff "$(date +%s)" "$START_TIME" )" + +echo "Completed $(date +%F-%T)" +echo "Duration: $DURATION" + +if (( RESULT == 0 )); then + termux-notification --title "Backup completed successfully in $DURATION" +else + termux-notification --title "Backup failed in $DURATION" +fi + +# Remove our wakelock +termux-wake-unlock diff --git a/doc/state-diagram.txt b/doc/state-diagram.txt new file mode 100644 index 0000000..67fbd17 --- /dev/null +++ b/doc/state-diagram.txt @@ -0,0 +1,19 @@ +@startuml +Android -> Android: Take wakelock + +note right of rsync + The borg and rsync servers can be the server. + ""BORG_REPO"" and ""REMOTE_HOST"" are configurable. +end note + +Android -> "Rsync Server" as rsync: rsync data to temporary location +rsync --> Android: rsync complete +Android -> rsync: nohup spawn borg backup + +rsync -> "Borg Server" as borg: borg backup temporary directory +borg --> rsync: borg complete +rsync --> Android: borg complete + +Android -> Android: Show success notification +Android -> Android: Release wakelock +@enduml diff --git a/env.dist b/env.dist new file mode 100644 index 0000000..e69de29 diff --git a/run_remote.sh b/run_remote.sh new file mode 100755 index 0000000..d829c81 --- /dev/null +++ b/run_remote.sh @@ -0,0 +1,5 @@ +#!/data/data/com.termux/files/usr/bin/bash +ABSPATH="$(readlink -f "$BASH_SOURCE")" +cd "${ABSPATH%/*}" || { echo "Cannot change directory" >&2; exit 1; } +test -f ./env && source ./env || { echo "Unable to source user variables"; exit 1; } +ssh "$REMOTE_HOST" -- "$@" diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..e9cb268 --- /dev/null +++ b/setup.sh @@ -0,0 +1,75 @@ +#!/data/data/com.termux/files/usr/bin/bash +ABSPATH="$(readlink -f "$BASH_SOURCE")" +cd "${ABSPATH%/*}" || { echo "Cannot change directory" >&2; exit 1; } + +function ask() { + echo -n "$1 [Y/n]? " + read RESP + if [ -z "$RESP" ] || [ "${RESP,,}" = "y" ]; then + return 0 + fi + return 1 +} + +# Read a variable +function configure_var() { + # If the variable already exists, continue without asking questions + if [ ! -z "${!1}" ]; then + echo "It is ${!1}" + return + fi + + DEFAULT="$2" + echo -n "export $1= ($2): " + read RESP + RESP="${RESP:-$DEFAULT}" + # Save the variable + #TODO: This introduces possible bugs when quotes are involved with RESP + echo "# Line added on $(date +%F-%T)" >>env + echo "export $1=\"$RESP\"" >>env + # Load the variable for use in this script + export "$1"="$RESP" +} + +# Begin configuration +echo +echo "Asking config questions..." +echo "IMPORTANT: Trailing slashes matter for paths. Consult the rsync manual for details." +echo "It is recommended to use trailing slashes for all _LOCATION variables." +echo "Also, paths can be relative or absolute" +echo + +# Load existing configurations +test -f env && source env + +configure_var LOCAL_HOSTNAME android1 +configure_var REMOTE_HOST user@example.com +configure_var SOURCE_LOCATION "/storage/emulated/0/" +configure_var DESTINATION_LOCATION "tmp/borg/$LOCAL_HOSTNAME/" +configure_var BORG_REPO "ssh://$REMOTE_HOST/~/borg-backups" +configure_var BORG_COMMAND "borg create --verbose --progress --stats --one-file-system --exclude-caches --compression=auto,lzma --exclude=storage/emulated/0/Android" +configure_var RSYNC_COMMAND "rsync --relative --recursive --links --times --human-readable --partial --info=progress2 --delete --delete-excluded --exclude=storage/emulated/0/Android" + +# Install borg-backup into ~/.shortcuts +mkdir -p ~/.shortcuts +if [ -L ~/.shortcuts/borg-backup ]; then + rm ~/.shortcuts/borg-backup +fi +ln -s "$(readlink -f backup-android.sh)" ~/.shortcuts/borg-backup || echo "Unable to link to ~/.shortcuts" >&2 + +# Ask about authentication +SSH_KEYGEN_COMMAND="ssh-keygen -b 512 -t ed25519 -f $HOME/.ssh/id_ed25519 -N ''" +ask "Generate a new ssh key ($SSH_KEYGEN_COMMAND)" && $SSH_KEYGEN_COMMAND +ask "Run ssh-copy-id on host" && ssh-copy-id "$HOST" + +# Create destination location +echo "Creating DESTINATION_LOCATION" +./run_remote.sh mkdir -p "$DESTINATION_LOCATION" + +# Exclude file +touch exclude +ask "Edit exclude file with vi" && vi exclude + +# Run final checks +ask "Check if borg command exists (on remote)" && { ./run_remote.sh command -v borg || echo "Borg command not found on remote host"; } +ask "Run borg list (on remote)" && ./run_remote.sh borg list "$BORG_REPO"