summaryrefslogtreecommitdiff
path: root/andbackup.sh
blob: 40ab2244fdb109c352a71c833783cbde5744817f (plain)
    1 #!/system/bin/sh
    2 #
    3 # Andbackup performs Android backups and restores.
    4 #
    5 # Copyright (C) 2017  Aaron Ball <nullspoon@oper.io>
    6 #
    7 # This program is free software: you can redistribute it and/or modify
    8 # it under the terms of the GNU General Public License as published by
    9 # the Free Software Foundation, either version 3 of the License, or
   10 # (at your option) any later version.
   11 #
   12 # This program is distributed in the hope that it will be useful,
   13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15 # GNU General Public License for more details.
   16 #
   17 # You should have received a copy of the GNU General Public License
   18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
   19 #
   20 set -u
   21 
   22 backups=/sdcard/andbackup
   23 
   24 
   25 usage() {
   26   # NOTE: The maximum width of this text should be 65 chars. This
   27   #       will ensure it displays properly on most devices without
   28   #       strange wrapping.
   29   #       If using vim to reflow, execute ':set tw=65'
   30   out="
   31 Andbackup is a basic Android application (or package) backup and
   32 restore script. It can backup a single application, or a a list
   33 of applications provided in a text file (one application name per
   34 line). It can do the same for application restore.
   35 
   36 The default backup path is '/sdcard/andbackup/'.
   37 
   38 Usage:
   39   andbackup.sh <command> [opts]
   40 
   41 Commands:
   42   help                    Print this help text
   43   backup      <app_name>  Back up a single package
   44   restore     <app_name>  Restore a single package
   45   list                    List installed packages
   46   listbackup  <list_file> Backup packages using a list text file
   47   listrestore <list_file> Restore packages from a list text file
   48 
   49 "
   50   printf "${out}"
   51 }
   52 
   53 
   54 log() {
   55   logtype=${1:-}
   56   logmsg=${2:-}
   57   logdate=$(date '+%T %F')
   58 
   59   printf "%s   %s   %s\n" "${logdate}" "${logtype}" "${logmsg}"
   60 }
   61 
   62 # Some useful macros
   63 linfo() {
   64   log "info " "${1:-}"
   65 }
   66 
   67 lerror() {
   68   log "error" "${1:-}"
   69 }
   70 
   71 lwarn() {
   72   log "warn " "${1:-}"
   73 }
   74 
   75 lfatal() {
   76   log "fatal" "${1:-}"
   77 }
   78 
   79 
   80 backup_app() {
   81   local app=${1:-}
   82 
   83   # Make sure app is specified
   84   if [[ -z ${app} ]]; then
   85     lerror "Please specify an app to backup."
   86     return 1
   87   fi
   88 
   89   # Make sure app is installed
   90   if [[ $(dumpsys package ${app} | wc -l) -eq 0 ]]; then
   91     lerror "Package ${app} appears to not be installed."
   92     return 1
   93   fi
   94 
   95   linfo "Backing up ${app}"
   96 
   97   # Stop the application if it is running
   98   am force-stop ${app}
   99 
  100   # Get pertinent metadata
  101   local code=$(dumpsys package ${app} | grep codePath | cut -d'=' -f2 | head -n1)
  102   local data=$(dumpsys package ${app} | grep dataDir  | cut -d'=' -f2 | head -n1)
  103 
  104   # Create backup destination if not exist
  105   [[ ! -d ${backups} ]] && mkdir -p ${backups}
  106   [[ ! -d ${backups}/${app}/data ]] && mkdir -p ${backups}/${app}/data
  107 
  108   # Backup the apk file if it exists
  109   if [[ -f ${code}/base.apk ]]; then
  110     cp ${code}/base.apk ${backups}/${app}/base.apk
  111   else
  112     linfo "${app} apk file could not be found. Skipping apk installer backup."
  113   fi
  114 
  115   # Copy the user data
  116   cp -rp ${data}/* ${backups}/${app}/data/
  117   # Delete cache directory if it exists
  118   # This will sometimes significant amounts of space
  119   if [[ -d "${backups}/${app}/data/cache" ]]; then
  120     rm -rf "${backups}/${app}/data/cache"
  121   fi
  122 
  123   # Compress the backup
  124   linfo "Compressing userdata for ${app}"
  125   cd ${backups}/${app}/
  126   tar -c data | gzip -c > data.tar.gz
  127   rm -rf data
  128 }
  129 
  130 
  131 function list_apps {
  132   pm list package | cut -d ':' -f 2 | sort
  133 }
  134 
  135 
  136 restore_app() {
  137   local app=${1:-}
  138 
  139   # Make sure app is specified
  140   if [[ -z ${app} ]]; then
  141     lerror "Please specify an app to restore."
  142     return 1
  143   fi
  144 
  145   # Check that backup exists to be restored
  146   if [[ ! -d ${backups}/${app} ]]; then
  147     lerror "No backup for ${app} exists."
  148     return 1
  149   fi
  150 
  151   linfo "Restoring ${app}"
  152 
  153   # Install app if it is not yet installed
  154   if [[ ! -f "${backups}/${app}/base.apk" ]]; then
  155     linfo "Installer for ${app} not found. Only restoring data."
  156   elif [[ $(dumpsys package ${app} | grep -c userId) -eq 0 ]]; then
  157     linfo "Installer detected but package ${app} is not installed. Installing"
  158     pm install ${backups}/${app}/base.apk
  159   fi
  160 
  161   # Stop the application if it is running
  162   am force-stop ${app}
  163 
  164   # Get pertinent metadata
  165   local owner=$(dumpsys package ${app} | grep userId | cut -d'=' -f2 | head -n1)
  166   local code=$(dumpsys package ${app} | grep codePath | cut -d'=' -f2 | head -n1)
  167   local data=$(dumpsys package ${app} | grep dataDir  | cut -d'=' -f2 | head -n1)
  168 
  169   # Decompress backup
  170   linfo "Decompressing user data for ${app}."
  171   cd ${backups}/${app}/
  172   gzip -d -c data.tar.gz | tar -x
  173 
  174   # Copy the user data in
  175   cp -rp ${backups}/${app}/data/* ${data}/
  176   # Fix data permissions
  177   chown -R ${owner}:${owner} ${data}
  178   # Fix selinux labels
  179   linfo "Restoring SELinux contexs for ${app}"
  180   restorecon -R ${data} 2>/dev/null
  181 
  182   # Cleanup the extracted data so restores don't take too much space
  183   rm -rf ${backups}/${app}/data
  184 }
  185 
  186 
  187 backup_apps() {
  188   local apps=${@}
  189   [[ -z ${apps[@]:-} ]] && lerror "At least one app is required." && return 1
  190 
  191   for app in ${apps[@]}; do
  192     backup_app ${app}
  193   done
  194 }
  195 
  196 
  197 restore_apps() {
  198   local apps=${@}
  199   [[ -z ${apps[@]:-} ]] && log error "At least one app is required." && return 1
  200 
  201   for app in ${apps[@]}; do
  202     restore_app ${app}
  203   done
  204 }
  205 
  206 
  207 list_backup_apps() {
  208   local list=${1:-}
  209   [[ -z ${list} ]] && printf "A backup list is required." && return 1
  210 
  211   for app in $(cat ${list}); do
  212     backup_app ${app}
  213   done
  214 }
  215 
  216 
  217 list_restore_apps() {
  218   local list=${1}
  219   [[ -z ${list} ]] && printf "A backup list is required." && return 1
  220 
  221   for app in $(cat ${list}); do
  222     restore_app ${app}
  223   done
  224 }
  225 
  226 
  227 main() {
  228   local cmd=${1:-}
  229   shift
  230 
  231   # Ensure root is running this script
  232   if [ $(id -u) -gt 0 ]; then
  233     lerror "Script must be run as root (uid 0)."
  234     return 1
  235   fi
  236 
  237 
  238   if [ "${cmd}" = 'backup' ]; then
  239     backup_apps ${@}
  240   elif [ "${cmd}" = 'listbackup' ]; then
  241     list_backup_apps ${@}
  242   elif [ "${cmd}" = 'restore' ]; then
  243     restore_apps ${@}
  244   elif [ "${cmd}" = 'listrestore' ]; then
  245     list_restore_apps ${@}
  246   elif [ "${cmd}" = 'ls' ] || [ "${cmd}" = 'list' ]; then
  247     list_apps
  248   elif [ "${cmd}" = 'help' ]; then
  249     usage
  250   else
  251     lerror "Unknown command '${cmd}'"
  252     usage
  253     return 1
  254   fi
  255 }
  256 
  257 main ${@}

Generated by cgit