forked from Botspot/pi-apps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
manage
executable file
·357 lines (301 loc) · 13.2 KB
/
manage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#!/bin/bash
#$1 is an action, like install
#$2 is app name, like Arduino
DIRECTORY="$(readlink -f "$(dirname "$0")")"
function error {
echo -e "\e[91m$1\e[39m"
exit 1
}
if [ -z "$1" ];then
error "You need to specify an operation, and in most cases, which app to operate on."
fi
source "${DIRECTORY}/api" || error "failed to source ${DIRECTORY}/api"
mkdir -p "${DIRECTORY}/data/status" "${DIRECTORY}/data/update-status" "${DIRECTORY}/logs"
#remove week-old logfiles
find "${DIRECTORY}/logs" -type f -mtime +7 -exec rm -f {} \; &>/dev/null &
#check for internet connection
if ! wget --spider github.com &>/dev/null ;then
error "No internet connection!\ngithub.com failed to respond.\nErrors: $(wget --spider github.com 2>&1)"
fi
dirhash() {
#echo "Hashing this dir: $1" 1>&2
if [ -d "$1" ];then
#-------- hash every file in the folder ---------------------------------------------------- list all filenames ------------------- hash all that again
hash="$(echo -e "$(find "$1" -type f -print0 | xargs -0 sha1sum | awk '{print $1}')$(find "$1" -type f -exec basename {} \;)" | sha1sum | awk '{print $1}')"
else
hash=''
#echo "dirhash(): Folder '$1' does not exist" 1>&2
fi
echo "$hash"
}
#check if hardware and OS is supported
if is_supported_system >/dev/null;then
supported=yes
else
supported=no
fi
if [ "$1" == 'multi-uninstall' ] || [ "$1" == 'multi-install' ];then
if [ "$1" == 'multi-uninstall' ];then
action=uninstall
elif [ "$1" == 'multi-install' ];then
action=install
fi
failed_apps=''
IFS=$'\n'
for app in $2
do
"${DIRECTORY}/manage" $action "$app"
if [ $? != 0 ];then
#this app failed to install - add it to the list of failed apps
failed_apps="$failed_apps
$app"
fi
done
app_results="${app_results:1}" #remove first blank newline
if [ "$supported" == yes ];then #if hardware and OS is supported
for app in $(echo "$failed_apps" | list_intersect "$(list_apps online)") ;do
logfile="$(ls -dt "${DIRECTORY}/logs"/* | grep '\-'"${app}"'\.log' -m 1)" #sort logfiles by modification date, get the most recent one for the current app
#if logfile detected, exists, and is not empty, ask permission to send error report
if [ ! -z "$logfile" ] && [ -f "$logfile" ] && [ -s "$logfile" ];then
send_error_report_gui "$logfile" "<b>$app</b> failed to ${action}."
fi
done
fi
if [ ! -z "$failed_apps" ];then
exit 1
fi
elif [ "$1" == 'install-if-not-installed' ];then
#if not installed
if [ "$(app_status "$2")" != installed ];then
#install it
"${DIRECTORY}/manage" install "$2" || exit 1
fi
elif [ "$1" == 'install' ] || [ "$1" == 'uninstall' ];then
#for this operation, a program name must be specified.
app="$2"
if [ -z "$app" ];then
error "For this operation, you must specify which app to operate on."
elif [ ! -d "${DIRECTORY}/apps/$app" ];then
error "${DIRECTORY}/apps/$app does not exist!"
fi
#check if hardware and OS is supported
if [ "$supported" == no ];then
echo -e "\e[103m\e[30mWARNING: YOUR SETUP IS UNSUPPORTED:\n$(is_supported_system)\e[39m\e[49m"
sleep 10
fi
if [ "$1" == install ];then
mode=install
else
mode=uninstall
fi
#ensure not a disabled app
if [ "$mode" == install ] && [ "$(app_status "${app}")" == 'disabled' ];then
echo -e "\e[93mNot installing the $app app. IT IS DISABLED.\e[39m"
exit 0
fi
#if already installed then ask for confirmation
if [ "$(app_status "${app}")" == "${mode}ed" ];then
yad --text="$app is already ${mode}ed. Are you sure you want to $mode it again?" \
--text-align=center --center --title='Quick question' --window-icon="${DIRECTORY}/icons/logo.png" \
--button=No!"${DIRECTORY}/icons/exit.png":1 --button=Yes!"${DIRECTORY}/icons/check.png":0 || exit 0
fi
#analytics
"${DIRECTORY}/etc/bitlylink" "$app" "$mode" &
#determine which script to run
if [ "$mode" == install ];then
scriptname="$(script_name_cpu "$app")" #will be install, install-32, or install-64
[ -z "$scriptname" ] && error "It appears $app does not have an install-${arch} script suitable for your ${arch}-bit OS."
else #uninstall mode
scriptname=uninstall
fi
appscript="${DIRECTORY}/apps/${app}/${scriptname}"
#determine path for log file to be created
logfile="${DIRECTORY}/logs/${mode}-incomplete-${app}.log"
if [ -f "$logfile" ] || [ -f "$(echo "$logfile" | sed 's+-incomplete-+-success-+g')" ] || [ -f "$(echo "$logfile" | sed 's+-incomplete-+-fail-+g')" ];then
#append a number to logfile's file-extension if the original filename already exists
i=1
while true;do
#if variable $i is 2, then example newlogfile value: /path/to/install-Discord.log2
newlogfile="$logfile$i"
if [ ! -f "$newlogfile" ] && [ ! -f "$(echo "$newlogfile" | sed 's+/-incomplete-+-success-+g')" ] && [ ! -f "$(echo "$newlogfile" | sed 's+-incomplete-+-fail-+g')" ];then
logfile="${newlogfile}"
break
fi
i=$((i+1))
done
fi
#for piping to tee, the app script's exit code is preserved by enabling the pipefail bash builtin
set -o pipefail
chmod u+x "$appscript"
echo -e "\e[96m${mode^}ing \e[1m${app}\e[0m\e[96m...\e[39m" | tee -a "$logfile"
cd $HOME
echo 'corrupted' > "${DIRECTORY}/data/status/${app}"
nice "$appscript" &> >(tee -a "$logfile")
if [ $? == 0 ]; then #if app script succeeded
#change the status from corrupted to 'installed' or 'uninstalled'
echo "${mode}ed" > "${DIRECTORY}/data/status/${app}"
echo -e "\n\e[42m\e[30m${mode^}ed ${app} successfully.\e[39m\e[49m" | tee -a "$logfile"
format_log_file "$logfile" #remove escape sequences from logfile
mv "$logfile" "$(echo "$logfile" | sed 's+-incomplete-+-success-+g')" #
exit 0
else
echo -e "\n\e[91mFailed to ${mode} ${app} with $scriptname script"\!"\e[39m
\e[40m\e[93m\e[5m▲\e[25m\e[39m \e[49m\e[93mNeed help? Copy the \e[1mENTIRE\e[0m\e[49m\e[93m terminal output or take a screenshot.
Please ask on Github: \e[94m\e[4mhttps://github.com/Botspot/pi-apps/issues/new/choose\e[24m\e[93m
Or on Discord: \e[94m\e[4mhttps://discord.gg/RXSTvaUvuu\e[24m\e[39m" | tee -a "$logfile"
format_log_file "$logfile" #remove escape sequences from logfile
mv "$logfile" "$(echo "$logfile" | sed 's+-incomplete-+-fail-+g')"
exit 1
fi
elif [ "$1" == 'update' ];then
#UPDATE
#for this operation, a program name must be specified.
app="$2"
if [ -z "$app" ];then
error "For this operation, you must specify which app to operate on."
fi
#HIDDEN FEATURE - if $3 equals nofetch, then don't download github repo. Useful for updating multiple apps at maximum speed
if [ "$3" == 'nofetch' ] && [ -d "${DIRECTORY}/update" ];then
true
else
rm -rf "${DIRECTORY}/update" && mkdir "${DIRECTORY}/update" && cd "${DIRECTORY}/update" || error "failed to enter the update directory!"
git clone --depth=1 "$(cat "${DIRECTORY}/etc/git_url")" || error "failed to clone repository!"
fi
newhash="$(dirhash "${DIRECTORY}/update/pi-apps/apps/${app}")"
oldhash="$(dirhash "${DIRECTORY}/apps/${app}")"
#detect which installation script exists and get the hash for that one
scriptname="$(script_name_cpu "$app")"
[ $? == 1 ] && exit 1
newinstallhash="$(sha1sum "${DIRECTORY}/update/pi-apps/apps/${app}/${scriptname}" 2>/dev/null | awk '{print $1}')"
oldinstallhash="$(sha1sum "${DIRECTORY}/apps/${app}/${scriptname}" 2>/dev/null | awk '{print $1}')"
#echo -e "newinstallhash: $newinstallhash\noldinstallhash: $oldinstallhash"
#echo -e "newhash: $newhash\noldhash: $oldhash"
if [ "$newhash" == "$oldhash" ];then
echo "$app is identical to the online version. Nothing to do!"
exit 0
fi
#else
echo "$app is not identical to the online version."
installback=no
#if install script was changed #if installed already
if [ "$newinstallhash" != "$oldinstallhash" ] && [ "$(app_status "${app}")" == 'installed' ];then
installback=yes
echo "$app's install script has been updated. Reinstalling $app..."
#uninstall it using a recursive script instance
"${DIRECTORY}/manage" uninstall "$app"
#fix edge case: if app is installed but uninstall script doesn't exist somehow, then pretend app was uninstalled so that the reinstall later will happen noninteractively
if [ "$(app_status "$app")" == installed ];then
echo 'uninstalled' > "${DIRECTORY}/data/status/${app}"
fi
fi
#move old program to trash
gio trash "${DIRECTORY}/apps/${app}" 2>/dev/null
#failsafe
[ -d "${DIRECTORY}/apps/${app}" ] && rm -rf "${DIRECTORY}/apps/${app}"
#copy new version from update/ to apps/
cp -rf "${DIRECTORY}/update/pi-apps/apps/${app}" "${DIRECTORY}/apps/${app}"
if [ "$installback" == 'yes' ];then
echo "$app was originally installed before updating. Reinstalling the new version now."
#install it using a recursive script instance
"${DIRECTORY}/manage" install "$app"
fi
echo -e "\e[92m${app} was updated successfully.\e[39m"
elif [ "$1" == 'check-all' ];then
#CHECK-ALL
#for this operation, a program name cannot be specified.
#hidden flags
installedonly=0
if [ "$2" == 'installedonly' ];then
# If $2 is 'installedonly', then only check for updates for those apps that are installed
installedonly=1
elif [ "$2" == 'nogenerate' ];then
# If $2 is 'nogenerate', then don't hash anything or git-clone anything. Simply reads out data found in data/update-status folder in the same app-per-newline format.
echo -e "\e[97mThese apps can be updated:" 1>&2
echo "${updatable}"
IFS=$'\n'
for app in $(list_apps all);do
updatestatus="$(cat "${DIRECTORY}/data/update-status/${app}" 2>/dev/null)"
if [ "$updatestatus" == 'new' ] || [ "$updatestatus" == 'updatable' ];then
#if current app's update-status is 'new' or 'updatable', then echo it.
echo "$app"
fi
done
exit 0 #the lines above have displayed a list of apps already.
fi #end of checking for hidden flags
#if the updater script exists in update folder, then just git pull to save time
if [ -f "${DIRECTORY}/update/pi-apps/updater" ];then
cd "${DIRECTORY}/update/pi-apps"
echo -n "The 'git pull' command says: " 1>&2
git pull 1>&2 || rm -rf "${DIRECTORY}/update"
fi
#re-check and if updater script does not exist then do a git clone
if [ ! -f "${DIRECTORY}/update/pi-apps/updater" ];then
rm -rf "${DIRECTORY}/update"
mkdir -p "${DIRECTORY}/update" && cd "${DIRECTORY}/update" || error "failed to enter the update directory!"
git clone --depth=1 "$(cat "${DIRECTORY}/etc/git_url")" 1>&2|| error "failed to clone repository to the update directory!"
fi
if [ $installedonly == 1 ];then
#installedonly flag enabled. Remove apps that are uninstalled, disabled, or corrupted
applist="$(list_apps installed)"
else
#installedonly flag disabled, so use entire combined list of apps, both local and online
applist="$(list_apps all)"
fi
#echo "App list: $applist" 1>&2
updatable=''
IFS=$'\n'
for app in $applist
do
#echo "app: $app"
newhash="$(dirhash "${DIRECTORY}/update/pi-apps/apps/${app}")" #folder will not exist if local app
oldhash="$(dirhash "${DIRECTORY}/apps/${app}")" #folder will not exist if new online app
#echo -e "newhash: $newhash\noldhash: $oldhash" 1>&2
if [ "$newhash" == "$oldhash" ];then
#echo -e "${app} is identical\e[90m to the online version. Nothing to do!\e[39m" 1>&2
echo 'latest' > "${DIRECTORY}/data/update-status/${app}"
else
if [ ! -d "${DIRECTORY}/apps/${app}" ];then
echo -e "\e[97m${app} does not exist locally.\e[39m Adding to updatable list." 1>&2
echo 'new' > "${DIRECTORY}/data/update-status/${app}"
#in this case, add to updatable list
updatable="${updatable}
${app}"
elif [ ! -d "${DIRECTORY}/update/pi-apps/apps/${app}" ];then
#echo -e "\e[97m${app} only exists locally.\e[39m Will not add to updatable list." 1>&2
echo 'local' > "${DIRECTORY}/data/update-status/${app}"
#in this case, do not add to updatable list
else
echo -e "\e[97m${app} exists in both locations, but online version is newer\e[39m. Adding to updatable list." 1>&2
echo 'updatable' > "${DIRECTORY}/data/update-status/${app}"
#in this case, add to updatable list
updatable="${updatable}
${app}"
fi
fi
done
#remove initial newline character
updatable="${updatable:1}"
if [ -z "$updatable" ];then
updatable='.'
fi
echo "${updatable}"
elif [ "$1" == 'update-all' ];then
#UPDATE-ALL
#for this operation, a program name cannot be specified.
IFS=$'\n'
updatable="$("${DIRECTORY}/manage" check-all)"
if [ "$updatable" == '.' ];then
updatable=''
fi
echo "Updatable: ${updatable}EOU"
for updateapp in $updatable
do
echo "updating $updateapp"
#update it using a recursive script instance
echo "${DIRECTORY}/manage update $updateapp"
"${DIRECTORY}/manage" update "$updateapp" || exit 1
done
echo -e '\e[92mOperation completed successfully!\e[39m'
else
error "Did not understand $1. Allowed values: 'install', 'multi-install', 'install-if-not-installed', 'uninstall', 'multi-uninstall', 'update','update-all', or 'check-all'."
fi