summaryrefslogtreecommitdiff
path: root/andbackup.sh
blob: 646c0ce87a1bc0a7f48af4735c8a556515a9b78a (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 #backups=/storage/ext_sd/bk/andbackup/andbackupBK
   24 flag_delimiter="-"
   25 
   26 usage() {
   27   # NOTE: The maximum width of this text should be 65 chars. This
   28   #       will ensure it displays properly on most devices without
   29   #       strange wrapping.
   30   #       If using vim to reflow, execute ':set tw=65'
   31   out="
   32 Andbackup is a basic Android application (or package) backup and
   33 restore script. It can backup a single application, or a a list
   34 of applications provided in a text file (one application name per
   35 line). It can do the same for application restore.
   36 
   37 The default backup path is '/sdcard/andbackup/'.
   38 
   39 Usage:
   40   andbackup.sh <command> [opts]
   41 
   42 Commands:
   43   help                    Print this help text
   44   backup      <app_name>  Back up a single package
   45   restore     <app_name>  Restore a single package
   46   list                    List installed packages
   47   listbackup  <list_file> Backup packages using a list text file
   48   listrestore <list_file> Restore packages from a list text file
   49 
   50 "
   51   printf "${out}"
   52 }
   53 
   54 
   55 log() {
   56   logtype=${1:-}
   57   logmsg=${2:-}
   58   logdate=$(date '+%T %F')
   59 
   60   printf "%s   %s   %s\n" "${logdate}" "${logtype}" "${logmsg}"
   61 }
   62 
   63 # Some useful macros
   64 linfo() {
   65   log "info " "${1:-}"
   66 }
   67 
   68 lerror() {
   69   log "error" "${1:-}"
   70 }
   71 
   72 lwarn() {
   73   log "warn " "${1:-}"
   74 }
   75 
   76 lfatal() {
   77   log "fatal" "${1:-}"
   78 }
   79 
   80 
   81 backup_app() {
   82   local app=${1:-}
   83 
   84   # Make sure app is specified
   85   if [[ -z ${app} ]]; then
   86     lerror "Please specify an app to backup."
   87     return 1
   88   fi
   89 
   90   #If app has the flag_delimiter that means options are passed in
   91   local preserveCache=false
   92   if [[ 0 = $(echo ${app} | grep --quiet [${flag_delimiter}]; echo $?) ]]; then
   93     linfo "This app $app has passed in options"
   94     if [[ 0 = $(echo ${app} | grep --quiet "preserveCache"; echo $?) ]]; then
   95       preserveCache=true
   96     fi
   97     #Need to get app back to std naming (without params)
   98     app=`echo ${app} | grep --only-matching ^.*- | sed s/-//g | cat -`
   99   fi
  100   
  101   # Make sure app is installed
  102   if [[ $(dumpsys package ${app} | wc -l) -eq 0 ]]; then
  103     lerror "Package ${app} appears to not be installed."
  104     return 1
  105   fi
  106 
  107   linfo "Backing up ${app}"
  108 
  109   # Stop the application if it is running
  110   am force-stop ${app}
  111 
  112   # Get pertinent metadata
  113   local code=$(dumpsys package ${app} | grep codePath | cut -d'=' -f2 | head -n1)
  114   local data=$(dumpsys package ${app} | grep dataDir  | cut -d'=' -f2 | head -n1)
  115   #linfo HERE is your data dir: $data
  116 
  117   # Create backup destination if not exist
  118   [[ ! -d ${backups} ]] && mkdir -p ${backups}
  119   [[ ! -d ${backups}/${app}/data ]] && mkdir -p ${backups}/${app}/data
  120 
  121   # Backup the apk file if it exists
  122   if [[ -f ${code}/base.apk ]]; then
  123     cp ${code}/base.apk ${backups}/${app}/base.apk
  124   else
  125     linfo "${app} apk file could not be found. Skipping apk installer backup."
  126   fi
  127 
  128   # Copy the user data
  129   cp -rp ${data}/* ${backups}/${app}/data/
  130   # Delete cache directory if it exists
  131   # This will sometimes free up significant amounts of space
  132   if [[ ! -d "${backups}/${app}/data/cache" ]]; then
  133     linfo "Cache doesn't exist for ${app}"
  134   elif ! $preserveCache; then
  135     linfo "Deleting cache for ${app}"
  136     rm -rf "${backups}/${app}/data/cache"
  137   else
  138     linfo "Preserving cache for ${app}"
  139   fi
  140 
  141   # Compress the backup
  142   linfo "Compressing userdata for ${app}"
  143   cd ${backups}/${app}/
  144   tar -c data | gzip -c > data.tar.gz
  145   rm -rf data
  146 }
  147 
  148 
  149 function list_apps {
  150   pm list package | cut -d ':' -f 2 | sort
  151 }
  152 
  153 
  154 restore_app() {
  155   local app=${1:-}
  156 
  157   # Make sure app is specified
  158   if [[ -z ${app} ]]; then
  159     lerror "Please specify an app to restore."
  160     return 1
  161   fi
  162 
  163   # When restoring something with flags need to restore app alone
  164   if [[ 0 = $(echo ${app} | grep --quiet [${flag_delimiter}]; echo $?) ]]; then
  165     linfo "This app $app had passed in options"
  166     #Need to get app back to std naming (without params)
  167     app=`echo ${app} | grep --only-matching ^.*- | sed s/-//g | cat -`
  168     linfo "Restoring to std name \"${app}\" while we restore"
  169   fi
  170 
  171   # Check that backup exists to be restored
  172   if [[ ! -d ${backups}/${app} ]]; then
  173     lerror "No backup for ${app} exists."
  174     return 1
  175   fi
  176 
  177   linfo "Restoring ${app}"
  178 
  179   # Install app if it is not yet installed
  180   if [[ ! -f "${backups}/${app}/base.apk" ]]; then
  181     linfo "Installer for ${app} not found. Only restoring data."
  182   elif [[ $(dumpsys package ${app} | grep -c userId) -eq 0 ]]; then
  183     linfo "Installer detected but package ${app} is not installed. Installing"
  184     pm install ${backups}/${app}/base.apk
  185   fi
  186 
  187   # Stop the application if it is running
  188   am force-stop ${app}
  189 
  190   # Get pertinent metadata
  191   local owner=$(dumpsys package ${app} | grep userId | cut -d'=' -f2 | head -n1)
  192   local code=$(dumpsys package ${app} | grep codePath | cut -d'=' -f2 | head -n1)
  193   local data=$(dumpsys package ${app} | grep dataDir  | cut -d'=' -f2 | head -n1)
  194 
  195   # Decompress backup
  196   linfo "Decompressing user data for ${app}."
  197   cd ${backups}/${app}/
  198   gzip -d -c data.tar.gz | tar -x
  199 
  200   # Copy the user data in
  201   cp -rp ${backups}/${app}/data/* ${data}/
  202   # Fix data permissions
  203   chown -R ${owner}:${owner} ${data}
  204   # Fix selinux labels
  205   linfo "Restoring SELinux contexs for ${app}"
  206   restorecon -R ${data} 2>/dev/null
  207 
  208   # Cleanup the extracted data so restores don't take too much space
  209   rm -rf ${backups}/${app}/data
  210 }
  211 
  212 
  213 backup_apps() {
  214   local apps=${@}
  215   [[ -z ${apps[@]:-} ]] && lerror "At least one app is required." && return 1
  216 
  217   for app in ${apps[@]}; do
  218     backup_app ${app}
  219   done
  220 }
  221 
  222 
  223 restore_apps() {
  224   local apps=${@}
  225   [[ -z ${apps[@]:-} ]] && log error "At least one app is required." && return 1
  226 
  227   for app in ${apps[@]}; do
  228     restore_app ${app}
  229   done
  230 }
  231 
  232 
  233 list_backup_apps() {
  234   local list=${1:-}
  235   [[ -z ${list} ]] && printf "A backup list is required." && return 1
  236 
  237   for app in $(cat ${list}); do
  238     backup_app ${app}
  239   done
  240 }
  241 
  242 
  243 list_restore_apps() {
  244   local list=${1}
  245   [[ -z ${list} ]] && printf "A backup list is required." && return 1
  246 
  247   for app in $(cat ${list}); do
  248     restore_app ${app}
  249   done
  250 }
  251 
  252 
  253 main() {
  254   local cmd=${1:-}
  255   shift
  256 
  257   # Ensure root is running this script
  258   if [ $(id -u) -gt 0 ]; then
  259     lerror "Script must be run as root (uid 0)."
  260     return 1
  261   fi
  262 
  263 
  264   if [ "${cmd}" = 'backup' ]; then
  265     backup_apps ${@}
  266   elif [ "${cmd}" = 'listbackup' ]; then
  267     list_backup_apps ${@}
  268   elif [ "${cmd}" = 'restore' ]; then
  269     restore_apps ${@}
  270   elif [ "${cmd}" = 'listrestore' ]; then
  271     list_restore_apps ${@}
  272   elif [ "${cmd}" = 'ls' ] || [ "${cmd}" = 'list' ]; then
  273     list_apps
  274   elif [ "${cmd}" = 'help' ]; then
  275     usage
  276   else
  277     lerror "Unknown command '${cmd}'"
  278     usage
  279     return 1
  280   fi
  281 }
  282 
  283 main ${@}

Generated by cgit