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