Skip to content

Commit

Permalink
backup-lib: Backup and restore termux
Browse files Browse the repository at this point in the history
Use special logic to provide better UX for restoring.
Restoring incrementally causes issues like failures with ordinary binaries like 'ls' returning permission denied.
Likely because chmod is not correct or permissions are not preserved when restored from cloud storage.

Downside is a new tar.gz on each backup, so no incremental backup adavantages for backup of termux app itself.

#17
  • Loading branch information
schnatterer committed Jan 12, 2025
1 parent 2331c7a commit a96ae42
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 45 deletions.
52 changes: 33 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,12 @@ Starting from there, backups made with older versions can no longer be restored.

```shell
# Install packages
apt install termux-api tsu openssh
# Either install
apt install rsync
# or
apt install rclone

# Fore remote backups, set up key
ssh-keygen -t ecdsa -b 521
pkg install -y git termux-api tsu rsync # or rclone

# Fore remote backups via rsync, set up key. For rclone see bellow.
ssh-keygen
# Copy key to the remote machine. Password authentication has to be enabled in order to install pubkey on remote machine.
ssh-copy-id -p 22222 -i id_ecdsa.pub user@host
ssh-copy-id -i id_ecdsa.pub user@host # Specify port, if necessary: -p 22222
```

### Usage
Expand Down Expand Up @@ -116,18 +112,36 @@ export LOG_LEVEL='INFO' # Options: TRACE, INFO WARN, OFF. Default: INFO
# Restores all apps from a folder (except termux, because this would cancel restore process! See bellow)
# It the app does not work as expected after restore, consider restoring the keystore (see above)
./restore-all.sh user@host:/my/folder/backup/
```

### Restore termux

# Restore termux separately, if necessary
pkg install rsync
# Uncomment if needed
#RSYNC_ARGS=-e "ssh -p 22222 -i $HOME/.ssh/mykey"
rsync --stats --progress --human-readable -r --times $RSYNC_ARGS user@host:/my/folder/backup/com.termux/data/data/com.termux/files/home/ ~
rsync --stats --progress --human-readable -r --times $RSYNC_ARGS user@host:/my/folder/backup/com.termux/data/data/com.termux/files/usr ../usr/
# Packages are there but don't seem to work, so install them again
for pkg in `dpkg --get-selections | awk '{print $1}' | egrep -v '(dpkg|apt|mysql|mythtv)'` ; do apt-get -y --force-yes install --reinstall $pkg ; done
# If you have been using a different shell, re-enable it, for example:
#chsh -s zsh
* Install termux app either
* manually from backup,
* directly via apk from [fdroid](https://f-droid.org/de/packages/com.termux/) or [github](https://github.com/termux/termux-app/releases/) or
* via an app store app
* [Install packages](#preparation)
* `git clone` this repo.
💡 To make sure no to interfere with the version that is restored from the backup clone it to a different directory

```shell
termux-setup-storage
# Restore .ssh and or `.suroot/.config/rclone/rclone.conf` if necessary
# e.g. mkdir -p .suroot/.config/rclone && cp storage/downloads/rclone.conf
# or cp -r storage/downloads/.ssh .

cd storage/downloads
git clone https://github.com/schnatterer/termux-scripts/
cd termux-scripts/backup
./restore-app.sh com.termux
# or
./restore-app.sh user@host:/my/folder/backup/com.termux --data
# or
./restore-app.sh --rclone remote-encrypted:/my/folder/backup/com.termux --data

# chsh if necessary and restart app
```

### Rclone

* `--rclone` - use `rclone` instead of `rsync`
Expand Down
100 changes: 74 additions & 26 deletions scripts/backup/backup-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,47 @@ function backupApp() {
fi

if [[ "${APK}" != 'true' ]]; then

# Backup to target paths as short as possible to avoid "path too long" errors
backupFolder "/data/data/${packageName}" "${baseDestFolder}/data/"

backupFolder "/sdcard/Android/data/${packageName}" "${baseDestFolder}/sdcard/"

# Backing up to /sdcard/data and /sdcard/media would have been more intuitive
# But: media was added later and migrating data would be too much effort for me right now
backupFolder "/sdcard/Android/media/${packageName}" "${baseDestFolder}/sdcard-media/"
if [[ "${packageName}" != 'com.termux' ]]; then
# Backup to target paths as short as possible to avoid "path too long" errors
backupFolder "/data/data/${packageName}" "${baseDestFolder}/data/"

backupFolder "/sdcard/Android/data/${packageName}" "${baseDestFolder}/sdcard/"

# Backing up to /sdcard/data and /sdcard/media would have been more intuitive
# But: media was added later and migrating data would be too much effort for me right now
backupFolder "/sdcard/Android/media/${packageName}" "${baseDestFolder}/sdcard-media/"
else
backupTermux "$@"
fi
fi

if [[ "${DATA}" != 'true' ]]; then
# Backup all APKs from path (can be multiple for split-apks!)
apkPath=$(dirname "$(sudo pm path "$packageName" | head -n1 | sed 's/package://')")
# Only sync APKs, libs, etc are extracted during install
# shellcheck disable=SC2046
# This might return multiple parameters that we don't want quoted here
doSync "$apkPath/" "$baseDestFolder/" $(includeOnlyApk)
# Backup all APKs from path (can be multiple for split-apks!)
apkPath=$(dirname "$(sudo pm path "$packageName" | head -n1 | sed 's/package://')")
# Only sync APKs, libs, etc are extracted during install
# shellcheck disable=SC2046
# This might return multiple parameters that we don't want quoted here
doSync "$apkPath/" "$baseDestFolder/" $(includeOnlyApk)
fi
}

function backupTermux() {
local tmpFolder packageName="$1"
local baseDestFolder="$2/$1"
# Create in HOME, because usr/tmp is part of the backup
# For local backups we could get rid of the temp folder, but this would make the code more complicated
tmpFolder=$(mktemp -d "--tmpdir=$HOME")

backupFolder "/data/data/${packageName}/files/home" "${baseDestFolder}/data/files/home"

trace "Writing termux.tgz to ${tmpFolder}"
trap "rm -rf ${tmpFolder}" 0
termux-backup "${tmpFolder}/termux.tgz"
doSync "${tmpFolder}/termux.tgz" "${baseDestFolder}/"

rm -rf "${tmpFolder}"
}

function backupFolder() {
srcFolder="$1"
rootDestFolder="$2"
Expand Down Expand Up @@ -60,35 +80,63 @@ function restoreApp() {
fi

if [[ "${APK}" != 'true' ]]; then
user=$(sudo stat -c '%U' "/data/data/$packageName")
group=$(sudo stat -c '%G' "/data/data/$packageName")
user=$(sudo stat -c '%U' "/data/data/${packageName}")
group=$(sudo stat -c '%G' "/data/data/${packageName}")

restoreFolder "${rootSrcFolder}" "data" "/data/data"
if [[ "${packageName}" != 'com.termux' ]]; then
restoreFolder "${rootSrcFolder}" "data" "/data/data"

restoreFolder "${rootSrcFolder}" "sdcard" "/sdcard/Android/data"
restoreFolder "${rootSrcFolder}" "sdcard" "/sdcard/Android/data"

restoreFolder "${rootSrcFolder}" "sdcard-media" "/sdcard/Android/media"
restoreFolder "${rootSrcFolder}" "sdcard-media" "/sdcard/Android/media"
else
restoreTermux "$@"
fi
fi
}

function restoreTermux() {
local tmpFolder rootSrcFolder="$1"

restoreFolder "${rootSrcFolder}" "data/files/home" "/data/data"

# Create in HOME, because usr/tmp is part of the restore.
# This might not be strictly necessary here but, still might avoid trouble
# For restores from local source we could get rid of the temp folder, but this would make the code more complicated
tmpFolder=$(mktemp -d "--tmpdir=$HOME")

trace "Fetching termux.tgz to ${tmpFolder}"
trap "rm -rf ${tmpFolder}" 0
doSync "${rootSrcFolder}/termux.tgz" "${tmpFolder}" '--include=*.tgz'
sudo chown -R "$(id -u):$(id -g)" "${tmpFolder}"
rm -rf " ${tmpFolder}"

trace "Restoring termux from ${tmpFolder}"
termux-restore "${tmpFolder}/termux.tgz"

info "Restored termux. Pleas restart termux app."
info "Optional: if you used a shell other than bash, set it as default again, e.g. chsh zsh"
}

function restoreFolder() {
# e.g. /folder/com.nxp.taginfolite
# or remote:/folder/com.nxp.taginfolite
# or remote:/folder/com.nxp.taginfolite/
local packageName rootSrcFolder="$1"
# e.g. com.nxp.taginfolite
packageName=$(extractAppNameFromFolder "$rootSrcFolder")
# e.g. data
# e.g. data or data/foo/bar
local relativeSrcFolder="$2"
# e.g. '' or 'foo/bar'
local subFolder=''
[[ "${relativeSrcFolder}" == */* ]] && subFolder="/${relativeSrcFolder#*/}"
# e.g. /data/data
local rootDestFolder="$3"


# e.g. /folder/com.nxp.taginfolite/data/
local actualSrcFolder="${rootSrcFolder}/${relativeSrcFolder}"
# e.g. /data/data/com.nxp.taginfolite
local actualDestFolder="${rootDestFolder}/${packageName}"

local actualDestFolder="${rootDestFolder}/${packageName}${subFolder}"

if [[ "$(checkActualSourceFolderExists "${actualSrcFolder}")" == 'true' ]]; then
trace "Restoring data to ${actualDestFolder}"
Expand All @@ -101,8 +149,7 @@ function restoreFolder() {
fi
else
info "Backup does not contain folder '${actualSrcFolder}'. Skipping"
fi

fi
}

function extractAppNameFromFolder() {
Expand Down Expand Up @@ -160,6 +207,7 @@ function backupFolderSyncArgs() {
}

function includeOnlyApk() {
# TODO simplify using '--include=*.apk'?
if [[ "${RCLONE}" == 'true' ]]; then
# Avoid fuss with whitespaces inside the filter rules by importing them from a file
echo --filter-from="${LIB_DIR}/rclone-apk-filter.txt"
Expand Down

0 comments on commit a96ae42

Please sign in to comment.