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

Generated by cgit