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 ${@}
|