You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Botspot-Pi-Apps/manage

373 lines
15 KiB
Bash

#!/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
set -a #make all functions in the api available to apps
source "${DIRECTORY}/api" || error "failed to source ${DIRECTORY}/api"
#remove old mcpi repositories - this runonce is here so that terminal-only users will still receive the fix.
(runonce <<"EOF"
if [ -f /etc/apt/sources.list.d/mcpi-revival.list ] && cat /etc/apt/sources.list.d/mcpi-revival.list | grep -qF 'https://mcpirevival.tk/mcpi-packages' ;then
echo 'deb [trusted=yes] https://mcpi-revival.github.io/mcpi-packages/ buster main' | sudo tee /etc/apt/sources.list.d/mcpi-revival.list
elif [ -f /etc/apt/sources.list.d/mcpi-revival.list ] && cat /etc/apt/sources.list.d/mcpi-revival.list | grep -qF 'https://mcpi-revival.github.io/mcpi-packages/debs' ;then
echo 'deb [trusted=yes] https://mcpi-revival.github.io/mcpi-packages/ buster main' | sudo tee /etc/apt/sources.list.d/mcpi-revival.list
fi
if [ -f /etc/apt/sources.list.d/Alvarito050506_mcpi-devs.list ];then
sudo rm -f /etc/apt/sources.list.d/Alvarito050506_mcpi-devs.list
fi
if dpkg -l box86 &>/dev/null ;then
sudo apt purge -y box86
sudo apt install -y box86-no-binfmt-restart
fi
EOF
) &>/dev/null
#Silently re-download repo if github repository is over 3 months out of date
{
current_git_date="$(cd "$DIRECTORY"; git show -s --format=%ct)"
if [ $(date +%s) -gt $(($current_git_date + 7776000)) ];then
yad --window-icon="${DIRECTORY}/icons/logo.png" --width=500 --no-buttons --center --title="Auto-updating Pi-Apps" \
--text="Your Pi-Apps github repository is somehow 3 months out-of-date."$'\n'"Downloading fresh version of pi-apps and saving the old version to ${DIRECTORY}-3-months-old..." &
sleep 1
clear
echo -e "\nYour Pi-Apps github repository is somehow 3 months out-of-date.\nDownloading fresh version of pi-apps and saving the old version to ${DIRECTORY}-3-months-old...\n\n" 1>&2
cd $HOME
rm -rf ~/pi-apps-forced-update
git clone "$(cat "${DIRECTORY}/etc/git_url")" pi-apps-forced-update 1>&2 && cp -af "${DIRECTORY}/data" ~/pi-apps-forced-update && mv -f "$DIRECTORY" "${DIRECTORY}-3-months-old" && mv -f ~/pi-apps-forced-update "${DIRECTORY}"
temp_logfile="$(mktemp).txt"
echo -e "Updated a 3-months-outdated pi-apps install.\nFrom: $current_git_date\nTo: $(cd "$DIRECTORY"; git show -s --format=%ct)\n$(get_device_info)" >> "$temp_logfile"
send_error_report "$temp_logfile" 1>&2
rm -f "$temp_logfile"
sleep 10
fi
}
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
#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
app_list="$2" #newline-separated list of apps to install/uninstall
#if trying to install an already-installed app, or trying to uninstall and already-uninstalled app, ask for confirmation
IFS=$'\n'
for app in $app_list ;do
if [ "$(app_status "${app}")" == "${action}ed" ];then
yad --text="$app is already ${action}ed. Are you sure you want to $action 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
if [ $? != 0 ];then
#user clicked No, so remove the app from list
app_list="$(echo "$app_list" | grep -vx "$app")"
fi
fi
done
#install/uninstall one app at a time. If it fails then add the app to the list of failed apps
failed_apps=''
for app in $app_list ;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
failed_apps="${failed_apps: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, is not empty, and does not contain a bunch of arrows (user-fault error) ask permission to send error report
if [ ! -z "$logfile" ] && [ -f "$logfile" ] && [ -s "$logfile" ] && ! grep -qF '▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼' "$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
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
#display warning if hardware and os is unsupported
if [ "$supported" == no ];then
echo -e "\e[103m\e[30mWARNING: YOUR SYSTEM IS UNSUPPORTED:\n$(is_supported_system)\e[39m\e[49m"
sleep 1
echo -e "\e[103m\e[30mThe ability to send error reports has been disabled.\e[39m\e[49m"
sleep 1
echo -e "\e[103m\e[30mWaiting 10 seconds... (To cancel, press Ctrl+C or close this terminal)\e[39m\e[49m"
sleep 10
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
#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 diff -r "${DIRECTORY}/apps/${app}" "${DIRECTORY}/update/pi-apps/apps/${app}" -q >/dev/null;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
fi #end of checking for hidden flags
echo -n "Checking for online changes... " 1>&2
#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"
git pull || rm -rf "${DIRECTORY}/update"
fi | grep -v "Already up to date." 1>&2
#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
echo "Done" 1>&2
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 -en "Scanning apps... $app\033[0K\r" 1>&2
if [ ! -d "${DIRECTORY}/apps/${app}" ];then
#add to updatable list
updatable="${updatable}
${app}"
elif [ ! -d "${DIRECTORY}/update/pi-apps/apps/${app}" ];then
#app only exists locally
true
elif ! diff -r "${DIRECTORY}/apps/${app}" "${DIRECTORY}/update/pi-apps/apps/${app}" -q >/dev/null ;then
#if app hashes don't match, add to updatable list
updatable="${updatable}
${app}"
fi
done
#remove initial newline character
updatable="${updatable:1}"
if [ -z "$updatable" ];then
updatable='.'
fi
echo -e "Scanning apps... Done\033[0K" 1>&2
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