#!/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" "$app 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