#!/bin/sh

# adb_backup.sh
# 2026-02-14
# by Gernot Walzl

# Backup and restore data of Android apps.
# ADB root debugging is required. Tested on Android 15.

set -e

CWD=$(pwd)
TMP=${TMP:-'/var/tmp'}
USER_ID=${USER_ID:-'0'}

print_usage () {
  echo "Usage: $0 {list|backup PACKAGE|restore FILENAME}"
  echo "Example: $0 backup org.videolan.vlc"
  echo "Example: $0 restore org.videolan.vlc_2026-01-31.zip"
}

list () {
  adb shell "pm list package -3 | sed 's/^package://' | sort"
}

backup () {
  PKG="$1"
  adb root

  adb shell "test -d 'data/data/$PKG'" || return 1

  echo
  echo "Backing up $PKG:"
  mkdir -p "$TMP/$PKG"
  cd "$TMP/$PKG" || return 1

  echo "/data/data/$PKG"
  adb exec-out "tar -cz 'data/data/$PKG' \
      2> /dev/null" \
    > "${PKG}_data.tar.gz"
  adb shell "find 'data/data/$PKG' -type f -exec sha256sum '{}' \;" \
    > "${PKG}_data.sha256"

  DIRS=$(adb shell "cd '/storage/emulated/$USER_ID' \
      && find Android -type d -name '$PKG'")
  for DIR in $DIRS; do
    echo "/storage/emulated/$USER_ID/$DIR"
    ARCHNAME="${PKG}_$(dirname "$DIR" | tr / _)"
    adb exec-out "tar -cz -C '/storage/emulated/$USER_ID' '$DIR' \
        2> /dev/null" \
      > "${ARCHNAME}.tar.gz"
    adb shell "cd '/storage/emulated/$USER_ID' \
        && find '$DIR' -type f -exec sha256sum '{}' \;" \
      > "${ARCHNAME}.sha256"
  done

  PKGPATHS=$(adb shell "pm path '$PKG' | sed 's/^package://'")
  for PKGPATH in $PKGPATHS; do
    echo "$PKGPATH"
    adb pull "$PKGPATH" \
      > /dev/null
    BASENAME=$(basename "$PKGPATH")
    mv "$BASENAME" "${PKG}_${BASENAME}"
  done

  ZIPFILE="${PKG}_$(date +%Y-%m-%d).zip"
  echo
  echo "Creating $ZIPFILE:"
  zip -r "$CWD/$ZIPFILE" .

  cd "$CWD"
  rm -rf "$TMP/$PKG"
}

restore () {
  FILENAME=$(realpath "$1")
  adb root

  PKG=$(unzip -l "$FILENAME" \
    | sed -n '/_Android_/d;s/.* \(.*\)_data.tar.gz$/\1/p')
  if [ -z "$PKG" ]; then
    return 1
  fi

  echo
  mkdir -p "$TMP/$PKG"
  cd "$TMP/$PKG" || return 1
  unzip "$FILENAME"

  echo
  echo "Restoring $1:"
  for APK in *.apk; do
    echo "$APK"
    adb install "$APK"
  done
  USER_APP=$(adb shell "stat -c '%U' '/data/data/$PKG'")

  for ARCHIVE in *_Android_*.tar.gz; do
    echo "$ARCHIVE"
    cat "$ARCHIVE" \
      | adb shell "tar -xzf - -C '/storage/emulated/$USER_ID'"
  done
  adb shell "cd '/storage/emulated/$USER_ID' \
      && find Android -type d -name '$PKG' -exec chown -R '$USER_APP' '{}' \;"

  echo "${PKG}_data.tar.gz"
  cat "${PKG}_data.tar.gz" \
    | adb shell "tar -xzf -"
  adb shell "chown -R '${USER_APP}:${USER_APP}' \
    '/data/data/$PKG'"
  adb shell "chown -R '${USER_APP}:${USER_APP}_cache' \
    '/data/data/$PKG/cache' \
    '/data/data/$PKG/code_cache'"
  adb shell "restorecon -DR '/data/data/$PKG'"

  echo
  echo "Checking restored data:"
  for CHECKSUMS in *_Android_*.sha256; do
    echo "$CHECKSUMS"
    if [ -s "$CHECKSUMS" ]; then
      cat "$CHECKSUMS" \
        | adb shell "cd '/storage/emulated/$USER_ID' \
            && sha256sum -cs -" \
        || echo "ERROR: 'sha256sum -c $CHECKSUMS' failed!"
    fi
  done

  echo "${PKG}_data.sha256"
  cat "${PKG}_data.sha256" \
    | adb shell "sha256sum -cs -" \
    || echo "ERROR: 'sha256sum -c ${PKG}_data.sha256' failed!"

  cd "$CWD"
  rm -rf "$TMP/$PKG"
}

case "$1" in
  'list')
    list
    ;;
  'backup')
    backup "$2"
    ;;
  'restore')
    restore "$2"
    ;;
  *)
    print_usage
esac