797 lines
32 KiB
Bash
797 lines
32 KiB
Bash
#!/bin/bash
|
|
{ #prevents errors if script was modified while in use
|
|
|
|
#$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 [[ $(id -u) == 0 ]]; then
|
|
error "Artian-Apps is not designed to be run as root! Please try again as a regular user."
|
|
fi
|
|
|
|
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"
|
|
|
|
validate_apps_gui() { #Given a list of actions and apps, graphically notify the user if there is a problem or ask for confirmation. Result is sent to stdout.
|
|
#action and app are separated by semicolon. (;)
|
|
#example line of input on $1: "install;Zoom"
|
|
|
|
local IFS=$'\n'
|
|
|
|
local queue="$1"
|
|
local action
|
|
local app
|
|
local line
|
|
|
|
#Ensure that the first word of each line is 'install' or 'uninstall' or 'update' or 'refresh' or 'update-file'
|
|
local i=1 #track the line number in queue
|
|
for line in $queue ;do
|
|
action="$(echo "$line" | awk -F ';' '{print $1}')"
|
|
|
|
if [ "$action" != install ] && [ "$action" != uninstall ] && [ "$action" != update ] && [ "$action" != refresh ] && [ "$action" != update-file ];then
|
|
warning "illegal mode: '$action' Removing this line from the queue"
|
|
|
|
#remove the app from list
|
|
queue="$(echo "$queue" | sed ${i}d)"
|
|
fi
|
|
|
|
i=$((i+1))
|
|
done
|
|
[ -z "$queue" ] && exit 0
|
|
|
|
#Ensure that each app-name is valid
|
|
local i=1 #track the line number in queue
|
|
for line in $queue ;do
|
|
action="$(echo "$line" | awk -F ';' '{print $1}')"
|
|
app="$(echo "$line" | awk -F ';' '{print $2}')"
|
|
|
|
if [ "$action" == update-file ];then
|
|
# the current line is a pseudo-action used to update a file
|
|
# skip app validation
|
|
true
|
|
elif ([ "$action" == update ] || [ "$action" == refresh ]) && [ ! -d "${DIRECTORY}/update/Artian-apps/apps/${app}" ];then
|
|
|
|
yad --class Artian-Apps --name "Artian-Apps" --text="Invalid app "\""<b>$app</b>"\"". Cannot $action it." \
|
|
--text-align=center --center --title='Error' --window-icon="${DIRECTORY}/icons/logo.png" \
|
|
--button=OK!"${DIRECTORY}/icons/check.png":0
|
|
|
|
#remove the app from list
|
|
queue="$(echo "$queue" | sed ${i}d)"
|
|
elif [ "$action" != update ] && [ "$action" != refresh ] && [ ! -d "${DIRECTORY}/apps/${app}" ];then
|
|
yad --class Artian-Apps --name "Artian-Apps" --text="Invalid app "\""<b>$app</b>"\"". Cannot $action it." \
|
|
--text-align=center --center --title='Error' --window-icon="${DIRECTORY}/icons/logo.png" \
|
|
--button=OK!"${DIRECTORY}/icons/check.png":0
|
|
|
|
#remove the app from list
|
|
queue="$(echo "$queue" | sed ${i}d)"
|
|
fi
|
|
|
|
i=$((i+1))
|
|
done
|
|
[ -z "$queue" ] && exit 0
|
|
|
|
#if trying to install an already-installed app, or trying to uninstall and already-uninstalled app, ask for confirmation
|
|
i=1 #track the line number in queue
|
|
for line in $queue ;do
|
|
action="$(echo "$line" | awk -F ';' '{print $1}')"
|
|
app="$(echo "$line" | awk -F ';' '{print $2}')"
|
|
|
|
if [ "$action" == update-file ];then
|
|
# the current line is a pseudo-action used to update a file
|
|
# skip app validation
|
|
true
|
|
elif [ "$(app_status "${app}")" == "${action}ed" ];then
|
|
yad --class Artian-Apps --name "Artian-Apps" --text="<b>$app</b> 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
|
|
queue="$(echo "$queue" | sed ${i}d)"
|
|
fi
|
|
fi
|
|
|
|
i=$((i+1))
|
|
done
|
|
[ -z "$queue" ] && exit 0
|
|
|
|
#Check if any apps are updatable and ask user if they really want to INSTALL an outdated version. (This is skipped if uninstalling apps)
|
|
|
|
local update_queue=''
|
|
for line in $queue ;do
|
|
action="$(echo "$line" | awk -F ';' '{print $1}')"
|
|
app="$(echo "$line" | awk -F ';' '{print $2}')"
|
|
|
|
if [ $action == install ];then
|
|
#determine the filename for the app's script to be run
|
|
script_name_cpu="$(script_name_cpu "$app")"
|
|
|
|
#if the app-script doesn't match version in update folder
|
|
if [ -f "${DIRECTORY}/update/Artian-apps/apps/${app}/${script_name_cpu}" ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/${script_name_cpu}" "${DIRECTORY}/apps/${app}/${script_name_cpu}" ;then
|
|
|
|
"${DIRECTORY}/updater" set-status &>/dev/null & #check for updates in background, so if user chooses "Yes", the updater will be guaranteed to have the app listed
|
|
|
|
yad --class Artian-Apps --name "Artian-Apps" --text="Hold up..."$'\n'"<b>$app</b>'s $script_name_cpu script does not match the online version. Either you are about to install an outdated version, or you've made changes to the script yourself."$'\n\n'"<b>Would you like to install the newest official version of $app?</b>" \
|
|
--text-align=center --center --title='Quick question' --window-icon="${DIRECTORY}/icons/logo.png" --width=400 \
|
|
--image="${DIRECTORY}/apps/$app/icon-64.png" \
|
|
--button="I know what I am doing, Install current version"!"${DIRECTORY}/icons/forward.png":1 --button="Yes, Install newest official version"!"${DIRECTORY}/icons/download.png"!"In most cases, this is the button you should click.":0
|
|
|
|
if [ $? == 0 ];then
|
|
if [ -z "$update_queue" ] && ! files_match "${DIRECTORY}/api" "${DIRECTORY}/update/Artian-apps/api" ;then #if user clicked Yes, update api (for potential new functions) and the app
|
|
update_queue="update-file;api"$'\n'"refresh;$app"
|
|
elif [ -z "$update_queue" ];then
|
|
update_queue="refresh;$app"
|
|
else
|
|
update_queue+=$'\n'"refresh;$app"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ ! -z "$update_queue" ]; then
|
|
# place all updates before queue input to validate_apps_gui
|
|
queue="$update_queue"$'\n'"$queue"
|
|
fi
|
|
echo "$queue"
|
|
}
|
|
|
|
#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/Alvarito050506_mcpi-devs.list ];then
|
|
sudo rm -f /etc/apt/sources.list.d/Alvarito050506_mcpi-devs.list
|
|
fi
|
|
if [ -f /etc/apt/sources.list.d/mcpi-revival.list ];then
|
|
sudo rm -f /etc/apt/sources.list.d/mcpi-revival.list
|
|
sudo rm -f /etc/apt/trusted.gpg.d/mcpi-revival.gpg
|
|
fi
|
|
|
|
if dpkg -l box86-no-binfmt-restart &>/dev/null ;then
|
|
sudo apt purge -y box86-no-binfmt-restart
|
|
sudo apt update
|
|
sudo apt install -y box86
|
|
fi
|
|
EOF
|
|
) &>/dev/null
|
|
|
|
#An apt repository's Packages file can be corrupted so that an apt update will silently fail. See: https://bugs.launchpad.net/ubuntu/+source/apt/+bug/1809174
|
|
#This line will fix the problem by removing any zero-size Packages files.
|
|
removal_list="$(find /var/lib/apt/lists -type f -name '*Packages' -size 0 2>/dev/null)"
|
|
if [ ! -z "$removal_list" ]; then
|
|
if [ x$DISPLAY != x ] ; then
|
|
while ! sudo -n true; do
|
|
yad --class Artian-Apps --name "Artian-Apps" --title="Broken Local Packages Repo Detected" --text="Please enter your user password \nso Artian-apps can attempt a repair:" --image="dialog-password" --entry --hide-text 2>/dev/null | sudo -S echo "" 2>&1 >/dev/null
|
|
done
|
|
fi
|
|
echo "$removal_list" | xargs sudo rm -f
|
|
fi
|
|
|
|
#Silently re-download repo if github repository is over 3 months out of date
|
|
{
|
|
#first compare local git repo's last-commit-time with the current system time (Unix epoch time format)
|
|
current_git_date="$(cd "$DIRECTORY"; git show -s --format=%ct)"
|
|
current_local_date="$(date +%s)"
|
|
if [ -z "$current_git_date" ] || [ "$current_local_date" -gt $(($current_git_date + 7776000)) ];then
|
|
|
|
#if local git repo's last-commit-time is 3 months older than current system time, now compare the local git repo's last-commit-time with the online repo's modification time to avoid false positives.
|
|
#This two-tiered approach prevents unnecessary GitHub API calls and speeds up manage script's execution for normal usage.
|
|
|
|
command -v curl >/dev/null || sudo apt install -y curl
|
|
upstream_git_date="$(curl https://git.fdc.abrish.ir/a.kamyar/PublicFiles/Artian-Apps 2>&1 | grep '"date":' | tail -n 1 | sed 's/"date"://g' | xargs date +%s -d 2>/dev/null)"
|
|
|
|
if [[ "$upstream_git_date" =~ ^[0-9]+$ ]] && ([ -z "$current_git_date" ] || [ "$upstream_git_date" -gt $(($current_git_date + 7776000)) ]);then
|
|
yad --class Artian-Apps --name "Artian-Apps" --window-icon="${DIRECTORY}/icons/logo.png" --width=500 --no-buttons --center --title="Auto-updating Artian-Apps" \
|
|
--text="Your Artian-Apps installation is somehow 3 months out-of-date."$'\n'"Reinstalling Artian-Apps and saving the old version to ${DIRECTORY}-3-months-old..." &
|
|
yadpid=$!
|
|
|
|
clear
|
|
echo -e "\nYour Artian-Apps installation is somehow 3 months out-of-date.\nReinstalling Artian-Apps and saving the old version to ${DIRECTORY}-3-months-old...\n\n" 1>&2
|
|
cd $HOME
|
|
rm -rf ~/Artian-apps-forced-update
|
|
command -v git >/dev/null || sudo apt install -y git
|
|
git clone "$(cat "${DIRECTORY}/etc/git_url")" Artian-apps-forced-update 1>&2 && \
|
|
cp -af "${DIRECTORY}/data" ~/Artian-apps-forced-update && \
|
|
cp -af "${DIRECTORY}/apps" ~/Artian-apps-forced-update && \
|
|
mv -f "$DIRECTORY" "${DIRECTORY}-3-months-old" && \
|
|
mv -f ~/Artian-apps-forced-update "$DIRECTORY"
|
|
sleep 10
|
|
|
|
kill $yadpid 2>/dev/null
|
|
|
|
#run new manage script in background
|
|
"${DIRECTORY}/manage" "$@"
|
|
|
|
#run updater to update apps
|
|
"${DIRECTORY}/updater" gui
|
|
exit $?
|
|
fi
|
|
fi
|
|
}
|
|
|
|
mkdir -p "${DIRECTORY}/data/status" "${DIRECTORY}/data/update-status" "${DIRECTORY}/logs"
|
|
|
|
#remove week-old logfiles
|
|
find "${DIRECTORY}/logs" -type f -mtime +6 -exec rm -f {} \; &>/dev/null &
|
|
|
|
#check if hardware and OS is supported
|
|
if [ "$supported" == "yes" ] || [ "$supported" == "no" ]; then
|
|
true
|
|
else
|
|
if is_supported_system >/dev/null;then
|
|
export supported=yes
|
|
export unsupported_message=''
|
|
else
|
|
export supported=no
|
|
export unsupported_message="$(is_supported_system)"
|
|
fi
|
|
fi
|
|
|
|
if [ "$1" == 'daemon' ];then
|
|
#Daemon to run in the background and install/uninstall/update apps as the user makes selections
|
|
#This allows the user to queue up a list of actions that will run sequentially.
|
|
|
|
#this folder will contain the runtime files necessary to make the daemon work.
|
|
mkdir -p "${DIRECTORY}/data/manage-daemon"
|
|
|
|
#a list of pending steps is displayed with yad. This function adds an item to the list
|
|
add_to_list() { # $1 is action, $2 is app, $3 is exit code status ('0' = success), $4 is output location (usually blank, but can be 'stdout')
|
|
local action="$1"
|
|
local app="$2"
|
|
local status="$3"
|
|
|
|
# $4 specifies where the output should go. Default is to send the output to the yadlist pipe, but if value is 'stdout', output to stdout.
|
|
local output="$4"
|
|
|
|
#choose an icon for this app or file being updated/installed
|
|
if [ -f "${DIRECTORY}/apps/$app/icon-24.png" ];then
|
|
local icon="${DIRECTORY}/apps/$app/icon-24.png"
|
|
elif [ "$action" == update-file ];then
|
|
#determine mimetype of file to display an informative icon in the list
|
|
if [ "$(file -b --mime-type "${DIRECTORY}/${app}")" == 'text/x-shellscript' ];then
|
|
#if updatable file in question is a main Artian-apps shell script, then display shellscript icon.
|
|
local icon="${DIRECTORY}/icons/shellscript.png"
|
|
app+=' (script)'
|
|
elif [[ "${DIRECTORY}/${app}" == *.png ]] || [[ "${DIRECTORY}/${app}" == *.svg ]];then
|
|
local icon="${DIRECTORY}/icons/image.png"
|
|
app+=' (image)'
|
|
else
|
|
#otherwise display txt icon.
|
|
local icon="${DIRECTORY}/icons/txt.png"
|
|
app+=' (file)'
|
|
fi
|
|
action=update
|
|
else
|
|
local icon="${DIRECTORY}/icons/none-24.png"
|
|
fi
|
|
|
|
if [ -z "$status" ];then
|
|
#if there is no status number, then this action has not occured yet.
|
|
local content="${DIRECTORY}/icons/wait.png
|
|
${DIRECTORY}/icons/$action.png
|
|
Will $action
|
|
$icon
|
|
$app"
|
|
elif [ "$status" == 0 ];then
|
|
#if status is 0, then action completed successfully.
|
|
local content="${DIRECTORY}/icons/success.png
|
|
${DIRECTORY}/icons/$action.png
|
|
$(echo "${action^}ed" | sed 's/Updateed/Updated/g')
|
|
$icon
|
|
$app"
|
|
elif [ "$status" == 'in-progress' ];then
|
|
#if status is "in-progress", then action is currently being executed.
|
|
local content="${DIRECTORY}/icons/prompt.png
|
|
${DIRECTORY}/icons/$action.png
|
|
$(echo "${action^}ing..." | sed 's/Updateing/Updating/g')
|
|
$icon
|
|
$app"
|
|
else
|
|
#if status is 1, then action completed unsuccessfully.
|
|
local content="${DIRECTORY}/icons/failure.png
|
|
${DIRECTORY}/icons/$action.png
|
|
${action^} failed
|
|
$icon
|
|
$app"
|
|
fi
|
|
|
|
#write the output to yadlist or stdout
|
|
if [ "$output" == stdout ];then
|
|
echo "$content"
|
|
else
|
|
echo "$content" > "${DIRECTORY}/data/manage-daemon/yadlist"
|
|
fi
|
|
}
|
|
|
|
clear_list() { #clear the queue-viewer window
|
|
echo -e '\f' > "${DIRECTORY}/data/manage-daemon/yadlist"
|
|
}
|
|
|
|
write_list() { #given $queue in $1, rebuild the queue-viewer window. This avoids clearing the list until the new one has been generated, so makes for a smoother experience
|
|
local queue="$1"
|
|
local output='' #variable to send to yadlist file
|
|
|
|
local IFS=$'\n'
|
|
local line
|
|
for line in $queue ;do
|
|
|
|
local action="$(echo "$line" | awk -F';' '{print $1}')"
|
|
local app="$(echo "$line" | awk -F';' '{print $2}')"
|
|
local code="$(echo "$line" | awk -F';' '{print $3}')"
|
|
|
|
output+="$(add_to_list "$action" "$app" "$code" stdout)"$'\n'
|
|
done
|
|
output="${output::-1}" #remove final newline character
|
|
|
|
clear_list
|
|
echo "$output" > "${DIRECTORY}/data/manage-daemon/yadlist"
|
|
}
|
|
|
|
reorder_list() { # given $queue in $1, output a new queue with app refreshes and file updates prioritized before install/uninstalls
|
|
local queue="$1"
|
|
# only reorder queue for actions that have not completed yet (no exit code present)
|
|
local queue_pending
|
|
local queue_pending_refresh
|
|
local queue_pending_files
|
|
local queue_pending_other
|
|
local queue_completed
|
|
local queue_reordered
|
|
local IFS=$'\n'
|
|
local line
|
|
for line in $queue ;do
|
|
if ! [[ "$(echo "$line" | awk -F';' '{print $3}')" =~ ^[0-9]+$ ]] ; then
|
|
# echo "$line is still pending" 1>&2
|
|
queue_pending+="$line"$'\n'
|
|
else
|
|
# echo "$line has been completed already" 1>&2
|
|
queue_completed+="$line"$'\n'
|
|
fi
|
|
done
|
|
[ ! -z "$queue_pending" ] && queue_pending="${queue_pending::-1}" #remove final newline character
|
|
[ ! -z "$queue_completed" ] && queue_reordered+="$queue_completed" #already includes trailing newline
|
|
for line in $queue_pending ;do
|
|
if [[ "$line" =~ ^"refresh;".* ]] ; then
|
|
# echo "$line has the refresh action" 1>&2
|
|
queue_pending_refresh+="$line"$'\n'
|
|
elif [[ "$line" =~ ^"update-file;".* ]];then
|
|
# echo "$line has the update-file action" 1>&2
|
|
queue_pending_files+="$line"$'\n'
|
|
else
|
|
# echo "$line has some other action" 1>&2
|
|
queue_pending_other+="$line"$'\n'
|
|
fi
|
|
done
|
|
[ ! -z "$queue_pending_files" ] && queue_reordered+="$queue_pending_files" #already includes trailing newline
|
|
[ ! -z "$queue_pending_refresh" ] && queue_reordered+="$queue_pending_refresh" #already includes trailing newline
|
|
[ ! -z "$queue_pending_other" ] && queue_reordered+="$queue_pending_other" #already includes trailing newline
|
|
queue_reordered="${queue_reordered::-1}" #remove final newline character
|
|
|
|
echo "$queue_reordered"
|
|
}
|
|
|
|
#make a named pipe so that other daemon processes can notify this master daemon process to complete new tasks
|
|
if [ ! -e "${DIRECTORY}/data/manage-daemon/queue" ];then
|
|
mkfifo "${DIRECTORY}/data/manage-daemon/queue"
|
|
fi
|
|
|
|
#each line in $2 is something like "install Zoom" or "uninstall Arduino"
|
|
queue="$2"
|
|
|
|
#To simplify parsing, place a ';' character between $1 (the action) and subsequent args. (the app)
|
|
queue="$(echo "$queue" | sed 's/^\( *[^ ]\+\) /\1;/')"
|
|
|
|
#validate the selections first
|
|
queue="$(validate_apps_gui "$queue")"
|
|
[ -z "$queue" ] && exit 0
|
|
|
|
#send each requested action to the queue file
|
|
if [ ! -z "$queue" ];then
|
|
echo "$queue" > "${DIRECTORY}/data/manage-daemon/queue" &
|
|
fi
|
|
|
|
#only one instance of this script should ever be running at a time.
|
|
#Use a PID file to check if another daemon process is running.
|
|
if [ -f "${DIRECTORY}/data/manage-daemon/pid" ];then
|
|
#check if PID is running
|
|
if process_exists $(cat "${DIRECTORY}/data/manage-daemon/pid") ;then
|
|
echo "Sending instructions to daemon. (PID $(cat "${DIRECTORY}/data/manage-daemon/pid"))"
|
|
|
|
#Immediately add these new actions to the gui list
|
|
IFS=$'\n'
|
|
for line in $queue ;do
|
|
#get first word of this line - the action. Subsequent words are the name of the app.
|
|
action="$(echo "$line" | awk -F ';' '{print $1}')"
|
|
app="$(echo "$line" | awk -F ';' '{print $2}')"
|
|
|
|
add_to_list "$action" "$app"
|
|
done
|
|
|
|
#exit script - data has been sent to already-running daemon
|
|
exit 0
|
|
fi
|
|
fi #past this point, this instance is acting as the daemon.
|
|
|
|
#write my own PID to the pid file
|
|
echo $$ > "${DIRECTORY}/data/manage-daemon/pid"
|
|
|
|
#Display a list of actions and their current status.
|
|
#This list is updated with new information as time progresses.
|
|
#Another named pipe is created to refresh the yad list later.
|
|
rm -f "${DIRECTORY}/data/manage-daemon/yadlist"
|
|
mkfifo "${DIRECTORY}/data/manage-daemon/yadlist" #make a named pipe
|
|
|
|
[ -z "$geometry2" ] && geometry2='--center'
|
|
|
|
tail -f --retry "${DIRECTORY}/data/manage-daemon/yadlist" | yad --class Artian-Apps --name "Artian-Apps" --width=330 --height=400 "$geometry2" --title='Monitor Progress' \
|
|
--list --tail --no-headers --column=:IMG --column=:IMG --column=Text --column=:IMG --column=Text \
|
|
--separator='\n' --window-icon="${DIRECTORY}/icons/logo.png" \
|
|
--dclick-action=true --select-action=true \
|
|
--no-buttons &
|
|
yadpid=$!
|
|
|
|
trap "kill $yadpid 2>/dev/null" EXIT
|
|
|
|
#Used to track which line of $queue is currently being dealt with.
|
|
current_line_num=1
|
|
|
|
sourced_updater=0 #updater script needs to be sourced if files are updated. This allows it to only be sourced once.
|
|
|
|
queue=''
|
|
IFS=$'\n'
|
|
while true;do #repeat until nothing is left in the queue
|
|
|
|
#check for new actions to be executed
|
|
echo -n > "${DIRECTORY}/data/manage-daemon/queue" & #ensure that the pipe is in write mode to prevent cat from hanging if the queue file is empty
|
|
new_lines="$(tac "${DIRECTORY}/data/manage-daemon/queue")" # tac reverses the order of the list. a plain cat of the file will give the newest item in the queue first.
|
|
|
|
#keep track of all actions for this session with the $queue variable
|
|
if [ -z "$queue" ];then
|
|
queue="$new_lines"
|
|
elif [ ! -z "$new_lines" ];then # add new_lines to queue if new_lines is not empty
|
|
queue+=$'\n'"$new_lines"
|
|
fi
|
|
|
|
# reorder queue list to prioritize app refresh and file update actions
|
|
queue="$(reorder_list "$queue")"
|
|
|
|
if [ ! -z "$new_lines" ] && [ "$sourced_updater" == 0 ] && grep -q "update\|refresh\|update-file" <<<"$new_lines" ;then #source updater if necessary
|
|
source "${DIRECTORY}/updater" source
|
|
sourced_updater=1
|
|
fi
|
|
#echo "length of queue is $(echo "$queue" | wc -l)"
|
|
|
|
#exit loop if queue is complete and no new actions were added to the queue
|
|
if [ "${current_line_num}" -gt "$(echo "$queue" | wc -l)" ];then
|
|
break
|
|
fi
|
|
|
|
#echo "current position in queue is ${current_line_num}"
|
|
#echo "queue is '$queue'"
|
|
|
|
line="$(echo "$queue" | sed -n "${current_line_num}"p)"
|
|
#echo "Now handling request: '$line'"
|
|
|
|
#indicate current action in current line of $queue
|
|
queue="$(echo "$queue" | sed "${current_line_num}s/$/;in-progress/")"
|
|
|
|
#get first word of this line - the action. Subsequent words are the name of the app.
|
|
action="$(echo "$line" | awk -F';' '{print $1}')"
|
|
app="$(echo "$line" | awk -F';' '{print $2}')"
|
|
|
|
#Set terminal title
|
|
echo -ne "\e]0;${action^}ing ${app}\a" | sed 's/Updateing/Updating/g'
|
|
|
|
#refresh the list in queue-viewer window as a background process - skip it if the list is still refreshing from last loop iteration; in game dev this is 'dropped input'
|
|
if [ -z "$write_list_pid" ] || ! process_exists "$write_list_pid" ;then
|
|
write_list "$queue" &
|
|
write_list_pid=$!
|
|
|
|
#secondary list-writing background process - kill it if it exists because write_list just sent a newer version of the list to yad
|
|
[ ! -z "$secondary_write_list_pid" ] && process_exists "$secondary_write_list_pid" && kill "$secondary_write_list_pid"
|
|
else
|
|
#if app1 is refreshed and app2 is then reinstalled, the list would only say app1 is bring refreshed for the entirety of app2's reinstallation, due to the process-skipping.
|
|
#launch a secondary background process that waits for $write_list_pid to finish
|
|
|
|
#only allow one secondary background process to run; kill previous jobs and start a new one
|
|
[ ! -z "$secondary_write_list_pid" ] && process_exists "$secondary_write_list_pid" && kill "$secondary_write_list_pid"
|
|
(while process_exists $write_list_pid ;do sleep 1 ;done ; write_list "$queue") &
|
|
secondary_write_list_pid=$!
|
|
fi
|
|
|
|
#run manage script for app installs, uninstalls, or updates. Avoid using it for file-updates and refreshes because that is out of the scope for manage script.
|
|
if [ "$action" == update-file ];then
|
|
update_file "$app"
|
|
exitcode=$?
|
|
elif [ "$action" == refresh ];then
|
|
refresh_app "$app"
|
|
exitcode=$?
|
|
else
|
|
"${DIRECTORY}/manage" "$action" "$app"
|
|
exitcode=$?
|
|
fi
|
|
|
|
#record exit code in current line of $queue
|
|
queue="$(echo "$queue" | sed "${current_line_num}s/;in-progress$/;$exitcode/")"
|
|
|
|
#one more line of $queue has been completed.
|
|
current_line_num=$((current_line_num+1))
|
|
done
|
|
|
|
#refresh the list in queue-viewer window for the final time with all actions complete
|
|
[ ! -z "$secondary_write_list_pid" ] && process_exists "$secondary_write_list_pid" && kill "$secondary_write_list_pid" #kill secondary list writer
|
|
wait $write_list_pid
|
|
write_list "$queue"
|
|
|
|
#before exiting the loop, add a line to the queue-viewer window indicating that all items have completed.
|
|
echo "${DIRECTORY}/icons/none-1.png
|
|
${DIRECTORY}/icons/none-1.png
|
|
Done.
|
|
${DIRECTORY}/icons/none-1.png
|
|
" > "${DIRECTORY}/data/manage-daemon/yadlist"
|
|
|
|
#all actions have been completed. Daemon has effectively stopped listening, so remove its pid file
|
|
rm -f "${DIRECTORY}/data/manage-daemon/pid"
|
|
|
|
#close the queue-viewer window in a few seconds
|
|
(sleep 5; kill $yadpid 2>/dev/null) &
|
|
|
|
#diagnose every failed app's logfile - list item format is $action;$app;$exitcode
|
|
failed_apps="$(echo "$queue" | grep ';1$' | awk -F';' '{print $2}')"
|
|
diagnose_apps "$failed_apps"
|
|
|
|
# if update refresh or update-file actions were run then update the .git folder
|
|
if [ "$sourced_updater" == 1 ]; then
|
|
update_git
|
|
fi
|
|
|
|
#updates could have been run as part of the manage-daemon, so update the updatable-files and updatable-apps status files
|
|
"${DIRECTORY}/updater" set-status
|
|
|
|
elif [ "$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
|
|
|
|
#check if any app names are invalid - use the validate_apps_gui function which requires the action to prefix each line.
|
|
queue="$(validate_apps_gui "$(echo "$app_list" | sed "s/^/${action};/g")")"
|
|
[ -z "$queue" ] && exit 0
|
|
|
|
if grep -q "update\|refresh\|update-file" <<<"$queue" ;then #source updater if necessary
|
|
source "${DIRECTORY}/updater" source
|
|
fi
|
|
|
|
#install/uninstall one app at a time. If it fails then add the app to the list of failed apps
|
|
IFS=$'\n'
|
|
failed_apps=''
|
|
for line in $queue ;do
|
|
action="$(echo "$line" | awk -F ';' '{print $1}')"
|
|
app="$(echo "$line" | awk -F ';' '{print $2}')"
|
|
|
|
#Set terminal title
|
|
echo -ne "\e]0;${action^}ing ${app}\a"
|
|
|
|
#run manage script for app installs, uninstalls, or updates. Avoid using it for file-updates and refreshes because that is out of the scope for manage script.
|
|
if [ "$action" == update-file ];then
|
|
update_file "$app"
|
|
exitcode=$?
|
|
elif [ "$action" == refresh ];then
|
|
refresh_app "$app"
|
|
exitcode=$?
|
|
else
|
|
"${DIRECTORY}/manage" "$action" "$app"
|
|
exitcode=$?
|
|
fi
|
|
|
|
if [ $exitcode != 0 ];then
|
|
#this app failed to install - add it to the list of failed apps
|
|
failed_apps+="$app"$'\n'
|
|
fi
|
|
done
|
|
|
|
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
|
|
action=install
|
|
else
|
|
action=uninstall
|
|
fi
|
|
|
|
if [ "$action" == install ];then
|
|
#check for internet connection
|
|
errors="$(command wget --spider https://github.com 2>&1)"
|
|
if [ $? != 0 ];then
|
|
error "No internet connection! (github.com failed to respond)\nErrors:\n$errors"
|
|
fi
|
|
fi
|
|
|
|
#ensure not a disabled app
|
|
if [ "$action" == install ] && [ "$(app_status "${app}")" == 'disabled' ];then
|
|
warning "Not installing the $app app. IT IS DISABLED."
|
|
exit 0
|
|
fi
|
|
|
|
#determine path for log file to be created
|
|
logfile="${DIRECTORY}/logs/${action}-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
|
|
|
|
#display warning if hardware and os is unsupported
|
|
if [ "$supported" == no ];then
|
|
|
|
warning "YOUR SYSTEM IS UNSUPPORTED:\n$unsupported_message" 2>&1 | tee -a "$logfile"
|
|
sleep 1
|
|
echo -e "\e[103m\e[30mThe ability to send error reports has been disabled.\e[39m\e[49m" | tee -a "$logfile"
|
|
sleep 1
|
|
echo -e "\e[103m\e[30mWaiting 10 seconds... (To cancel, press Ctrl+C or close this terminal)\e[39m\e[49m" | tee -a "$logfile"
|
|
sleep 10
|
|
fi
|
|
|
|
#if this app has scripts, determine which script to run
|
|
if [ "$(app_type "$app")" == standard ];then
|
|
if [ "$action" == install ];then
|
|
scriptname="$(script_name_cpu "$app")" #will be install, install-32, or install-64
|
|
if [ -z "$scriptname" ];then
|
|
error "It appears $app does not have an install-${arch} script suitable for your ${arch}-bit OS." | tee -a "$logfile"
|
|
exit 1
|
|
fi
|
|
else #uninstall mode
|
|
scriptname=uninstall
|
|
fi
|
|
appscript=("${DIRECTORY}/apps/${app}/${scriptname}")
|
|
chmod u+x "$appscript" &>/dev/null
|
|
#if this app just lists a package-name, set the appscript to install that package
|
|
else
|
|
appscript=(bash -c -o pipefail "apt_lock_wait ; sudo -E apt $(echo "$action" | sed 's/uninstall/purge --autoremove/g') -yf $(cat "${DIRECTORY}/apps/$app/packages") 2>&1 | less_apt")
|
|
fi
|
|
|
|
#print to terminal
|
|
status "${action^}ing \e[1m${app}\e[0m\e[96m..." | tee -a "$logfile"
|
|
echo
|
|
cd $HOME
|
|
if [ "$3" == "update" ]; then
|
|
script_input="update"
|
|
else
|
|
script_input=""
|
|
fi
|
|
nice "${appscript[@]}" "$script_input" &> >(tee -a "$logfile")
|
|
exitcode="${PIPESTATUS[0]}"
|
|
|
|
#if app succeeded
|
|
if [ $exitcode == 0 ];then
|
|
|
|
#Contribute to app install/uninstall count as long as parent processes is NOT updater (during an app-reinstall)
|
|
#See: https://askubuntu.com/a/1012236
|
|
if [ "$(cat /proc/$PPID/comm)" != "updater" ] && [ "$3" != "update" ];then
|
|
shlink_link "$app" "$action" &
|
|
fi
|
|
|
|
status_green "\n${action^}ed ${app} successfully." | tee -a "$logfile"
|
|
echo "${action}ed" > "${DIRECTORY}/data/status/${app}"
|
|
|
|
format_logfile "$logfile" #remove escape sequences from logfile
|
|
mv "$logfile" "$(echo "$logfile" | sed 's+-incomplete-+-success-+g')" #rename logfile to indicate it was successful
|
|
|
|
else #if app failed to install/uninstall
|
|
|
|
#remove dummy deb if app failed to install (to avoid dummy debs being left on a users install for broken apps)
|
|
package_name="$(app_to_pkgname "$app")"
|
|
if [[ ${action} == "install" ]] && [[ "$(app_type "$app")" == standard ]] && package_installed "$package_name"; then
|
|
#Run purge_packages
|
|
echo $'\n'"Running purge_packages..." >> "$logfile"
|
|
purge_packages 2>&1 | tee -a "$logfile" >/dev/null
|
|
fi
|
|
unset package_name
|
|
|
|
echo -e "\n\e[91mFailed to ${action} ${app}!\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://git.fdc.abrish.ir/a.kamyar/PublicFiles/Artian-Apps\e[24m\e[93m
|
|
Or on Discord: \e[94m\e[4mhttps://discord.gg/RXSTvaUvuu\e[0m" | tee -a "$logfile"
|
|
|
|
#set the app's status to 'corrupted' if the diagnostics determine the error is NOT internet or system or package, AND if the app is a script-type app
|
|
if ! [[ "$(log_diagnose "$logfile" | head -n1)" =~ ^(system|internet|package)$ ]] && [ "$(app_type "$app")" == standard ];then
|
|
echo "corrupted" > "${DIRECTORY}/data/status/${app}"
|
|
fi
|
|
|
|
format_logfile "$logfile" #remove escape sequences from logfile
|
|
mv "$logfile" "$(echo "$logfile" | sed 's+-incomplete-+-fail-+g')" #rename logfile to indicate it was unsuccessful
|
|
fi
|
|
|
|
#if the app is a package, set its status to whatever dpkg thinks it is
|
|
if [ "$(app_type "$app")" == package ];then
|
|
refresh_pkgapp_status "$app"
|
|
fi
|
|
|
|
#exit the manage script with the same exit-code of the app's script
|
|
exit $exitcode
|
|
|
|
elif [ "$1" == 'update' ];then #user-facing argument to update an app. Artian-Apps scripts should directly use updater's update_app function.
|
|
#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
|
|
|
|
# make sure update_app function is available
|
|
typeset -f update_app &>/dev/null || source "${DIRECTORY}/updater" source
|
|
|
|
update_app "$app" || exit $?
|
|
|
|
elif [ "$1" == 'check-all' ];then #The manage script no longer handles updates. This mode is only for backwards-compatibility and uses the updater script
|
|
|
|
warning "The manage script ONLY updates apps, and this mode has been replaced by the updater script.
|
|
If you want to update Artian-Apps from the command-line, please use:
|
|
~/Artian-apps/updater cli-yes"
|
|
|
|
#get functions from updater script
|
|
source "${DIRECTORY}/updater" source
|
|
|
|
check_repo
|
|
get_updatable_apps
|
|
|
|
elif [ "$1" == 'update-all' ];then #The manage script no longer handles updates. This mode is only for backwards-compatibility and uses the updater script
|
|
|
|
warning "The manage script ONLY updates apps, and this mode has been replaced by the updater script.
|
|
If you want to update Artian-Apps from the command-line, please use:
|
|
~/Artian-apps/updater cli-yes"
|
|
|
|
#get functions from updater script
|
|
source "${DIRECTORY}/updater" source
|
|
|
|
check_repo
|
|
updatable_apps="$(get_updatable_apps)"
|
|
updatable_files=''
|
|
|
|
update_now_cli
|
|
|
|
else
|
|
error "Invalid mode. ($1) Allowed values: 'install', 'multi-install', 'install-if-not-installed', 'uninstall', 'multi-uninstall', 'update', 'update-all', 'check-all', or 'daemon'."
|
|
fi
|
|
|
|
# exit script when finished. Prevents errors if script was modified while in use and new script is longer than previous script.
|
|
exit "$?"
|
|
}
|