3279 lines
160 KiB
Bash
3279 lines
160 KiB
Bash
#!/bin/bash
|
|
|
|
#This script is the backbone of Artian-Apps. It hosts functions that all other scripts depend upon.
|
|
#By default, this file is hard to read. But there's an easy way to fix that. In your code editor, find the option to "Fold All".
|
|
#In Geany, this option can be found in the Document toolbar.
|
|
#Botspot has found it helpful to create a keyboard shortcut to do this quickly. You may find it helpful too.
|
|
|
|
#output functions below
|
|
error() { #red text and exit 1
|
|
echo -e "\e[91m$1\e[0m" 1>&2
|
|
exit 1
|
|
}
|
|
|
|
warning() { #yellow text
|
|
echo -e "\e[93m\e[5m◢◣\e[25m WARNING: $1\e[0m" 1>&2
|
|
}
|
|
|
|
status() { #cyan text to indicate what is happening
|
|
|
|
#detect if a flag was passed, and if so, pass it on to the echo command
|
|
if [[ "$1" == '-'* ]] && [ ! -z "$2" ];then
|
|
echo -e $1 "\e[96m$2\e[0m" 1>&2
|
|
else
|
|
echo -e "\e[96m$1\e[0m" 1>&2
|
|
fi
|
|
}
|
|
|
|
status_green() { #announce the success of a major action
|
|
echo -e "\e[92m$1\e[0m" 1>&2
|
|
}
|
|
|
|
generate_logo() { #display colorized Artian-Apps logo in terminal
|
|
#ANSI color codes: https://misc.flogisoft.com/bash/tip_colors_and_formatting
|
|
#Search for unicode characters: https://unicode-search.net - search for "block" and "quadrant"
|
|
|
|
# foreground colors
|
|
blue1='\e[38;5;75m'
|
|
blue2='\e[38;5;26m'
|
|
blue3='\e[38;5;21m'
|
|
blue4='\e[38;5;93m'
|
|
|
|
green='\e[38;5;46m'
|
|
darkgreen='\e[38;5;34m'
|
|
red='\e[38;5;197m'
|
|
white='\e[97m'
|
|
black='\e[30m'
|
|
default='\e[39m'
|
|
# background colors
|
|
bg_default='\e[49m'
|
|
bg_black='\e[40m'
|
|
bg_white='\e[107m'
|
|
|
|
#use simpler logo if OS is Buster or lower - to fix issue https://github.com/Botspot/pi-apps/issues/1441
|
|
local version_id="$(grep 'VERSION_ID=' /etc/os-release | tr -cd '0123456789.')"
|
|
if [[ "$version_id" != *\.* ]] && [ "$version_id" -ge 11 ];then
|
|
echo -e "\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[48;5;31m \e[38;5;31;48;5;31m▄\e[49m \e[m
|
|
\e[49m \e[48;5;38m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄\e[49;38;5;38m▀▀▀▀▀▀▀\e[49m \e[49;38;5;31m▀▀▀▀▀▀▀\e[38;5;31;48;5;31m▄▄▄▄\e[48;5;31m \e[38;5;31;48;5;32m▄\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀▀▀\e[49;38;5;39m▀\e[49m \e[49;38;5;31m▀▀▀▀▀\e[49m \e[38;5;15;49m▄▄\e[49m \e[38;5;15;49m▄▄▄▄▄▄\e[49m \e[38;5;15;49m▄▄▄▄▄▄▄▄▄▄▄▄\e[49m \e[38;5;15;49m▄▄\e[49m \e[38;5;15;49m▄\e[49m \e[38;5;15;49m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄▄▄▄▄▄▄▄▄\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄▄\e[38;5;31;49m▄▄▄▄▄▄▄▄▄▄\e[49m \e[38;5;15;49m▄\e[48;5;15m \e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀▀▀▀▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49;38;5;15m▀▀▀▀\e[48;5;15m \e[49;38;5;15m▀▀▀▀▀\e[48;5;15m \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄▄▄\e[38;5;31;49m▄▄\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;32;49m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄\e[48;5;15m \e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀▀▀▀▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄▄▄▄\e[49;38;5;31m▀▀▀▀▀▀▀▀▀\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49m \e[38;5;15;48;5;15m▄▄\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[49m \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49;38;5;15m▀\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄▄\e[49m \e[38;5;31;49m▄▄▄▄▄▄\e[38;5;31;48;5;31m▄\e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄\e[49m \e[38;5;31;49m▄▄▄▄▄\e[38;5;31;48;5;32m▄\e[38;5;31;48;5;31m▄▄\e[48;5;31m \e[38;5;31;49m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;25;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
"
|
|
else
|
|
echo -e "\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[48;5;31m \e[38;5;31;48;5;31m▄\e[49m \e[m
|
|
\e[49m \e[48;5;38m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄\e[49;38;5;38m▀▀▀▀▀▀▀\e[49m \e[49;38;5;31m▀▀▀▀▀▀▀\e[38;5;31;48;5;31m▄▄▄▄\e[48;5;31m \e[38;5;31;48;5;32m▄\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀▀▀\e[49;38;5;39m▀\e[49m \e[49;38;5;31m▀▀▀▀▀\e[49m \e[38;5;15;49m▄▄\e[49m \e[38;5;15;49m▄▄▄▄▄▄\e[49m \e[38;5;15;49m▄▄▄▄▄▄▄▄▄▄▄▄\e[49m \e[38;5;15;49m▄▄\e[49m \e[38;5;15;49m▄\e[49m \e[38;5;15;49m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄▄▄▄▄▄▄▄▄\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄▄\e[38;5;31;49m▄▄▄▄▄▄▄▄▄▄\e[49m \e[38;5;15;49m▄\e[48;5;15m \e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀▀▀▀▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49;38;5;15m▀▀▀▀\e[48;5;15m \e[49;38;5;15m▀▀▀▀▀\e[48;5;15m \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄▄▄\e[38;5;38;48;5;38m▄▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄▄▄\e[38;5;31;49m▄▄\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[38;5;15;48;5;15m▄▄\e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;32;49m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄\e[48;5;15m \e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[49;38;5;15m▀▀\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[38;5;15;49m▄▄▄▄▄▄\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀▀▀▀▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[38;5;38;48;5;38m▄▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄▄▄▄\e[49;38;5;31m▀▀▀▀▀▀▀▀▀\e[49m \e[48;5;15m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;49m▄\e[49m \e[48;5;15m \e[49m \e[48;5;15m \e[49m \e[38;5;15;49m▄\e[38;5;15;48;5;15m▄\e[49;38;5;15m▀▀▀▀▀▀▀▀\e[38;5;15;48;5;15m▄▄\e[49m \e[48;5;15m \e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[48;5;15m \e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49m \e[38;5;15;48;5;15m▄▄\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[49m \e[49;38;5;15m▀\e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49;38;5;15m▀\e[49m \e[38;5;15;48;5;15m▄\e[49m \e[48;5;15m \e[49;38;5;15m▀\e[49m \e[49;38;5;15m▀\e[48;5;15m \e[38;5;15;48;5;15m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;49m▄\e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄▄\e[49m \e[38;5;31;49m▄▄▄▄▄▄\e[38;5;31;48;5;31m▄\e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;31;49m▄▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[38;5;38;48;5;38m▄\e[38;5;38;49m▄▄▄▄▄\e[49m \e[38;5;31;49m▄▄▄▄▄\e[38;5;31;48;5;32m▄\e[38;5;31;48;5;31m▄▄\e[48;5;31m \e[38;5;31;49m▄\e[49m \e[m
|
|
\e[49m \e[38;5;38;48;5;38m▄\e[48;5;38m \e[48;5;31m \e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄▄\e[48;5;38m \e[48;5;31m \e[38;5;31;48;5;31m▄\e[38;5;25;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[49;38;5;38m▀▀▀\e[38;5;38;48;5;38m▄\e[38;5;31;48;5;31m▄\e[49;38;5;31m▀▀▀\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
\e[49m \e[m
|
|
"
|
|
fi
|
|
}
|
|
#end of output functions
|
|
|
|
add_english() { #add en_US locale for more accurate error
|
|
if [ "$(cat /usr/share/i18n/SUPPORTED | grep -o 'en_US.UTF-8' )" == "en_US.UTF-8" ]; then
|
|
locale=$(locale -a | grep -oF 'en_US.utf8')
|
|
if [ "$locale" != 'en_US.utf8' ]; then
|
|
status "Adding en_US locale for better logging... "
|
|
sudo sed -i '/en_US.UTF-8/s/^#[ ]//g' /etc/locale.gen
|
|
sudo locale-gen
|
|
fi
|
|
else
|
|
warning "en_US locale is not available on your system. This may cause bad logging experience."
|
|
fi
|
|
export LANG="en_US.UTF-8"
|
|
export LANGUAGE="en_US.UTF-8"
|
|
export LC_ALL="en_US.UTF-8"
|
|
}
|
|
|
|
#package functions
|
|
package_info() { #list everything dpkg knows about the $1 package. Note: the package has to be installed for this to show anything.
|
|
local package="$1"
|
|
[ -z "$package" ] && error "package_info(): no package specified!"
|
|
#list lines in /var/lib/dpkg/status between the package name and the next empty line (empty line is then removed)
|
|
sed -n -e '/^Package: '"$package"'$/,/^$/p' /var/lib/dpkg/status | head -n -1
|
|
true #this may exit with code 141 if the pipe was closed early (to be expected with grep -v)
|
|
}
|
|
|
|
package_installed() { #exit 0 if $1 package is installed, otherwise exit 1
|
|
local package="$1"
|
|
[ -z "$package" ] && error "package_installed(): no package specified!"
|
|
#find the package listed in /var/lib/dpkg/status
|
|
#package_info "$package"
|
|
|
|
#directly search /var/lib/dpkg/status
|
|
grep "^Package: $package$" /var/lib/dpkg/status -A 1 | tail -n 1 | grep -q 'Status: install ok installed'
|
|
}
|
|
|
|
package_available() { #determine if the specified package-name exists in a repository
|
|
local package="$1"
|
|
[ -z "$package" ] && error "package_available(): no package name specified!"
|
|
#using find and grep to do this is nearly instantaneous, rather than apt-cache which takes several seconds
|
|
local IFS=$'\n'
|
|
for file in $(find /var/lib/apt/lists -maxdepth 1 -type f -name "*_Packages") ;do
|
|
grep -q "^Package: $package$" "$file" && break
|
|
done
|
|
}
|
|
|
|
package_dependencies() { #outputs the list of dependencies for the $1 package
|
|
local package="$1"
|
|
[ -z "$package" ] && error "package_dependencies(): no package specified!"
|
|
|
|
#find the package listed in /var/lib/dpkg/status
|
|
package_info "$package" | grep '^Depends: ' | sed 's/^Depends: //g'
|
|
}
|
|
|
|
package_latest_version() { #returns the latest available versions of the specified package-name. Doesn't matter if it's installed or not.
|
|
local package="$1"
|
|
[ -z "$package" ] && error "package_latest_version(): no package specified!"
|
|
|
|
# use slower but more accurate apt list command to get package version for current architecture
|
|
apt-cache policy "$package" 2>/dev/null | grep "Candidate: " | awk '{print $2}'
|
|
#grep -rx "Package: $package" /var/lib/apt/lists --exclude="lock" --exclude-dir="partial" --after 4 | grep -o 'Version: .*' | awk '{print $2}' | sort -rV | head -n1
|
|
}
|
|
|
|
package_is_new_enough() { #check if the $1 package has an available version greater than or equal to $2
|
|
local package="$1"
|
|
[ -z "$package" ] && error "package_is_new_enough(): no package specified!"
|
|
|
|
local compare_version="$2"
|
|
[ -z "$package" ] && error "package_is_new_enough(): no comparison version number specified!"
|
|
|
|
#determine the latest available version for the specified package
|
|
local package_version="$(package_latest_version "$package")"
|
|
|
|
#if version value not found, return 1 now
|
|
if [ -z "$package_version" ];then
|
|
return 1
|
|
fi
|
|
|
|
#given both the package_version and compare_version, see if the greater of the two is the available package's version
|
|
if [ "$(echo "$package_version"$'\n'"$compare_version" | sort -rV | head -n1)" == "$package_version" ];then
|
|
#if so, indicate success
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
anything_installed_from_repo() { #Given an apt repository URL, determine if any packages from it are currently installed
|
|
[ -z "$1" ] && error "anything_installed_from_repo: A repository URL must be specified."
|
|
|
|
#user input repo-url. Remove 'https://', and translate '/' to '_' to conform to apt file-naming standard
|
|
local url="$(echo "$1" | sed 's+.*://++g' | tr '/' '_')"
|
|
|
|
#find all package-lists pertaining to the url
|
|
local repofiles="$(ls /var/lib/apt/lists/*_Packages | grep "$url")"
|
|
|
|
#for every repo-file, check if any of them have an installed file
|
|
local found=0
|
|
local IFS=$'\n'
|
|
local repofile
|
|
for repofile in $repofiles ;do
|
|
#search the repo-file for installed packages
|
|
grep '^Package' "$repofile" | awk '{print $2}' | while read -r package ;do
|
|
if package_installed "$package" ;then
|
|
echo "Package installed: $package"
|
|
exit 1
|
|
fi
|
|
done #if exit code is 1, search was successful. If exit code is 0, no packages from the repo were installed.
|
|
|
|
found=$?
|
|
|
|
if [ $found == 1 ];then
|
|
break
|
|
fi
|
|
done
|
|
|
|
#return an exit code
|
|
if [ $found == 1 ];then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
remove_repofile_if_unused() { #Given a sources.list.d file, delete it if nothing from that repository is currently installed. Deletion skipped if $2 is 'test'
|
|
local file="$1"
|
|
local testmode="$2"
|
|
local key="$3"
|
|
[ -z "$file" ] && error "remove_repo_if_unused: no sources.list.d file specified!"
|
|
#return now if the list file does not exist
|
|
[ -f "$file" ] || return 0
|
|
|
|
#determine what repo-urls are in the file
|
|
local urls="$(cat "$file" | grep -v '^#' | tr ' ' '\n' | grep '://')"
|
|
|
|
#there could be multiple urls in one file. Check each url and set the in_use variable to 1 if any packages are found
|
|
local IFS=$'\n'
|
|
local in_use=0
|
|
local url
|
|
for url in $urls ;do
|
|
if anything_installed_from_repo "$url" >/dev/null;then
|
|
in_use=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$in_use" == 0 ] && [ "$testmode" == test ];then
|
|
echo "The given repository is not in use and can be deleted:"$'\n'"$file" 1>&2
|
|
elif [ "$in_use" == 0 ];then
|
|
status "Removing the $(basename "$file" | sed 's/.list$//g') repo as it is not being used"
|
|
sudo rm -f "$file"
|
|
[ -f "$key" ] && sudo rm -f "$key" || true
|
|
fi
|
|
|
|
}
|
|
|
|
#apt functions
|
|
apt_lock_wait() { #Wait until other apt processes are finished before proceeding
|
|
#make sure english locale is added first
|
|
add_english
|
|
|
|
#check if sudo needs a password currently. this prevents sudo asking for a password in the below fuser command which would have "Waiting until APT locks are released... " written after it after 5 seconds
|
|
#the result would look like something in the terminal to a user "[sudo] password for USER: Waiting until APT locks are released... " which might be confusing
|
|
#this is very often the first command that Artian-apps scripts run that requires sudo privileges from the user
|
|
#passwordless sudo (like on piOS) will always skip the contents of the if
|
|
if ! sudo -n true ; then
|
|
# sudo needs a password so prompt the user to give one before running other commands
|
|
sudo echo > /dev/null
|
|
fi
|
|
|
|
#in a background subprocess, after 5 seconds, say "Waiting until APT locks are released... "
|
|
(sleep 5; echo -n "Waiting until APT locks are released... ") &
|
|
local pid=$!
|
|
|
|
while [ ! -z "$(sudo fuser /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/log/unattended-upgrades/unattended-upgrades.log /var/lib/dpkg/lock-frontend 2>/dev/null)" ];do
|
|
sleep 1
|
|
done
|
|
|
|
#Try to install a non-existent package to see if apt fails due to a lock-file. Repeat until no errors mention 'Could not get lock'
|
|
while sudo -E apt install lkqecjhxwqekc 2>&1 | grep -q 'Could not get lock' ;do
|
|
sleep 1
|
|
done
|
|
|
|
#If the background process finished, then that means the "waiting until" message was displayed. This means the kill command will return 1, so echo Done
|
|
kill $pid &>/dev/null || echo "Done"
|
|
}
|
|
|
|
less_apt() { #remove unwanted lines from apt output
|
|
grep --line-buffered -v "apt does not have a stable CLI interface.\|Reading package lists...\|Building dependency tree\|Reading state information...\|Need to get\|Selecting previously unselected package\|Preparing to unpack\|Setting up \|Processing triggers for \|^$"
|
|
}
|
|
|
|
apt_update() { #run an apt update with error-checking and minimal output
|
|
apt_lock_wait
|
|
|
|
status "Running \e[7msudo apt update\e[27m..."
|
|
output="$(sudo -E apt update --allow-releaseinfo-change "$@" 2>&1 | less_apt | tee /dev/stderr)"
|
|
exitcode=$?
|
|
status "apt update complete."
|
|
|
|
#inform user about autoremovable packages
|
|
if [ ! -z "$(echo "$output" | grep 'autoremove to remove them' )" ];then
|
|
echo -e "\e[33mSome packages are unnecessary.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt autoremove\e[0m."
|
|
fi
|
|
|
|
#inform user packages are upgradeable
|
|
if [ ! -z "$(echo "$output" | grep 'packages can be upgraded' )" ];then
|
|
echo -e "\e[33mSome packages can be upgraded.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt full-u\e[0mpg\e[4mrade\e[0m."
|
|
elif [ ! -z "$(echo "$output" | grep 'package can be upgraded' )" ];then
|
|
echo -e "\e[33mOne package can be upgraded.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt full-u\e[0mpg\e[4mrade\e[0m."
|
|
fi
|
|
|
|
#exit on apt error
|
|
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
|
|
if [ $exitcode != 0 ] || [ ! -z "$errors" ];then
|
|
echo -e "\e[91mFailed to run \e[4msudo apt update\e[0m\e[39m!"
|
|
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
|
|
|
|
#run some apt error diagnosis
|
|
echo "$output"
|
|
exit 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
repo_add() { #add local packages to the /tmp/Artian-apps-local-packages repository
|
|
#given local deb file(s), make a local apt repository in /tmp/Artian-apps-local-packages
|
|
|
|
#see: https://unix.stackexchange.com/questions/87130/how-to-quickly-create-a-local-apt-repository-for-random-packages-using-a-debian
|
|
#and: https://serverfault.com/questions/447457/use-apt-get-source-on-a-debian-repo-without-using-etc-apt-source-list
|
|
#and: https://askubuntu.com/questions/382664/use-custom-directory-for-apt-get
|
|
|
|
#use this flag for apt commands to use the local repository: -o Dir::Etc::SourceList=/tmp/Artian-apps-local-packages/source.list
|
|
|
|
#ensure the repo-folder exists
|
|
mkdir -p /tmp/Artian-apps-local-packages || error "repo_add(): failed to create folder /tmp/Artian-apps-local-packages"
|
|
|
|
#move every mentioned deb file to it
|
|
for file in "$@"; do
|
|
mv -f "$file" /tmp/Artian-apps-local-packages || error "repo_add(): failed to move '$file' to the repository: /tmp/Artian-apps-local-packages"
|
|
done
|
|
}
|
|
|
|
repo_refresh() { #index the Artian-apps local apt repository
|
|
|
|
[ -d /tmp/Artian-apps-local-packages ] || error "repo_update(): cannot index the repository - it's missing! /tmp/Artian-apps-local-packages"
|
|
|
|
#index the repository by creating a Packages file
|
|
(cd /tmp/Artian-apps-local-packages && apt-ftparchive packages . > Packages) || error "repo_update(): apt-ftparchive failed to index the repository: /tmp/Artian-apps-local-packages
|
|
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
|
|
#Make sure the Packages file actually exists
|
|
[ -f /tmp/Artian-apps-local-packages/Packages ] || error "repo_update(): apt-ftparchive failed to index the repository: /tmp/Artian-apps-local-packages
|
|
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
|
|
|
|
#by default, apt-ftparchive will generate lines like "Filename: ./package-name". This seemed to have caused one error-report, so we remove the "./" to hopefully solve the problem.
|
|
sed -i 's+^Filename: \./+Filename: +g' /tmp/Artian-apps-local-packages/Packages
|
|
|
|
# set repo origin name to Artian-apps-local-packages - see PR #1986
|
|
echo 'APT::FTPArchive::Release {
|
|
Origin "Artian-apps-local-packages";
|
|
};' > /tmp/Artian-apps-local-packages/aptftp.conf
|
|
|
|
#hash the repository by creating a Release file
|
|
(cd /tmp/Artian-apps-local-packages && apt-ftparchive -c=/tmp/Artian-apps-local-packages/aptftp.conf release . > Release) || error "repo_update(): apt-ftparchive failed to hash the repository: /tmp/Artian -apps-local-packages
|
|
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
|
|
|
|
#create a source.list for the repository - add Artian-apps-local-packages to the top, then add lines from /etc/apt/sources.list
|
|
rm -f /tmp/Artian-apps-local-packages/source.list
|
|
echo "deb [trusted=yes] file:/tmp/Artian-apps-local-packages/ ./" > /tmp/Artian-apps-local-packages/source.list
|
|
cat /etc/apt/sources.list >> /tmp/Artian-apps-local-packages/source.list
|
|
|
|
# add priority to all Artian-apps-local-packages so that they always have higher priority than repo versions - see PR #1986
|
|
# this has no affect when there the Artian-apps-local-packages repo does not exist
|
|
echo 'Package: *
|
|
Pin: Release o=Artian-apps-local-packages
|
|
Pin-Priority: 990' | sudo tee /etc/apt/preferences.d/Artian-apps-local-packages >/dev/null
|
|
}
|
|
|
|
repo_rm() { #remove the local apt repository
|
|
#wait for other operations to finish before continuing - hopefully this will solve cases when the Artian-apps local repository was removed unexpectedly by a second process
|
|
apt_lock_wait
|
|
|
|
rm -rf /tmp/Artian-apps-local-packages || sudo rm -rf /tmp/Artian-apps-local-packages || error "repo_rm(): failed to remove the local repository: /tmp/Artian-apps-local-packages"
|
|
|
|
#Also remove broken symbolic link to /tmp/Artian-apps-local-packages/./Packages
|
|
sudo rm -f /var/lib/apt/lists/_tmp_Artian-apps-local-packages_._Packages
|
|
}
|
|
|
|
app_to_pkgname() { #given an app-name, convert it to a unique, valid package-name that starts with 'Artian-apps-'
|
|
local app="$1"
|
|
[ -z "$app" ] && error "app_to_pkgname(): no app-name specified"
|
|
|
|
echo "Artian-apps-$(echo "$app" | md5sum | cut -c1-8 | awk '{print $1}')"
|
|
}
|
|
|
|
install_packages() { #Make some packages dependencies of the $app app. Package-names, regex, filenames, and urls are supported.
|
|
|
|
#convert input array to newline-separated string
|
|
local IFS=' '
|
|
for arg in "$@"; do
|
|
local packages+="$arg
|
|
"
|
|
done
|
|
packages="${packages::-1}" #remove final empty newline
|
|
|
|
#the $app variable must contain something
|
|
[ -z "$app" ] && error 'install_packages function can only be used by apps to install packages. (the $app variable was not set)'
|
|
|
|
status "Will install these packages: $(tr '\n' ' ' <<<"$packages")"
|
|
|
|
#array-variable to store custom apt options (for local repositories)
|
|
local apt_flags=()
|
|
|
|
#variable to remember if the Artian-apps-local-packages repository is being used
|
|
local using_local_packages=0
|
|
repo_rm #remove the local repo, just in case the last operation left it in an unrecoverable state.
|
|
|
|
#handle regex, urls, local packages
|
|
IFS=$'\n'
|
|
local package
|
|
for package in $packages ;do
|
|
|
|
#handle local packages (package-name starts with /)
|
|
if [[ "$package" == /* ]];then
|
|
|
|
#status "Handling local package $package"
|
|
|
|
[ -f "$package" ] || error "install_packages(): Local package does not exist! ($package)"
|
|
|
|
#determine the package name from the filename
|
|
packagename="$(dpkg-deb -I "$package" | grep "^ Package:" | awk '{print $2}')"
|
|
packageversion="$(dpkg-deb -I "$package" | grep "^ Version:" | awk '{print $2}')"
|
|
[ -z "$packagename" ] && error "install_packages(): failed to determine a package-name for the file '$package'"
|
|
[ -z "$packageversion" ] && error "install_packages(): failed to determine a package-version for the file '$package'"
|
|
|
|
#add this local package to the Artian-apps-local-packages repository
|
|
repo_add "$package" || return 1
|
|
using_local_packages=1 #remember that the Artian-apps-local-packages repository is being used
|
|
|
|
#replace package filename with name of package
|
|
packages="$(echo "$packages" | sed "s|$package|$packagename (>= $packageversion)|")"
|
|
|
|
#handle urls
|
|
elif [[ "$package" == *://* ]];then
|
|
|
|
#status "Handling url: $package"
|
|
|
|
local filename="/tmp/$(basename "$package")"
|
|
|
|
#add .deb extension if filename doesn't end with it.
|
|
if [ "${filename: -4}" != ".deb" ]; then
|
|
status "$filename is not ending with .deb, renaming it to '${filename}.deb'..."
|
|
local filename="${filename}.deb"
|
|
fi
|
|
|
|
wget -O "$filename" "$package" || return 1
|
|
|
|
#determine the package name from the filename
|
|
packagename="$(dpkg-deb -I "$filename" | grep "^ Package:" | awk '{print $2}')"
|
|
packageversion="$(dpkg-deb -I "$filename" | grep "^ Version:" | awk '{print $2}')"
|
|
[ -z "$packagename" ] && error "install_packages(): failed to determine a package-name for the file '$filename'"
|
|
[ -z "$packageversion" ] && error "install_packages(): failed to determine a package-version for the file '$filename'"
|
|
|
|
#add this local package to the Artian-apps-local-packages repository
|
|
repo_add "$filename" || return 1
|
|
using_local_packages=1 #remember that the Artian-apps-local-packages repository is being used
|
|
|
|
#replace package url with name of package
|
|
packages="$(echo "$packages" | sed "s|$package|$packagename (>= $packageversion)|")"
|
|
|
|
#expand regex (package-name contains *)
|
|
elif echo "$package" | grep -q '*' ;then
|
|
|
|
status "Expanding regex in '${package}'..."
|
|
|
|
list="$(apt-cache search "$package" | awk '{print $1}' | grep "$(echo "$package" | tr -d '*')")"
|
|
|
|
#replace package with expanded list
|
|
packages="$(echo "$packages" | grep -vF "$package")"$'\n'"$list"
|
|
fi
|
|
done
|
|
#now package list shouldn't contain any '*' characters, urls, local filepaths
|
|
if echo "$packages" | grep -q '*';then
|
|
error "install_packages(): failed to remove all regex from the package list:\n$packages"
|
|
elif [[ "$packages" == *://* ]];then
|
|
error "install_packages(): failed to remove all urls from the package list:\n$packages"
|
|
elif [[ "$packages" == */* ]];then
|
|
error "install_packages(): failed to remove all filenames from the package list:\n$packages"
|
|
fi #package list contains no '*' characters, urls, local filepaths
|
|
|
|
#change the $packages list from newline-delimited to comma-delimited
|
|
packages="$(tr '\n' ',' <<<"$packages")"
|
|
|
|
if [ "$using_local_packages" == 1 ];then
|
|
#Initialize the Artian-apps-local-packages repository
|
|
repo_refresh || return 1
|
|
#add this repository to flags to add to apt
|
|
apt_flags+=(-o 'Dir::Etc::SourceList=/tmp/Artian-apps-local-packages/source.list')
|
|
fi
|
|
|
|
status "Creating an empty apt-package to install the necessary apt packages..."
|
|
|
|
#to avoid issues with symbols and spaces in app names, we shasum the app name for use in apt
|
|
local package_name="$(app_to_pkgname "$app")"
|
|
|
|
echo "It will be named: $package_name"
|
|
|
|
#If this app's dummy deb is already installed, add its dependencies to the list.
|
|
#this allows install_packages() to be used multiple times in an app's script
|
|
if package_installed "$package_name" ;then
|
|
|
|
local existing_deps="$(package_dependencies "$package_name")"
|
|
status "The $package_name package is already installed. Inheriting its dependencies: $(echo "$existing_deps")"
|
|
packages+=",""$(echo "$existing_deps" | sed 's/, /,/g')"
|
|
fi
|
|
|
|
{ #create dummy apt package that depends on the packages this app requires
|
|
|
|
#this stores the comma-separated list of dependency packages. It removes duplicate entries.
|
|
local depends="$(echo "$packages" | sed 's/,|/ |/g' | sed 's/|,/| /g' | tr ',' '\n' | sort -u -t' ' -k1,1 | tr '\n' ',' | sed 's/^,//g' | sed 's/,$//g' | sed 's/,/, /g' ; echo)"
|
|
|
|
rm -rf ~/$package_name ~/$package_name.deb
|
|
mkdir -p ~/$package_name/DEBIAN
|
|
echo "Maintainer: Artian-Apps team
|
|
Name: $app
|
|
Description: Dummy package created by Artian-apps to install dependencies for the '$app' app
|
|
Version: 1.0
|
|
Architecture: all
|
|
Priority: optional
|
|
Section: custom
|
|
Depends: $depends
|
|
Package: $package_name" > ~/$package_name/DEBIAN/control
|
|
|
|
#fix error report "dpkg-deb: error: control directory has bad permissions 700 (must be >=0755 and <=0775)"
|
|
#The two zeros fix error report "dpkg-deb: error: control directory has bad permissions 2755 (must be >=0755 and <=0775)"
|
|
sudo chmod -R '00755' ~/$package_name
|
|
|
|
#display the finished "Depends: " line to the user
|
|
grep --color=never "^Depends: " ~/$package_name/DEBIAN/control
|
|
}
|
|
|
|
#Skip installing the dummy deb if it is already installed and has an identical control file
|
|
if package_installed "$package_name" && [ "$(package_info "$package_name" | sort | grep -v '^Status: ')" == "$(cat ~/$package_name/DEBIAN/control | sort)" ];then
|
|
echo "$package_name is already installed and no changes would be made. Skipping..."
|
|
else
|
|
|
|
#Before apt update, check if local repo still exists
|
|
if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/Artian-apps-local-packages/Packages ];then
|
|
error "User error: Uh-oh, the /tmp/Artian-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
|
|
fi
|
|
|
|
#run an apt update
|
|
apt_update "${apt_flags[@]}" || exit 1
|
|
|
|
#After apt update, check again if local repo still exists
|
|
if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/Artian-apps-local-packages/Packages ];then
|
|
error "User error: Uh-oh, the /tmp/Artian-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
|
|
fi
|
|
|
|
#Build .deb file for the dummy package
|
|
local output="$(dpkg-deb --build ~/$package_name 2>&1)"
|
|
if [ $? != 0 ] || [ ! -f ~/$package_name.deb ];then
|
|
echo ""
|
|
echo "$output"
|
|
error "install_packages(): failed to create dummy deb ${package_name}!"
|
|
fi
|
|
|
|
#install dummy deb
|
|
status "Installing the $package_name package..."
|
|
|
|
apt_lock_wait
|
|
local output="$(sudo -E apt install -fy --no-install-recommends --allow-downgrades "${apt_flags[@]}" ~/$package_name.deb 2>&1 | less_apt | tee /dev/stderr)"
|
|
status "Apt finished."
|
|
|
|
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
|
|
if [ ! -z "$errors" ];then
|
|
echo -e "\e[91mFailed to install the packages!\e[39m"
|
|
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
|
|
echo "$output"
|
|
|
|
#some error reports seem to indicate that package URLs aren't being properly downloaded. This output aims to solve the mystery.
|
|
if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/Artian-apps-local-packages/Packages ];then
|
|
echo "User error: Uh-oh, the /tmp/Artian-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
|
|
elif [ "$using_local_packages" == 1 ] && echo "$output" | grep 'but it is not installable' ;then
|
|
echo -e "\e[91mThe Artian-Apps Local Repository was being used, and a package seemed to not be available. Here's the Packages file:\e[39m"
|
|
cat /tmp/Artian-apps-local-packages/Packages
|
|
echo -e "Attempting apt --dry-run installation of the problematic package(s) for debugging purposes:\n"
|
|
grep "but it is not installable" <<<"$output" | awk '{print $4}' | uniq | xargs sudo -E apt install -fy --no-install-recommends --allow-downgrades --dry-run "${apt_flags[@]}"
|
|
fi
|
|
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
rm -f ~/$package_name.deb
|
|
rm -rf ~/$package_name
|
|
|
|
#delete the local repository if it was used
|
|
if [ "$using_local_packages" == 1 ];then
|
|
repo_rm
|
|
fi
|
|
|
|
status_green "Package installation complete."
|
|
}
|
|
|
|
purge_packages() { #Allow dependencies of the $app app to be autoremoved.
|
|
#the $app variable must contain something
|
|
[ -z "$app" ] && error 'purge_packages function can only be used by apps to install packages. (the $app variable was not set)'
|
|
|
|
status "Allowing packages required by the $app app to be uninstalled"
|
|
|
|
#to avoid issues with symbols and spaces in app names, we shasum the app name for use in apt
|
|
local package_name="$(app_to_pkgname "$app")"
|
|
|
|
#if dummy deb found/installed
|
|
if package_installed "$package_name" ;then
|
|
echo "These packages were: $(package_dependencies "$package_name")"
|
|
|
|
status "Purging the $package_name package..."
|
|
|
|
apt_lock_wait
|
|
local output="$(sudo -E apt purge -y "$package_name" --autoremove 2>&1 | less_apt | tee /dev/stderr)"
|
|
status "Apt finished."
|
|
|
|
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
|
|
if [ ! -z "$errors" ];then
|
|
echo -e "\e[91mFailed to uninstall the packages!\e[39m"
|
|
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
|
|
#run some apt error diagnosis
|
|
echo "$output"
|
|
exit 1
|
|
fi
|
|
|
|
elif [ -f "${DIRECTORY}/data/installed-packages/${app}" ];then
|
|
#legacy pkg-install implementation
|
|
warning "Using the old implementation - an installed-packages file instead of a dummy deb"
|
|
|
|
local packages="$(cat "${DIRECTORY}/data/installed-packages/${app}" | tr '\n' ' ' | sed 's/ / /g')"
|
|
|
|
#normal mode
|
|
local output="$(sudo -E apt purge -y $packages 2>&1)"
|
|
exitcode=$?
|
|
|
|
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
|
|
if [ $exitcode != 0 ] || [ ! -z "$errors" ];then
|
|
echo -e "\e[91mFailed to uninstall the packages!\e[39m"
|
|
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
|
|
#run some apt error diagnosis
|
|
echo "$output"
|
|
exit 1
|
|
fi
|
|
else
|
|
status "The $package_name package is not installed so there's nothing to do."
|
|
fi
|
|
|
|
status_green "All packages have been purged successfully."
|
|
rm -f "${DIRECTORY}/data/installed-packages/${app}"
|
|
}
|
|
|
|
get_icon_from_package() { #given a package-name, find all png files that it installed and print the one with the largest file-size.
|
|
[ -z "$1" ] && error "get_icon_from_package(): requires an apt package name"
|
|
|
|
#Find dependencies of the listed packages and scan them too
|
|
local package=''
|
|
local extra_packages=''
|
|
for package in "$@" ;do
|
|
#for every package specified, look for dependencies to that package that begin with the same name as the original package
|
|
#Example: given the 'shotwell' package, this will find the 'shotwell-common' package, as well as others
|
|
extra_packages+=" $(package_dependencies "$package" | sed 's/, \||/\n/g' | awk '{print $1}' | grep "^$package" | sort | uniq | tr '\n' ' ')"
|
|
done
|
|
dpkg-query -L "$@" $extra_packages 2>/dev/null | grep '\.png$\|\.svg$' | grep '/icons/\|/pixmaps/' | xargs wc -c | grep -v ' total' | sort -nr | head -n1 | sed 's/ / /g' | sed 's/^ //g' | tr ' ' '\n' | tail -n +2
|
|
}
|
|
|
|
ubuntu_ppa_installer() { #setup a PPA on an Ubuntu distro. Arguments: ppa_name
|
|
local ppa_name="$1"
|
|
[ -z "$1" ] && error "ubuntu_ppa_installer(): This function is used to add a ppa to a ubuntu based install but a required input argument was missing."
|
|
local ppa_grep="$ppa_name"
|
|
[[ "${ppa_name}" != */ ]] && local ppa_grep="${ppa_name}/"
|
|
local ppa_added=$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "$ppa_grep" | wc -l)
|
|
if [[ $ppa_added -eq "1" ]]; then
|
|
status "Skipping $ppa_name PPA, already added"
|
|
else
|
|
status "Adding $ppa_name PPA"
|
|
sudo add-apt-repository "ppa:$ppa_name" -y || exit 1
|
|
apt_update || exit 1
|
|
fi
|
|
# check if ppa .list filename does not exist under the current distro codename
|
|
# on a distro upgrade the .list filename is not updated and add-apt-repository can re-use the old filename
|
|
local ppa_dist="$__os_codename"
|
|
local standard_filename="/etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list"
|
|
if [[ ! -f "$standard_filename" ]] && ls /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-*.list 1> /dev/null; then
|
|
local original_filename="$(ls /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-*.list | head -1)"
|
|
# change the filename to match the current distro codename
|
|
sudo mv "$original_filename" "$standard_filename"
|
|
sudo rm -f "$original_filename".distUpgrade
|
|
sudo rm -f "$original_filename".save
|
|
fi
|
|
}
|
|
|
|
debian_ppa_installer() { #setup a PPA on a Debian distro. Arguments: ppa_name distribution key
|
|
local ppa_name="$1"
|
|
local ppa_dist="$2"
|
|
local key="$3"
|
|
[ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] && error "debian_ppa_installer(): This function is used to add a ppa to a debian based install but a required input argument was missing."
|
|
local ppa_grep="$ppa_name"
|
|
[[ "${ppa_name}" != */ ]] && local ppa_grep="${ppa_name}/ubuntu ${ppa_dist}"
|
|
local ppa_added=$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "$ppa_grep" | wc -l)
|
|
if [[ $ppa_added -eq "1" ]]; then
|
|
status "Skipping $ppa_name PPA, already added"
|
|
else
|
|
status "Adding $ppa_name PPA"
|
|
echo "deb https://ppa.launchpadcontent.net/${ppa_name}/ubuntu ${ppa_dist} main" | sudo tee /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list || error "Failed to add repository to sources.list!"
|
|
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys "$key"
|
|
if [ $? != 0 ];then
|
|
sudo rm -f /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list
|
|
error "Failed to sign the $ppa_name PPA!"
|
|
fi
|
|
apt_update || exit 1
|
|
fi
|
|
}
|
|
|
|
adoptium_installer() {
|
|
status "Adding Adoptium repository:"
|
|
|
|
echo "- public key -> keyring"
|
|
rm -f /tmp/adoptium-public-key /tmp/adoptium-archive-keyring.gpg
|
|
wget -O /tmp/adoptium-public-key https://adoptium.jfrog.io/artifactory/api/security/keypair/default-gpg-key/public
|
|
gpg --no-default-keyring --keyring /tmp/adoptium-keyring.gpg --import /tmp/adoptium-public-key
|
|
rm -f /tmp/adoptium-public-key
|
|
|
|
echo " - keyring -> GPG key"
|
|
gpg --no-default-keyring --keyring /tmp/adoptium-keyring.gpg --export --output /tmp/adoptium-archive-keyring.gpg
|
|
rm -f /tmp/adoptium-keyring.gpg
|
|
|
|
echo " - Moving GPG key to /usr/share/keyrings"
|
|
sudo mv -f /tmp/adoptium-archive-keyring.gpg /usr/share/keyrings
|
|
|
|
echo " - Creating /etc/apt/sources.list.d/adoptium.list"
|
|
case "$__os_codename" in
|
|
bionic | focal | jammy | buster | bullseye | bookworm)
|
|
echo "deb [signed-by=/usr/share/keyrings/adoptium-archive-keyring.gpg] https://adoptium.jfrog.io/artifactory/deb $__os_codename main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
|
|
;;
|
|
*)
|
|
# use bionic target name explicitly for any other OS as its the oldest LTS target adoptium continues to support
|
|
# all supported adoptium OSs use the same debs so the target specified does not actually matter
|
|
echo "deb [signed-by=/usr/share/keyrings/adoptium-archive-keyring.gpg] https://adoptium.jfrog.io/artifactory/deb bionic main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
|
|
;;
|
|
esac
|
|
apt_update
|
|
if [ $? != 0 ]; then
|
|
anything_installed_from_repo "https://adoptium.jfrog.io/artifactory/deb"
|
|
if [ $? != 0 ]; then
|
|
# nothing installed from repo, this check is to prevent removing repos which other Artian-apps scripts or the user have used successfully
|
|
# safe to remove
|
|
sudo rm -f /etc/apt/sources.list.d/adoptium.list /usr/share/keyrings/adoptium-archive-keyring.gpg
|
|
fi
|
|
error "Failed to perform apt update after adding Adoptium repository."
|
|
fi
|
|
}
|
|
|
|
pipx_install() {
|
|
# install pipx keeping in mind distro issues
|
|
# pipx <= 0.16.1 is compatible with 3.7 <= python3 < 3.9
|
|
# pipx >= 0.16.1 is compatible with python3 >= 3.7
|
|
# some distros lack pipx entirely
|
|
# some distros (raspbian bullseye specifically) have incompatible combinations of pipx (0.12.3) and python3 (3.9) versions, necessitating pipx to be installed/upgraded from pip
|
|
# pipx 1.0.0 is the first stable release and has some features that we would like to assume are available, install it from pip if the distro package is too old
|
|
if package_available pipx && package_is_new_enough pipx 1.0.0 ;then
|
|
install_packages pipx python3-venv || exit 1
|
|
elif package_is_new_enough python3 3.7 ; then
|
|
install_packages python3-venv || exit 1
|
|
sudo -H python3 -m pip install --upgrade pipx || exit 1
|
|
elif package_available python3.8 ;then
|
|
install_packages python3.8 python3.8-venv || exit 1
|
|
sudo -H python3.8 -m pip install --upgrade pipx || exit 1
|
|
else
|
|
error "pipx is not available so cannot install powerline-shell to python venv"
|
|
fi
|
|
sudo PIPX_HOME=/usr/local/pipx PIPX_BIN_DIR=/usr/local/bin pipx install "$@" || error "Failed to install $* with pipx"
|
|
}
|
|
|
|
pipx_uninstall() {
|
|
sudo PIPX_HOME=/usr/local/pipx PIPX_BIN_DIR=/usr/local/bin pipx uninstall "$@" || error "Failed to uninstall $* with pipx"
|
|
}
|
|
|
|
remove_deprecated_app() { # prompts a user to uninstall a deprecated Artian-apps application and then removes the application folder if it exists
|
|
local app="$1" # on app name
|
|
local removal_arch="$2" # 32 or 64 to only remove one architecture if specified
|
|
[ -z "$app" ] && error "remove_deprecated_app(): requires an Artian-apps app name"
|
|
local app_status="$(app_status "${app}")"
|
|
if [ ! -z "$removal_arch" ] && [ "$arch" == "$removal_arch" ] && [ -d "${DIRECTORY}/apps/$app" ] && [ "$app_status" == "installed" ]; then
|
|
local text="Artian-Apps has deprecated $app for ${removal_arch}-bit OSs which you currently have installed.
|
|
Would you like to uninstall it now or leave it installed? You will NOT be able to uninstall $app with Artian-apps later."
|
|
userinput_func "$text" "Uninstall now" "Leave installed"
|
|
elif [ ! -z "$removal_arch" ] && [ "$arch" != "$removal_arch" ]; then
|
|
rm -f "${DIRECTORY}/apps/$app/install-$removal_arch"
|
|
return 0
|
|
elif [ -z "$removal_arch" ] && [ -d "${DIRECTORY}/apps/$app" ] && [ "$app_status" == "installed" ]; then
|
|
local text="Artian-Apps has deprecated $app which you currently have installed.
|
|
Would you like to uninstall it now or leave it installed? You will NOT be able to uninstall $app with Artian-apps later."
|
|
userinput_func "$text" "Uninstall now" "Leave installed"
|
|
else
|
|
rm -rf "${DIRECTORY}/apps/$app"
|
|
return 0
|
|
fi
|
|
if [ "$output" == "Uninstall now" ]; then
|
|
"${DIRECTORY}/manage" uninstall "$app"
|
|
fi
|
|
rm -rf "${DIRECTORY}/apps/$app"
|
|
return 0
|
|
}
|
|
|
|
terminal_manage() { # wrapper for the original terminal_manage function to terminal_mange_multi
|
|
action="$1"
|
|
app="$2" #one app name
|
|
|
|
[ -z "$action" ] && error "terminal_manage(): Must specify an action: either 'install' or 'uninstall' or 'update' or 'refresh'"
|
|
|
|
terminal_manage_multi "$action $app"
|
|
}
|
|
|
|
terminal_manage_multi() { #function to install/uninstall/update/refresh multiple apps (or filelists) - uses a terminal and refreshes the app list
|
|
queue="$1" #one or multiple actions and app names (or filelists of the format 'filelist:path/to/file:path/to/another/file')
|
|
|
|
#To prevent multiple simultaneous manage instances, use the 'daemon' mode. This will create a queue of actions that are executed concurrently.
|
|
#The first daemon instance is the 'master' process. Subsequent processes will add the action to the queue and then exit.
|
|
#All output is generated by the 'master' daemon process. Subsequent processes shouldn't open a terminal, because the master one is already open.
|
|
if [ -f "${DIRECTORY}/data/manage-daemon/pid" ] && process_exists $(cat "${DIRECTORY}/data/manage-daemon/pid") ;then
|
|
#The 'master' daemon is already running. Avoid launching a second terminal.
|
|
"${DIRECTORY}/manage" daemon "$queue"
|
|
|
|
else
|
|
#in a terminal, first get the api functions, display the Artian-apps logo, run the manage script, and refresh the app list if the $pipe variable is set
|
|
"${DIRECTORY}/etc/terminal-run" '
|
|
DIRECTORY="'"$DIRECTORY"'"
|
|
export geometry2="'"$geometry2"'"
|
|
source "${DIRECTORY}/api"
|
|
generate_logo
|
|
|
|
refresh_list() { #Refresh the current list of apps in the event of a change
|
|
if [ ! -z "'"$pipe"'" ] && [ -p "'"$pipe"'" ];then
|
|
echo -e "\f" > "'"$pipe"'"
|
|
"${DIRECTORY}/preload" yad "'"$prefix"'" > "'"$pipe"'" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
"${DIRECTORY}/manage" daemon "'"$queue"'"
|
|
|
|
#refresh app list
|
|
refresh_list
|
|
|
|
for i in {30..1} ;do
|
|
echo -en "You can close this window now. Auto-closing in $i seconds.\e[0K\r"
|
|
sleep 1
|
|
done
|
|
' "Terminal Output"
|
|
|
|
# Check if terminal-run failed to launch. GUI users don't see any terminal output if it fails (since there is no terminal open) so we need to prompt them with a GUI window
|
|
if [ "$?" != 0 ]; then
|
|
echo -e "Unable to open a terminal.\nDebug output below.\n$(DEBUG=1 "${DIRECTORY}/etc/terminal-run" 2>&1)" | yad --center --window-icon="${DIRECTORY}/icons/logo.png" \
|
|
--width=700 --height=300 --text-info --title="Error occured when calling terminal-run" \
|
|
--image="${DIRECTORY}/icons/error.png" --image-on-top --fontname=12 \
|
|
--button='OK'
|
|
if echo "$queue" | grep -q "^update filelist:" ;then #list of files separated by :
|
|
updatable_apps='' updatable_files="$(echo "$queue" | grep "^update filelist:" | sed 's/^update filelist://g' | tr ':' '\n')" no_status=true update_now_cli
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
#end of apt functions
|
|
|
|
#flatpak functions
|
|
flatpak_install() { #install an app using flatpak
|
|
[ -z "$1" ] && error "flatpak_install(): This function is used to install a flatpak app, but nothing was specified."
|
|
|
|
#make sure flatpak is installed
|
|
if ! command -v flatpak >/dev/null ;then
|
|
error "flatpak_install(): Could not install $1 because flatpak is not installed!"
|
|
fi
|
|
|
|
if ! package_is_new_enough flatpak 1.14.4 ;then
|
|
case "$__os_codename" in
|
|
buster)
|
|
debian_ppa_installer "theofficialgman/flatpak-no-bwrap" "bionic" "0ACACB5D1E74E484"
|
|
apt_lock_wait
|
|
sudo apt --only-upgrade install flatpak -y | less_apt
|
|
;;
|
|
bullseye)
|
|
debian_ppa_installer "theofficialgman/flatpak-no-bwrap" "focal" "0ACACB5D1E74E484"
|
|
apt_lock_wait
|
|
sudo apt --only-upgrade install flatpak -y | less_apt
|
|
;;
|
|
bionic|focal|jammy)
|
|
ubuntu_ppa_installer "theofficialgman/flatpak-no-bwrap"
|
|
apt_lock_wait
|
|
sudo apt --only-upgrade install flatpak -y | less_apt
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
status -n "Flatpak: Adding flathub remote... "
|
|
#Add the flathub remote, first as root, if that fails then try installing as user, while removing unwanted output
|
|
( sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || error "Flatpak failed to add flathub remote!" ) | grep --line-buffered -v "Note that the directories
|
|
'/var/lib/flatpak/exports/share'
|
|
'$HOME/.local/share/flatpak/exports/share'
|
|
are not in the search path set by the XDG_DATA_DIRS environment variable, so
|
|
applications installed by Flatpak may not appear on your desktop until the
|
|
session is restarted."
|
|
#exit 1 if above code failed
|
|
if [ ${PIPESTATUS[0]} != 0 ];then
|
|
exit 1
|
|
fi
|
|
status_green Done
|
|
|
|
status -n "Flatpak: installing $1... "
|
|
#Install the specified app, first as root, if that fails then try installing as user, while removing unwanted output
|
|
( sudo flatpak install flathub "$1" -y || flatpak install flathub "$1" -y || error "Flatpak failed to install $1!" ) | grep --line-buffered -v "Note that the directories
|
|
|
|
'/var/lib/flatpak/exports/share'
|
|
'$HOME/.local/share/flatpak/exports/share'
|
|
are not in the search path set by the XDG_DATA_DIRS environment variable, so
|
|
applications installed by Flatpak may not appear on your desktop until the
|
|
session is restarted."
|
|
#exit 1 if above code failed
|
|
if [ ${PIPESTATUS[0]} != 0 ];then
|
|
exit 1
|
|
fi
|
|
status_green Done
|
|
|
|
#Artian-Apps tries to avoid unnecessary reboots at all cost. Flatpak places desktop launchers in /var/lib/flatpak/exports/share/applications, which is not searched by default.
|
|
#This path is added to $XDG_DATA_DIRS on the next reboot, but we don't want to wait for that!
|
|
#If there are files in /var/lib/flatpak/exports/share/applications, and XDG_DATA_DIRS is missing flatpak paths, then bind-mount to /usr/share/applications
|
|
if [[ "$XDG_DATA_DIRS" != */var/lib/flatpak/exports/share* ]] && [ ! -z "$(ls /var/lib/flatpak/exports/share/applications)" ] && [ -z "$(ls /usr/share/applications/flatpak-temporary)" ];then
|
|
sudo mkdir -p /usr/share/applications/flatpak-temporary
|
|
sudo mount --bind /var/lib/flatpak/exports/share/applications /usr/share/applications/flatpak-temporary
|
|
elif [[ "$XDG_DATA_DIRS" == */var/lib/flatpak/exports/share* ]] ;then
|
|
sudo rm -rf /usr/share/applications/flatpak-temporary
|
|
fi
|
|
#Additionally, PiOS Buster had a bug where XDG_DATA_DIRS was missing flatpak's entries due to PiOS mods. Artian-Apps fixes this with a runonce.
|
|
|
|
true
|
|
}
|
|
|
|
flatpak_uninstall() { #uninstall an app using flatpak
|
|
[ -z "$1" ] && error "flatpak_uninstall(): This function is used to uninstall a flatpak app, but nothing was specified."
|
|
|
|
#if flatpak is not installed, then skip everything with code 0.
|
|
if ! command -v flatpak >/dev/null ;then
|
|
return 0
|
|
fi
|
|
|
|
#Only try to remove flatpak app if it's installed.
|
|
if flatpak list | grep -qF "$1" ;then
|
|
sudo flatpak uninstall "$1" -y || flatpak uninstall "$1" -y || error "Flatpak failed to uninstall $1!"
|
|
fi
|
|
}
|
|
#end of flatpak functions
|
|
|
|
#app functions
|
|
list_apps() { # $1 can be: installed, uninstalled, corrupted, cpu_installable, hidden, visible, online, online_only, local, local_only
|
|
if [ -z "$1" ] || [ "$1" == local ];then
|
|
#list all apps
|
|
ls "${DIRECTORY}/apps"
|
|
|
|
elif [ "$1" == all ];then
|
|
#combined list of apps, both online and local. Removes duplicate apps from the list.
|
|
echo -e "$(list_apps local)\n$(list_apps online)" | sort | uniq
|
|
|
|
elif [ "$1" == installed ];then
|
|
#list apps | only show ( list of installed apps | remove match string | basename )
|
|
list_apps local | list_intersect "$(grep -rx 'installed' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
|
|
|
|
elif [ "$1" == corrupted ];then
|
|
#list apps |only show ( list of corrupted apps | remove match string | basename )
|
|
list_apps local | list_intersect "$(grep -rx 'corrupted' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
|
|
|
|
elif [ "$1" == disabled ];then
|
|
#list apps | only show ( list of disabled apps | remove match string | basename )
|
|
list_apps local | list_intersect "$(grep -rx 'disabled' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
|
|
|
|
elif [ "$1" == uninstalled ];then
|
|
#list apps that have a status file matching "uninstalled"
|
|
list_apps local | list_intersect "$(grep -rx 'uninstalled' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
|
|
#also list apps that don't have a status file
|
|
list_apps local | list_subtract "$(ls "${DIRECTORY}/data/status")"
|
|
|
|
elif [ "$1" == have_status ];then
|
|
#list apps that have a status file
|
|
list_apps local | list_intersect "$(ls "${DIRECTORY}/data/status")"
|
|
|
|
elif [ "$1" == missing_status ];then
|
|
#list apps that don't have a status file
|
|
list_apps local | list_subtract "$(ls "${DIRECTORY}/data/status")"
|
|
|
|
elif [ "$1" == cpu_installable ];then
|
|
#list apps that can be installed on the device's OS architecture (32-bit or 64-bit)
|
|
#find all apps that have install-XX script, install script, or a packages file
|
|
find "${DIRECTORY}/apps" -type f \( -name "install-$arch" -o -name "install" -o -name "packages" \) | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq
|
|
|
|
elif [ "$1" == package ];then
|
|
#list apps that have a "packages" file
|
|
find "${DIRECTORY}/apps" -type f -name "packages" | sed "s+/packages++g" | sed "s+${DIRECTORY}/apps/++g" | sort | uniq
|
|
|
|
elif [ "$1" == standard ];then
|
|
#list apps that have scripts
|
|
find "${DIRECTORY}/apps" -type f \( -name "install-32" -o -name "install-64" -o -name "install" -o -name "uninstall" \) | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq
|
|
|
|
elif [ "$1" == hidden ];then
|
|
#list apps that are hidden
|
|
read_category_files | grep '|hidden' | awk -F'|' '{print $1}'
|
|
|
|
elif [ "$1" == visible ];then
|
|
#list apps that are in any other category but 'hidden', and aren't disabled
|
|
read_category_files | grep -v '|hidden' | awk -F'|' '{print $1}' # | list_subtract "$(list_apps disabled)"
|
|
|
|
elif [ "$1" == online ];then
|
|
#list apps that exist on the online git repo
|
|
if [ -d "${DIRECTORY}/update/Artian-apps/apps" ];then
|
|
#if update folder exists, just use that
|
|
ls "${DIRECTORY}/update/Artian-apps/apps" | grep .
|
|
else
|
|
#if update folder doesn't exist, then parse github HTML to get a list of online apps. Horrible idea, but it works!
|
|
wget -qO- "${repo_url}/tree/master/apps" | grep 'title=".*" data-pjax=' -o | sed 's/title="//g' | sed 's/" data-pjax=//g'
|
|
fi
|
|
|
|
elif [ "$1" == online_only ];then
|
|
#list apps that exist only on the git repo, and not locally
|
|
list_apps online | list_subtract "$(list_apps local)"
|
|
|
|
elif [ "$1" == local_only ];then
|
|
#list apps that exist only locally, and not on the git repo
|
|
list_apps local | list_subtract "$(list_apps online)"
|
|
|
|
else
|
|
error "list_apps(): unrecognized filter '$1'!"
|
|
fi
|
|
}
|
|
|
|
list_intersect() { #Outputs only the apps that appear in both stdin and in $1
|
|
# for example, the following two inputs will be a match
|
|
# Audacity
|
|
# Audacity
|
|
# while these two will NOT be a match
|
|
# Multimedia/Audacity
|
|
# .*/Audacity
|
|
comm -12 - <(echo "$1" | sort)
|
|
}
|
|
|
|
list_intersect_partial() { #Outputs only the apps that appear in both stdin and in $1 even with a partial match
|
|
# for example, the following two inputs will be a match
|
|
# Multimedia/Audacity
|
|
# .*/Audacity
|
|
# change \n to \| | remove last "\|"
|
|
grep -x "$(echo "$1" | sed -z 's/\n/\\|/g' | sed -z 's/\\|$/\n/g')"
|
|
}
|
|
|
|
list_subtract() { #Outputs a list of apps from stdin, minus the ones that appear in $1
|
|
# for example, the following two inputs will be a match
|
|
# Audacity
|
|
# Audacity
|
|
# while these two will NOT be a match
|
|
# Multimedia/Audacity
|
|
# .*/Audacity
|
|
comm -23 - <(echo "$1" | sort)
|
|
}
|
|
|
|
list_subtract_partial() { #Outputs a list of apps from stdin, minus the ones that appear in $1 even with a partial match
|
|
# for example, the following two inputs will be a match
|
|
# Multimedia/Audacity
|
|
# .*/Audacity
|
|
# change \n to \| | remove last "\|"
|
|
grep -vx "$(echo "$1" | sed -z 's/\n/\\|/g' | sed -z 's/\\|$/\n/g')"
|
|
}
|
|
|
|
read_category_files() { #Generates a combined categories-list from several sources: category-overrides, global categories file, and unlisted apps. Format: "app|category"
|
|
|
|
#remove app category if app folder not found
|
|
local IFS=$'\n'
|
|
local app
|
|
for app in $(cat "${DIRECTORY}/data/category-overrides" 2>/dev/null); do
|
|
if ! [ -d "${DIRECTORY}/apps/$(echo "$app" | sed 's/|.*//')" ] &>/dev/null; then
|
|
sed -i "/$app/d" "${DIRECTORY}/data/category-overrides"
|
|
fi
|
|
done
|
|
|
|
# get device specific categories overrides (if they exist)
|
|
# obtain model and jetson_model
|
|
get_model &>/dev/null
|
|
version_id=$(grep 'VERSION_ID=' /etc/os-release | tr -cd '0123456789.')
|
|
unset device_override
|
|
if [[ "$model" != *"Raspberry Pi"* ]]; then
|
|
device_override="${DIRECTORY}/etc/category-overrides-non-raspberry"
|
|
fi
|
|
if [[ ! -z "$jetson_model" ]] && [[ "$version_id" == "18.04" ]]; then
|
|
device_override="${DIRECTORY}/etc/category-overrides-jetson-18.04"
|
|
elif [[ ! -z "$jetson_model" ]]; then
|
|
device_override="${DIRECTORY}/etc/category-overrides-jetson-generic"
|
|
fi
|
|
|
|
#list the user-overrides file and the device specific override and the global categories file |-----and all apps------------| filter out duplicates no '\n\n'
|
|
(cat "${DIRECTORY}/data/category-overrides" "$device_override" "${DIRECTORY}/etc/categories" 2>/dev/null ; echo ; list_apps local | sed 's/$/|/g') | awk -F'|' '!seen[$1]++' | grep .
|
|
}
|
|
|
|
app_prefix_category() { #lists all apps in a category with format "category/app", or if $1 is left blank, then list the full structure of all categories
|
|
|
|
#Subtract a type of app if enabled
|
|
local show_apps_setting="$(cat "${DIRECTORY}/data/settings/Show apps")"
|
|
local filter=()
|
|
if [ "$show_apps_setting" == 'standard' ];then
|
|
#if only showing standard apps, hide package apps
|
|
filter=(list_subtract_partial "$(list_apps package | sed 's+^+.*/+g')")
|
|
elif [ "$show_apps_setting" == 'packages' ];then
|
|
#if only showing package apps, hide standard apps
|
|
filter=(list_subtract_partial "$(list_apps standard | sed 's+^+.*/+g')")
|
|
else
|
|
#is the setting is "all" or missing, don't filter.
|
|
filter=(cat)
|
|
fi
|
|
|
|
#show special "Installed" category - don't filter it
|
|
if [ "$1" == "Installed" ]; then
|
|
list_apps installed | sed 's+^+Installed/+g'
|
|
#show special "Packages" category
|
|
elif [ "$1" == "Packages" ]; then
|
|
list_apps package | list_subtract "$(list_apps hidden)" | sed 's+^+Packages/+g'
|
|
#show special "All Apps" category
|
|
elif [ "$1" == "All Apps" ]; then
|
|
list_apps cpu_installable | list_subtract "$(list_apps hidden)" | sed 's+^+All Apps/+g' | "${filter[@]}"
|
|
# show all categories
|
|
elif [ -z "$1" ]; then
|
|
#show normal categories
|
|
read_category_files | grep . | awk -F'|' '{print $2"/"$1}' | sed 's+^/++g' | "${filter[@]}"
|
|
|
|
#show special "Installed" category - don't filter it
|
|
list_apps installed | sed 's+^+Installed/+g'
|
|
|
|
#show special "Packages" category
|
|
if [ "$show_apps_setting" != standard ];then
|
|
list_apps package | list_subtract "$(list_apps hidden)" | sed 's+^+Packages/+g'
|
|
fi
|
|
#show special "All Apps" category
|
|
list_apps cpu_installable | list_subtract "$(list_apps hidden)" | sed 's+^+All Apps/+g' | "${filter[@]}"
|
|
#show normal categories
|
|
else
|
|
read_category_files | grep . | awk -F'|' '{print $2"/"$1}' | grep "^$1/" | sed 's+^/++g' | "${filter[@]}"
|
|
fi
|
|
}
|
|
|
|
# bitly_link() { #Runs whenever an app is installed/uninstalled to tally the number of users for each app
|
|
# #This cannot possibly be used to identify you, or any information about you.
|
|
# #It simply "clicks" a bitly link - a shortened URL - so that the total number of clicks can be tallied to determine how popular a certain app is.
|
|
# app="$1"
|
|
# trigger="$2"
|
|
#
|
|
# [ -z "$app" ] && error "bitly_link(): requires an app argument"
|
|
# [ -z "$trigger" ] && error "bitly_link(): requires a trigger argument"
|
|
#
|
|
# #if the 'Enable Analytics' setting is enabled
|
|
# if [ "$(cat "${DIRECTORY}/data/settings/Enable analytics")" == 'Yes' ];then
|
|
# #determine the name of the link to "click"
|
|
# bitlylink="https://bit.ly/Artian-apps-$trigger-$(echo "$app" | tr -d ' ' | sed 's/[^a-zA-Z0-9]//g')"
|
|
# #click it
|
|
# curl -L --user-agent "Artian-Apps Raspberry Pi app store" "$bitlylink" &>/dev/null &
|
|
# fi
|
|
# }
|
|
|
|
bitly_link() { #compatibility function pointing to shlink_link (incase old manage script is running with new api)
|
|
shlink_link "$@"
|
|
}
|
|
|
|
shlink_link() { #Runs whenever an app is installed/uninstalled to tally the number of users for each app
|
|
#This cannot possibly be used to identify you, or any information about you.
|
|
#It simply "clicks" a shlink link - a shortened URL - so that the total number of clicks can be tallied to determine how popular a certain app is.
|
|
app="$1"
|
|
trigger="$2"
|
|
|
|
[ -z "$app" ] && error "shlink_link(): requires an app argument"
|
|
[ -z "$trigger" ] && error "shlink_link(): requires a trigger argument"
|
|
|
|
#if the 'Enable Analytics' setting is enabled
|
|
if [ "$(cat "${DIRECTORY}/data/settings/Enable analytics")" == 'Yes' ];then
|
|
#determine the name of the link to "click"
|
|
shlinklink="https://Artianhpc.ir -$trigger-$(echo "$app" | tr -d ' ' | sed 's/[^a-zA-Z0-9]//g')/track"
|
|
#click it
|
|
curl -s -X 'GET' "$shlinklink" -H 'accept: image/gif' -A "Artian-Apps a software manager for artian company" >/dev/null &
|
|
fi
|
|
}
|
|
|
|
usercount() { #Return number of users for specified app. $1 is app name. If empty, all are shown.
|
|
#If clicklist file missing or over a day old, download it.
|
|
if [ ! -f "${DIRECTORY}/data/clicklist" ] || [ ! -z "$(find "${DIRECTORY}/data/clicklist" -mtime +1 -print)" ]; then
|
|
wget 'https://git.fdc.abrish.ir/a.kamyar/PublicFiles/Artian-Apps/' -qO "${DIRECTORY}/data/clicklist" >/dev/null || return 1
|
|
fi
|
|
clicklist="$(cat "${DIRECTORY}/data/clicklist")"
|
|
|
|
[ -z "$clicklist" ] && error "usercount(): clicklist empty. Likely no internet connection"
|
|
|
|
if [ -z "$1" ];then
|
|
echo "$clicklist"
|
|
else
|
|
# $1 is app
|
|
echo "$clicklist" | grep "[0-9] $1"'$' | awk '{print $1}' | head -n1
|
|
fi
|
|
|
|
}
|
|
|
|
script_name() { #returns name of install script(s) for the $1 app. outputs: '', 'install-32', 'install-64', 'install', 'install-32 install-64'
|
|
[ -z "$1" ] && error 'script_name(): requires an argument'
|
|
|
|
#ensure $1 is valid app name
|
|
[ ! -d "${DIRECTORY}/apps/$1" ] && error "script_name: '$1' is an invalid app name.\n${DIRECTORY}/apps/$1 does not exist."
|
|
|
|
if [ -f "${DIRECTORY}/apps/$1/install-32" ] && [ ! -f "${DIRECTORY}/apps/$1/install-64" ];then
|
|
echo 'install-32'
|
|
elif [ -f "${DIRECTORY}/apps/$1/install-64" ] && [ ! -f "${DIRECTORY}/apps/$1/install-32" ];then
|
|
echo 'install-64'
|
|
elif [ -f "${DIRECTORY}/apps/$1/install-64" ] && [ -f "${DIRECTORY}/apps/$1/install-32" ];then
|
|
echo 'install-32 install-64'
|
|
elif [ -f "${DIRECTORY}/apps/$1/install" ];then
|
|
echo 'install'
|
|
else
|
|
true
|
|
#error "No install script found for the $app app! Please report this to Botspot."
|
|
fi
|
|
|
|
}
|
|
|
|
script_name_cpu() { #get script name to run based on detected CPU arch
|
|
[ -z "$1" ] && error 'script_name_cpu(): requires an argument.'
|
|
|
|
#ensure $1 is valid app name
|
|
if ! list_apps all | grep -q "$1" ;then
|
|
error "script_name_cpu: '$1' is an invalid app name."
|
|
fi
|
|
|
|
if [ -f "${DIRECTORY}/apps/$1/install-32" ] && [ $arch == 32 ];then
|
|
echo 'install-32'
|
|
elif [ -f "${DIRECTORY}/apps/$1/install-64" ] && [ $arch == 64 ];then
|
|
echo 'install-64'
|
|
elif [ -f "${DIRECTORY}/apps/$1/install" ];then
|
|
echo 'install'
|
|
elif [ -f "${DIRECTORY}/apps/$1/packages" ];then
|
|
echo 'packages'
|
|
else
|
|
true #app not compatible with current arch
|
|
fi
|
|
}
|
|
|
|
app_status() { #Gets the $1 app's current status. installed, uninstalled, corrupted, disabled
|
|
|
|
[ -z "$1" ] && error 'app_status(): requires an argument.'
|
|
#don't check if app exists, it may be a new app in an update
|
|
|
|
if [ -f "${DIRECTORY}/data/status/${1}" ];then
|
|
cat "${DIRECTORY}/data/status/${1}"
|
|
else
|
|
echo 'uninstalled' #if app status file doesn't exist, assume uninstalled
|
|
fi
|
|
}
|
|
|
|
app_type() { #there are 'standard' apps, and there are 'package' apps - an alias to install an apt package from the existing repositories.
|
|
#$1 is input app-name
|
|
local app="$1"
|
|
[ -z "$app" ] && error "app_type: no app specified!"
|
|
|
|
if [ -f "${DIRECTORY}/apps/${app}/packages" ];then
|
|
echo package
|
|
elif [ -f "${DIRECTORY}/apps/${app}/uninstall" ] || [ -f "${DIRECTORY}/apps/${app}/install" ] || [ -f "${DIRECTORY}/apps/${app}/install-32" ] || [ -f "${DIRECTORY}/apps/${app}/install-64" ];then
|
|
echo standard
|
|
else
|
|
return 1
|
|
fi
|
|
#if neither conditional above evaluated to true, no output will be returned and the function exits with code 1
|
|
}
|
|
|
|
will_reinstall() { #return 0 if $1 app will be reinstalled during an update, otherwise return 1.
|
|
local app="$1"
|
|
[ -z "$app" ] && error 'will_reinstall(): requires an argument'
|
|
|
|
#exit immediately if app is not installed.
|
|
if [ "$(app_status "$app")" != 'installed' ];then
|
|
return 1
|
|
fi
|
|
|
|
#detect which installation script exists - both for local install and for update directory
|
|
local local_scriptname="$(script_name_cpu "$app")"
|
|
|
|
local new_scriptname="$(DIRECTORY="${DIRECTORY}/update/Artian-apps" script_name_cpu "$app")"
|
|
|
|
if [ ! -z "$new_scriptname" ] && [ "$local_scriptname" == packages ] && [ "$new_scriptname" != packages ];then
|
|
#migration from package-app to script-app. Reinstall.
|
|
return 0
|
|
elif [ "$local_scriptname" != packages ] && [ "$new_scriptname" == packages ];then
|
|
#migration from script-app to package-app. Reinstall.
|
|
return 0
|
|
elif [ ! -z "$new_scriptname" ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/${new_scriptname}" "${DIRECTORY}/apps/${app}/${local_scriptname}" ;then
|
|
#if install script exists in update folder, and if install script was changed, and if installed already
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
app_search() { #search all apps for $1, with filenames in $2 (space-separated and optional)
|
|
local query="$1"
|
|
[ -z "$query" ] && error "app_search(): requires a search query."
|
|
|
|
#you can change which app-files are searched. Default: description, website, credits
|
|
if [ ! -z "${2+x}" ];then
|
|
#second argument can be set, but left blank
|
|
local search_files="$(echo "$2" | sed 's/^ //g' | sed 's/ $//g' | sed 's/ / /g')"
|
|
else
|
|
#if no second argument provided
|
|
local search_files="description website credits"
|
|
fi
|
|
|
|
#generate syntax for the find command. Example output: "-name description -o -name website -o -name credits"
|
|
local find_syntax="$(echo "$search_files" | sed 's/ / -o -name /g')"
|
|
echo "search pattern: $find_syntax" 1>&2
|
|
|
|
#search app files
|
|
if [ -z "$search_files" ];then
|
|
local results=""
|
|
else
|
|
local results="$(find "${DIRECTORY}/apps" \( -name $find_syntax \) -exec grep -m1 -Fi "$query" {} + | awk -F: '{print $1}' | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq)"
|
|
fi
|
|
#hide incompatible and hidden/disabled apps
|
|
results="$(echo "$results" | list_subtract "$(list_apps hidden)" | list_intersect "$(list_apps cpu_installable)")"
|
|
|
|
if [ -z "$app_list" ];then
|
|
local app_list="$(list_apps cpu_installable | list_subtract "$(list_apps hidden)")"
|
|
fi
|
|
|
|
#search app names - first prioritize results starting with query, then show results containing query, then show other pre-existing results.
|
|
results="$(grep -i "^$query" <<<"$app_list" | sort)
|
|
$(grep -i "$query" <<<"$app_list" | sort)
|
|
$results"
|
|
|
|
#remove duplicate entries
|
|
echo "$results" | awk '!seen[$0]++'
|
|
|
|
}
|
|
|
|
app_search_gui() { #graphical interface to search for apps
|
|
|
|
#pre-populate search field with last search, if available
|
|
if [ -f "${DIRECTORY}/data/last-search" ];then
|
|
local last_search="$(cat "${DIRECTORY}/data/last-search")"
|
|
else
|
|
local last_search=''
|
|
fi
|
|
|
|
local output=''
|
|
output="$(yad "${yadflags[@]}" --title=Search --width=310 \
|
|
--text="Search for apps."$'\n'"Not case-sensitive." \
|
|
--form --field='' "$last_search" \
|
|
--field='Search description':CHK 'TRUE' \
|
|
--field='Search website':CHK 'TRUE' \
|
|
--field='Search credits':CHK 'TRUE' \
|
|
--field='Search scripts':CHK 'FALSE')" || return 0
|
|
local query="$(echo "$output" | sed -n 1p)"
|
|
|
|
#exit now if search query is empty
|
|
if [ -z "$query" ];then
|
|
return 0
|
|
fi
|
|
#save query for next time in last-search data file
|
|
echo "$query" > "${DIRECTORY}/data/last-search"
|
|
|
|
#skip file-based search if user searched for exact app name
|
|
local app_list="$(list_apps cpu_installable | list_subtract "$(list_apps hidden)")"
|
|
local results="$(grep -xFi "$query" <<<"$app_list")"
|
|
if [ ! -z "$results" ];then
|
|
echo "$results"
|
|
return 0
|
|
fi
|
|
|
|
local search_files='description website credits scripts'
|
|
search_files=''
|
|
echo "$output" | sed -n 2p | grep -q 'TRUE' && search_files+='description '
|
|
echo "$output" | sed -n 3p | grep -q 'TRUE' && search_files+='website '
|
|
echo "$output" | sed -n 4p | grep -q 'TRUE' && search_files+='credits '
|
|
echo "$output" | sed -n 5p | grep -q 'TRUE' && search_files+='install install-32 install-64 uninstall '
|
|
|
|
#echo -e "query: $query\nsearch_files: $search_files"
|
|
|
|
results="$(app_search "$query" "$search_files")"
|
|
|
|
if [ ! -z "$results" ] && [ "$(echo "$results" | wc -l)" == 1 ];then
|
|
#if only one result, don't ask user to select it
|
|
echo "$results" #return result to outside world
|
|
|
|
elif [ ! -z "$results" ];then #display apps and their categories in a list for user to select
|
|
|
|
local app="$(show_app() {
|
|
echo "$2"
|
|
}
|
|
export -f show_app
|
|
|
|
categories="$(read_category_files)"
|
|
|
|
IFS=$'\n'
|
|
for app in $results ;do
|
|
local category="$(grep '^'"$app"'|' <<<"$categories" | awk -F'|' '{print $2}')"
|
|
|
|
echo "${DIRECTORY}/apps/${app}/icon-24.png
|
|
$app
|
|
$( (cat "${DIRECTORY}/apps/${app}/description" || echo "Description unavailable") | head -n1)
|
|
<small>in</small>
|
|
${DIRECTORY}/icons/categories/${category##*/}.png
|
|
${category}"
|
|
done | \
|
|
yad "${yadflags[@]}" --title="Results for "\""$query"\""" --width=310 --height=250 \
|
|
--list --no-headers --column=app:IMG --column=appname --column=tooltip:HD --column=in --column=category:IMG --column=categoryname \
|
|
--tooltip-column=3 --no-buttons \
|
|
--select-action="bash -c "\""show_app %s; kill "\$"YAD_PID"\""")"
|
|
|
|
echo "$app" #return result to outside world
|
|
|
|
elif list_apps | grep -qi "^$query" ;then
|
|
#no compatible apps found, but an incompatible app was found that matches the name
|
|
yad "${yadflags[@]}" --title=Results --width=310 \
|
|
--text=""\""<b>$(list_apps | grep -i -m1 "^$query")</b>"\"" is not compatible with your ${__os_desc} ${arch}-bit OS." \
|
|
--button=OK:0
|
|
return 0
|
|
|
|
else #no results found
|
|
yad "${yadflags[@]}" --title=Results --width=310 \
|
|
--text="No results found for "\""<b>$query</b>"\""." \
|
|
--button=OK:0
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
userinput_func() { # userinput function to display yad/cli prompts to the user
|
|
[ -z "$1" ] && error "userinput_func(): requires a description"
|
|
[ -z "$2" ] && error "userinput_func(): requires at least one output selection option"
|
|
local text_lines=$(echo -e "$1" | wc -l)
|
|
# there is no good universal way to calculate the required height of the window
|
|
# the users theme, default text size, and window scaling all affect it
|
|
# the idea is the height should be a function of the number of lines of input text and the number of list options
|
|
local height_list=$(echo $(( text_lines * 17 + $(( ${#@} - 1 )) * 29 + 65 )))
|
|
local commonflags=(--fixed --no-escape --undecorated --center --borders=20)
|
|
if [ "${#@}" == "2" ];then
|
|
yad "${yadflags[@]}" "${commonflags[@]}" \
|
|
--image "dialog-information" \
|
|
--text="$1" \
|
|
--button="$2":0
|
|
output="$2"
|
|
elif [ "${#@}" == "3" ];then
|
|
yad "${yadflags[@]}" "${commonflags[@]}" \
|
|
--image "dialog-question" \
|
|
--text="$1" \
|
|
--button="$2":0 \
|
|
--button="$3":1
|
|
if [ $? -ne 0 ]; then
|
|
output="$3"
|
|
else
|
|
output="$2"
|
|
fi
|
|
else
|
|
unset uniq_selection
|
|
for string in "${@:2}"; do
|
|
local uniq_selection+=(FALSE "$string")
|
|
done
|
|
local uniq_selection[0]=TRUE
|
|
output=$(yad "${yadflags[@]}" "${commonflags[@]}" \
|
|
--height=$height_list\
|
|
--text "$1" \
|
|
--list \
|
|
--no-headers \
|
|
--radiolist \
|
|
--center \
|
|
--column "" \
|
|
--column "Selection" \
|
|
--print-column=2 \
|
|
--separator='' \
|
|
--button="OK":0 \
|
|
"${uniq_selection[@]}")
|
|
fi
|
|
}
|
|
|
|
generate_app_icons() { #This converts the given $1 image into icon-24.png and icon-64.png files for the $2 app
|
|
icon="$1"
|
|
app="$2"
|
|
|
|
[ -z "$icon" ] && error "create_app_icons(): icon field empty!"
|
|
[ -z "$app" ] && error "create_app_icons(): app field empty!"
|
|
|
|
#ensure imagemagick is installed
|
|
if ! command -v convert >/dev/null ;then
|
|
yad "${yadflags[@]}" --text="To resize the images, imagemagick must be installed."$'\n'"Install now?" \
|
|
--text-align=center --title='Quick question' \
|
|
--button=No!"${DIRECTORY}/icons/exit.png":1 --button=Yes!"${DIRECTORY}/icons/check.png":0
|
|
button=$?
|
|
if [ $button == 0 ];then
|
|
sudo apt install -y --no-install-recommends imagemagick || icon=''
|
|
else
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
#scale it to 24x24
|
|
convert "$icon" -resize 24x24 "${DIRECTORY}/apps/${app}/icon-24.png"
|
|
|
|
#scale it to 64x64
|
|
convert "$icon" -resize 64x64 "${DIRECTORY}/apps/${app}/icon-64.png"
|
|
|
|
}
|
|
|
|
refresh_pkgapp_status() { #for the specified package-app, if dpkg thinks it's installed, then mark it as installed.
|
|
local app="$1"
|
|
[ -z "$app" ] && error "refresh_pkgapp_status(): no app specified!"
|
|
|
|
#From the list of packages for the $app app, get the first one
|
|
local package="$(cat "${DIRECTORY}/apps/${app}/packages" | awk '{print $1}')"
|
|
[ -z "$package" ] && error "refresh_pkgapp_status(): The $app app does not have a valid packages file."
|
|
|
|
#if that package is installed
|
|
if package_installed "$package" ;then
|
|
#mark this app as installed
|
|
if [ "$(app_status "$app")" != 'installed' ];then
|
|
echo "Marking $app as installed"
|
|
echo 'installed' > "${DIRECTORY}/data/status/${app}"
|
|
shlink_link "$app" install &
|
|
fi
|
|
#if that package is not installed, check if it even exists on the repositories
|
|
elif package_available "$package" ;then
|
|
#the package for the $app app is not installed but it is available, so mark this app as uninstalled
|
|
if [ "$(app_status "$app")" != 'uninstalled' ];then
|
|
echo "Marking $app as uninstalled"
|
|
rm -f "${DIRECTORY}/data/status/${app}"
|
|
shlink_link "$app" uninstall &
|
|
fi
|
|
else
|
|
#this app is trying to install a package that's not on the repository. Hide the app.
|
|
echo "Marking $app as hidden"
|
|
"${DIRECTORY}/etc/categoryedit" "$app" 'hidden' >/dev/null
|
|
fi
|
|
}
|
|
|
|
refresh_all_pkgapp_status() { #for every package-app, if dpkg thinks it's installed, then mark it as installed.
|
|
#repeat for every package-type app
|
|
local IFS=$'\n'
|
|
local PIDS=()
|
|
for app in $(list_apps package) ;do
|
|
refresh_pkgapp_status "$app" &
|
|
PIDS+=("$!")
|
|
done
|
|
wait "${PIDS[@]}" #wait for all subprocesses to complete
|
|
}
|
|
|
|
refresh_app_list() { #Force-regenerate the app list
|
|
local guimode="$(cat "${DIRECTORY}/data/settings/App List Style")"
|
|
|
|
#delete preload directory, then re-generate it
|
|
rm -rf "${DIRECTORY}/data/preload"
|
|
"${DIRECTORY}/etc/preload-daemon" "$guimode" once &>/dev/null
|
|
}
|
|
|
|
list_apps_missing_dummy_debs() { #List any installed apps that have had their dummy deb uninstalled more recently than the app was installed
|
|
local IFS=$'\n'
|
|
local app
|
|
local dummy_deb_status="$(grep ' installed Artian-apps-\| not-installed Artian-apps-' /var/log/dpkg.log | tac | awk -F' ' '!seen[$5]++' | sed 's/:all$//g')"
|
|
#example line value: 2023-03-20 13:13:54 status installed Artian-apps-6f59c0e1:all 1.0
|
|
|
|
for app in $(list_apps installed | list_intersect "$(list_apps standard)") ;do
|
|
#For every installed app, make sure the dummy deb, if it has ever been installed, is installed
|
|
local pkgname="$(app_to_pkgname "$app")"
|
|
local match="$(grep -m1 "$pkgname" <<<"$dummy_deb_status")"
|
|
|
|
if [ -z "$match" ];then
|
|
#echo "no dummy deb information associated with $app"
|
|
true
|
|
elif [ "$(echo "$match" | awk '{print $4}')" == not-installed ];then
|
|
#echo "$app has its $pkgname package uninstalled!"
|
|
|
|
#if the dummy deb was removed after app was installed, then there is a problem
|
|
if [ "$(date -d '2023-03-27 22:23:58' +'%s')" -gt "$(date -r "${DIRECTORY}/data/status/${app}" +'%s')" ];then
|
|
#echo "the dummy deb for $app was removed after $app was installed!"
|
|
echo "$app"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
#end of app functions
|
|
|
|
#logfile functions below
|
|
get_logfile() { #find the most recent logfile for the $1 app
|
|
local app="$1"
|
|
[ -z "$app" ] && error "get_logfile(): no app specified!"
|
|
ls -dt "${DIRECTORY}/logs"/* | grep -v 'success' | grep '\-'"${app}"'\.log' -m 1
|
|
}
|
|
|
|
log_diagnose() { #Given a logfile, explain errors to user, suggest fixes, and categorize issue with error_type variable
|
|
#errors="$(cat /dev/stdin)"
|
|
local errors="$(cat "$1")"
|
|
# any attempt to write to the logfile will instead write to /dev/null if allowwrite argument is not passed
|
|
if [ "$2" == "allowwrite" ]; then
|
|
local logfile="$1"
|
|
else
|
|
local logfile="/dev/null"
|
|
fi
|
|
|
|
#store the user-friendly explanations for each type of error
|
|
local error_caption=()
|
|
#store the general type of error
|
|
local error_type=''
|
|
|
|
#------------------------------------------
|
|
#repo issues below
|
|
#------------------------------------------
|
|
#check for 'E: The repository'
|
|
if grep -qF 'E: The repository' <<<"$errors" || grep -qF 'sources.list entry misspelt' <<<"$errors" || grep -qF 'component misspelt in' <<<"$errors" ;then
|
|
error_caption+=("APT reported a faulty repository, and you must fix it before Artian-Apps will work.
|
|
|
|
To delete the repository:
|
|
Remove the relevant line from /etc/apt/sources.list file or delete one file in
|
|
the /etc/apt/sources.list.d folder.
|
|
|
|
sources.list requires root permissions to edit: sudo mousepad /path/to/file")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for 'NO_PUBKEY' or ' is no longer signed.'
|
|
if grep -qF 'NO_PUBKEY' <<<"$errors" || grep -qF ' is no longer signed.' <<<"$errors" ;then
|
|
error_caption+=("APT reported an unsigned repository. This has to be solved before APT or Artian-Apps, will work.
|
|
|
|
If you're not sure what to do, you can try to fix the problem by running this command in a terminal:
|
|
sudo apt update 2>&1 | sed -ne 's/.*NO_PUBKEY //p' | while read key; do if ! [[ ${keys[*]} =~ "\""$key"\"" ]]; then sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "\""$key"\""; keys+=("\""$key"\""); fi; done")
|
|
error_type="system"
|
|
fi
|
|
|
|
# check for 'Could not resolve' or 'Failed to fetch'
|
|
if grep -q 'Could not resolve\|Failed to fetch\|Temporary failure resolving\|Network is unreachable\|Internal Server Error\|404 .*Not Found' <<<"$errors" ;then
|
|
error_caption+=("APT reported an unresolvable repository.
|
|
|
|
Check your Internet connection and try again.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
#check for 'is configured multiple times in'
|
|
if grep -qF 'is configured multiple times in' <<<"$errors" ;then
|
|
error_caption+=("APT reported a double-configured repository, and you must fix it to fix Artian-Apps.
|
|
|
|
To delete the repository:
|
|
Remove the relevant line from /etc/apt/sources.list file or delete the file in
|
|
the /etc/apt/sources.list.d folder.
|
|
|
|
sources.list requires root permissions to edit: sudo mousepad /path/to/file")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for "W: Conflicting distribution: "
|
|
if grep -qF "W: Conflicting distribution: " <<<"$errors" ;then
|
|
error_caption+=("APT reported a conflicting repository.
|
|
|
|
Read the installation errors, then look through /etc/apt/sources.list and /etc/apt/sources.list.d, making changes as necessary.
|
|
|
|
Perhaps doing a Google search for the exact error you received would help.")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for "Release file for <repo-url> is not valid yet"
|
|
if grep -q "Release file for .* is not valid yet" <<<"$errors" ;then
|
|
error_caption+=("APT reported a repository whose release file becomes valid in the future.
|
|
|
|
Review the errors to see how long you need to wait.")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for "Release file for <repo-url> is expired"
|
|
if grep -q "Release file for .* is expired" <<<"$errors" ;then
|
|
error_caption+=("APT reported a repository whose release file was invalidated in the past.
|
|
Please check that your system clock is set correctly, and if it is, check if the repository is kept updated or if its developers abandoned it.
|
|
|
|
If you think think you shouldn't see this error, you can try refreshing APT with these commands:
|
|
sudo rm -rf /var/lib/apt
|
|
sudo apt update")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for typo in sources.list and list.d
|
|
if grep -q "The list of sources could not be read\.\|Did not understand pin type\|E: Malformed entry .* in list file" <<<"$errors";then
|
|
error_caption+=("APT reported a typo in the sources.list file.
|
|
|
|
You must look around in /etc/apt/sources.list and /etc/apt/sources.list.d and fix the typo.")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for "E: The package cache file is corrupted"
|
|
if grep -q "E: The package cache file is corrupted\|The package lists or status file could not be parsed or opened." <<<"$errors" ;then
|
|
error_caption+=("APT found something wrong with a package list file.
|
|
|
|
Perhaps this link would help: https://askubuntu.com/questions/939345/the-package-cache-file-is-corrupted-error")
|
|
error_type="system"
|
|
fi
|
|
|
|
#check for broken Artian-apps-local-packages symlink
|
|
if grep -q "E: Could not open file /var/lib/apt/lists/_tmp_Artian-apps-local-packages_._Packages" <<<"$errors" ;then
|
|
error_caption+=("APT reported the Artian-apps-local-packages list as missing.
|
|
|
|
The Artian-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?")
|
|
# no error_type since we want the user to be able to upload logs for this issue while still showing the custom caption
|
|
fi
|
|
|
|
#------------------------------------------
|
|
#repo issues above, apt/dpkg issues below
|
|
#------------------------------------------
|
|
|
|
#check for "--fix-broken"
|
|
if grep -qF "\-\-fix\-broken" <<<"$errors" || grep -qF "needs to be reinstalled" <<<"$errors" ;then
|
|
error_caption+=("APT reported a broken package.
|
|
|
|
Please run this command: sudo apt --fix-broken install")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "dpkg --configure -a" <<<"$errors" ;then
|
|
error_caption+=("Before dpkg, apt, or Artian-Apps will work, dpkg needs to repair your system.
|
|
|
|
Please run this command: sudo dpkg --configure -a")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "package is in a very bad inconsistent state;" <<<"$errors" ;then
|
|
error_caption+=("Something is wrong with another package on your system.
|
|
|
|
Refer to this information while troubleshooting: https://askubuntu.com/questions/148715")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "dpkg: error: fgets gave an empty string from" <<<"$errors" ;then
|
|
error_caption+=("Something strange is going on with your system and dpkg won't work.
|
|
|
|
Perhaps this link will help: https://askubuntu.com/questions/1293709/weird-error-when-trying-to-install-packages-with-apt")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "Command line option --allow-releaseinfo-change is not understood" <<<"$errors" ;then
|
|
error_caption+=("The Debian Project recently upgraded from Buster to version Bullseye. As a result, all Raspberry Pi OS Buster users will receive APT errors saying the repositories changed from 'stable' to 'oldstable'.
|
|
|
|
This error broke Artian-apps. To fix it, the Artian-Apps developers added something to the 'sudo apt update' command: --allow-releaseinfo-change.
|
|
This flag allows the repository migration to succeed, thereby allowing Artian-Apps to work again.
|
|
|
|
Unfortunately for you, your operating system is too old for apt to understand this flag we added. Please upgrade your operating system for a better experience. Raspbian Stretch is unsupported and many apps will not install.
|
|
|
|
Please flash your SD card with the latest release of Raspberry Pi OS: https://www.raspberrypi.org/software")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "lzma error: compressed data is corrupt" <<<"$errors" ;then
|
|
error_caption+=("A package failed to install because it appears corrupted. (buggy download?)
|
|
|
|
Try installing the same app again and if the problem persists please reach out to the Artian-Apps developers.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -qF "E: Could not get lock" <<<"$errors" ;then
|
|
error_caption+=("Some other apt-get/dpkg process is running. Wait for that one to finish, then try again.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "dpkg: error: cannot scan updates directory '/var/lib/dpkg/updates/': No such file or directory" <<<"$errors" ;then
|
|
error_caption+=("What did you do to your system? The "\""/var/lib/dpkg/updates"\"" folder is missing.
|
|
|
|
You can try creating the folder with this command:
|
|
sudo mkdir -p /var/lib/dpkg/updates")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "E: Repository .* changed its 'Suite' value" <<<"$errors" ;then
|
|
error_caption+=("One or more APT repositories on your system have changed Suite values. Usually this occurs when a new version of Debian is released every two years.
|
|
|
|
Artian-Apps should work around this error, but somehow it did not.
|
|
Please run this command in a terminal: sudo apt update --allow-releaseinfo-change")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "E: Failed to fetch .* File has unexpected size .* Mirror sync in progress\?" <<<"$errors" ;then
|
|
error_caption+=("APT encountered a repository with a file that is of incorrect size. This can be caused by a periodic mirror sync, or maybe the repository is faulty.
|
|
|
|
In any case, Artian-Apps cannot work until you solve this issue. Try disabling any 3rd-party APT repos first, and if that doesn't work then ask for help.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "The following packages have unmet dependencies:" <<<"$errors" ;then
|
|
# we want these errors to come to Artian-apps logs if the user sends them, so don't add an error type
|
|
# add additional output for the packages that have unmet dependencies
|
|
|
|
echo -e "Additional log diagnosis for developers below:\n" >> "$logfile"
|
|
grep -E "^ .* : Depends:" <<<"$errors" | awk '{print $1, $4}' | tr " " "\n" | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt-cache show >> "$logfile"
|
|
grep -E "^ .* : Depends:" <<<"$errors" | awk '{print $1, $4}' | tr " " "\n" | sed 's/:armhf\|:arm64\|:all//g' | sort -u | xargs -r apt list -a >> "$logfile"
|
|
grep -E "^ .* : Depends:" <<<"$errors" | awk '{print $4}' | tr " " "\n" | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt install -fy --no-install-recommends --allow-downgrades --dry-run >> "$logfile"
|
|
grep -E "^ +Depends:" <<<"$errors" | awk '{print $2}' | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt-cache show >> "$logfile"
|
|
grep -E "^ +Depends:" <<<"$errors" | awk '{print $2}' | sed 's/:armhf\|:arm64\|:all//g' | sort -u | xargs -r apt list -a >> "$logfile"
|
|
grep -E "^ +Depends:" <<<"$errors" | awk '{print $2}' | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt install -fy --no-install-recommends --allow-downgrades --dry-run >> "$logfile"
|
|
grep -E "^Depends:" <<<"$errors" | cut -d' ' -f2- | sed "s/, /\n/g" | sed "s/:any//g" | sed 's/([^)]*)//g;s/ / /g' | xargs -r apt-cache show >> "$logfile"
|
|
grep -E "^Depends:" <<<"$errors" | cut -d' ' -f2- | sed "s/, /\n/g" | sed 's/:armhf\|:arm64\|:all//g' | sed 's/([^)]*)//g;s/ / /g' | sort -u | xargs -r apt list -a >> "$logfile"
|
|
grep -E "^Depends:" <<<"$errors" | cut -d' ' -f2- | sed "s/, /\n/g" | sed "s/:any//g" | sed 's/([^)]*)//g;s/ / /g' | xargs -r apt install -fy --no-install-recommends --allow-downgrades --dry-run >> "$logfile"
|
|
fi
|
|
|
|
if grep -q "trying to overwrite shared .*, which is different from other instances of package" <<<"$errors" ;then
|
|
echo -e "Additional log diagnosis for developers below:\n" >> "$logfile"
|
|
grep "trying to overwrite shared .*, which is different from other instances of package" <<<"$errors" | awk '{print $NF}' | sort -u | awk '/:armhf/{print; gsub(/:armhf/, ":arm64")}1' | xargs -r apt-cache show >> "$logfile"
|
|
grep "trying to overwrite shared .*, which is different from other instances of package" <<<"$errors" | awk '{print $NF}' | sed 's/:armhf\|:arm64\|:all//g' | sort -u | xargs -r apt list -a >> "$logfile"
|
|
fi
|
|
|
|
#if RPi OS, check for /etc/apt/sources.list.d/raspi.list
|
|
if [ -f /etc/rpi-issue ] && [ ! -f /etc/apt/sources.list.d/raspi.list ] ;then
|
|
error_caption+=("Packages failed to install because you seem to have deleted an important repository file in /etc/apt/sources.list.d
|
|
|
|
This error-dialog appeared because /etc/apt/sources.list.d/raspi.list is missing, but you may have deleted other files as well.
|
|
The raspi.list file should contain this:
|
|
|
|
deb http://archive.raspberrypi.org/debian/ $(get_codename) main
|
|
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
|
|
#deb-src http://archive.raspberrypi.org/debian/ $(get_codename) main")
|
|
error_type="system"
|
|
fi
|
|
|
|
#if /etc/apt/sources.list and /etc/apt/sources.list.d/ubuntu.sources (default sources in Ubuntu 23.10) missing, display error message
|
|
if [ ! -f /etc/apt/sources.list ] && [ ! -f /etc/apt/sources.list.d/ubuntu.sources ] ;then
|
|
if [ -f /etc/rpi-issue ] && [ "$arch" == 32 ];then
|
|
error_caption+=("Packages failed to install because you deleted an important repository file: /etc/apt/sources.list
|
|
|
|
You appear to be using Raspberry Pi OS 32-bit, so the sources.list file should contain this:
|
|
deb http://raspbian.raspberrypi.org/raspbian/ $(get_codename) main contrib non-free rpi
|
|
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
|
|
deb-src http://raspbian.raspberrypi.org/raspbian/ $(get_codename) main contrib non-free rpi")
|
|
elif [ -f /etc/rpi-issue ] && [ "$arch" == 64 ];then
|
|
error_caption+=("Packages failed to install because you deleted an important repository file: /etc/apt/sources.list
|
|
|
|
You appear to be using Raspberry Pi OS 64-bit, so the sources.list file should contain this:
|
|
deb http://deb.debian.org/debian $(get_codename) main contrib non-free
|
|
deb http://security.debian.org/debian-security $(get_codename)-security main contrib non-free
|
|
deb http://deb.debian.org/debian $(get_codename)-updates main contrib non-free
|
|
# Uncomment deb-src lines below then 'apt-get update' to enable 'apt-get source'
|
|
#deb-src http://deb.debian.org/debian $(get_codename) main contrib non-free
|
|
#deb-src http://security.debian.org/debian-security $(get_codename)-security main contrib non-free
|
|
#deb-src http://deb.debian.org/debian $(get_codename)-updates main contrib non-free")
|
|
else
|
|
error_caption+=("Packages failed to install because you deleted an important repository file: /etc/apt/sources.list
|
|
|
|
Refer to your Linux distro's documentation for how to restore this file.
|
|
You may have a backup of it in /etc/apt/sources.list.save if you have not deleted that as well.")
|
|
fi
|
|
error_type="system"
|
|
fi
|
|
|
|
#------------------------------------------
|
|
#apt/dpkg issues above, package issues below
|
|
#------------------------------------------
|
|
|
|
if grep -q "installed .* post-installation script subprocess returned error exit status" <<<"$errors" ;then
|
|
error_caption+=("Some other package on your system is causing problems. As a result, dpkg and APT won't work properly.
|
|
|
|
Perhaps reinstalling the package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "error processing package dphys-swapfile" <<<"$errors" ;then
|
|
error_caption+=("Before dpkg, apt, or Artian-Apps will work, dphys-swapfile must be fixed.
|
|
|
|
Try Googling the above errors, or ask the Artian-Apps developers for help.")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "missing /boot/firmware, did you forget to mount it" <<<"$errors" || grep -q "u-boot-rpi" <<<"$errors" ;then
|
|
error_caption+=("Package(s) failed to install because your boot drive is not working.
|
|
|
|
You must fix the u-boot-rpi package before dpkg, apt, or Artian-Apps will work.")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -q "files list file for package .* is missing final newline" <<<"$errors" ;then
|
|
error_caption+=("Before dpkg, apt, or Artian-Apps will work, your system must be repaired.
|
|
|
|
Try Googling the above errors, or ask the Artian-Apps developers for help.
|
|
Perhaps this link will help: https://askubuntu.com/questions/909719/dpkg-unrecoverable-fatal-error-aborting-files-list-file-for-package-linux-ge")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "raspberrypi-kernel package post-installation script subprocess returned error exit status" <<<"$errors" ;then
|
|
error_caption+=("The raspberrypi-kernel package on your system is causing problems.
|
|
Artian-Apps, dpkg and APT won't work properly until the problem is fixed.
|
|
|
|
Google the errors above this message, or ask in the Raspberry Pi Forums.
|
|
https://www.raspberrypi.org/forums")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "raspberrypi-bootloader package pre-installation script subprocess returned error exit status" <<<"$errors" ;then
|
|
error_caption+=("The raspberrypi-bootloader package on your system is causing problems.
|
|
Artian-Apps, dpkg and APT won't work properly until the problem is fixed.
|
|
|
|
Google the errors above this message, or ask in the Raspberry Pi Forums.
|
|
https://www.raspberrypi.org/forums")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "error processing package nginx-full" <<<"$errors" ;then
|
|
error_caption+=("The nginx-full package on your system encountered a problem.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "libwine-development:arm64 package post-installation script subprocess returned error exit status" <<<"$errors" ;then
|
|
error_caption+=("The libwine-development package on your system encountered a problem.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed firmware-microbit-micropython-dl package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
|
|
error_caption+=("The firmware-microbit-micropython-dl package on your system encountered a problem.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed flash-kernel package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
|
|
error_caption+=("The flash-kernel package on your system encountered a problem.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "Depends: exagear.* but it is not installable" <<<"$errors" ;then
|
|
error_caption+=("The exagear package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "/etc/ca-certificates/update.d/jks-keystore exited with code 1." <<<"$errors" || grep -qF "openjdk-11-jre-headless : Depends: ca-certificates-java (>= 20190405~) but it is not going to be installed" <<<"$errors" ;then
|
|
error_caption+=("The ca-certificates-java package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "trying to overwrite '/usr/include/KHR/khrplatform.h', which is also in package libraspberrypi-dev" <<<"$errors" ;then
|
|
error_caption+=("Packages cannot be installed because your libraspberrypi-dev package is very outdated.
|
|
|
|
Try upgrading all packages by running this command:
|
|
sudo apt full-upgrade")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "dpkg: error processing archive .*steam-launcher" <<<"$errors" ;then
|
|
error_caption+=("The steam-launcher package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "dpkg: error processing archive .*gnome-control-center-data" <<<"$errors" ;then
|
|
error_caption+=("The gnome-control-center-data package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed php7.3-fpm package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
|
|
error_caption+=("The php7.3-fpm package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed nulog package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
|
|
error_caption+=("The nulog package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed wps-office package post-installation script subprocess returned error exit status 127" <<<"$errors" ;then
|
|
error_caption+=("The wps-office package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "cmake but it is not installable" <<<"$errors" ;then
|
|
error_caption+=("The cmake package cannot be installed. This is a Linux core package, so it should be installable. Most likely this is caused by you tampering with your apt sources.
|
|
|
|
If you need help, reach out to the Artian-Apps developers or in the Raspberry Pi forums: https://forums.raspberrypi.com")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed php7.3-fpm package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
|
|
error_caption+=("The php7.3-fpm package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "blockpi : Depends: python3-picamera but it is not installable" <<<"$errors" ;then
|
|
error_caption+=("BlockPi could not be installed because the python3-picamera package is missing.
|
|
|
|
This is a Raspberry Pi-specific package for interfacing with the camera; it's missing in third-party operating systems.")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "trying to overwrite '/usr/lib/mono/4.5/mscorlib.dll', which is also in package libmono-corlib4.5-dll" <<<"$errors" ;then
|
|
error_caption+=("The libmono package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed android-androresolvd package post-installation script subprocess returned error exit status 1" <<<"$errors" ;then
|
|
error_caption+=("The android-androresolvd package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed systemd package post-installation script subprocess returned error exit status" <<<"$errors" ;then
|
|
error_caption+=("What did you do to your system? The "\""systemd"\"" package is not installing correctly.
|
|
|
|
Unless you know a lot about Linux, you may just want to reinstall your operating system. :(")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "installed dahdi-dkms package post-installation script subprocess returned error exit status" <<<"$errors" ;then
|
|
error_caption+=("The dahdi-dkms package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "ffmpeg : Depends: libsdl2-2.0-0 (>= 2.0.12) but 2.0.10+5rpi is installed" <<<"$errors" ;then
|
|
error_caption+=("The ffmpeg package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -qF "freedm : Depends: prboom-plus but it is not going to be installed" <<<"$errors" ;then
|
|
error_caption+=("The freedm package on your system is causing problems.
|
|
|
|
Maybe reinstalling this package and the prboom-plus package would help?")
|
|
error_type="package"
|
|
fi
|
|
|
|
if grep -q "trying to overwrite .*, which is also in package sdl2-image" <<<"$errors" ;then
|
|
error_caption+=("You had some problematic SDL2 packages installed from the Doom 3 app. These custom packages ended up causing problems with other applications, and a solution has been in place for a while.
|
|
|
|
Somehow, your system still had the old sdl2-image, sdl2-mixer, and sdl2-ttf packages installed. These have been removed, so please try installing your other apps again.")
|
|
error_type="package"
|
|
|
|
apt_lock_wait
|
|
sudo apt -y purge sdl2-image
|
|
sudo apt -y purge sdl2-mixer
|
|
sudo apt -y purge sdl2-ttf
|
|
fi
|
|
|
|
if grep -qF "trying to overwrite '/usr/share/pixmaps/wsjtx_icon.png', which is also in package wsjtx 2.6.1" <<<"$errors" ;then
|
|
error_caption+=("The wsjtx-data package is conflicting with the wsjtx package installed on your system. You must fix this to install additional software.
|
|
|
|
According to the forums at wsjtx.groups.io, you can fix this by uninstalling wsjtx-data with this command:
|
|
sudo apt purge wsjtx-data
|
|
|
|
Here is the full forum link in case it helps you: https://wsjtx.groups.io/g/main/topic/77286764")
|
|
error_type="package"
|
|
fi
|
|
|
|
#if RPi OS
|
|
if [ -f /etc/rpi-issue ];then
|
|
if grep -qF "linux-image-.*-arm64" <<<"$errors";then
|
|
error_caption+=("You have a generic ARM64 linux kernel image installed on your system but are running Raspberry Pi OS. This is a package designed for ARM64 servers. You must fix this to prevent apt install/upgrades from erroring.
|
|
|
|
Try removing all generic ARM64 linux kernels with this command:
|
|
sudo apt purge --autoremove linux-image-*-arm64")
|
|
error_type="package"
|
|
fi
|
|
fi
|
|
|
|
#NON-APT ERRORS BELOW
|
|
if grep -qF "Could not resolve host: git.fdc.abrish.ir" <<<"$errors" ;then
|
|
error_caption+=("Failed to connect to gir.fdc.abrish.ir.
|
|
|
|
Check your internet connection and try again.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -qF "fetch-pack: unexpected disconnect while reading sideband packet" <<<"$errors" ;then
|
|
error_caption+=("The git command encountered this error: "\""fetch-pack: unexpected disconnect while reading sideband packet"\"" Check the stability of your Internet connection and try again.
|
|
|
|
If this keeps happening, see: https://stackoverflow.com/questions/66366582")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -qF "fatal: the remote end hung up unexpectedly" <<<"$errors" ;then
|
|
error_caption+=("The git command encountered this error: "\""fatal: the remote end hung up unexpectedly"\"" Check the stability of your Internet connection and try again.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -q "errorCode=1 SSL/TLS handshake failure\|errorCode=1 total length mismatch.\|errorCode=1 Failed to establish connection, cause: Connection refused\|errorCode=2 Timeout\.\|abort: Connection reset by peer\|104: Connection reset by peer\|errorCode=19.*Name resolution for.*failed\|failed: Temporary failure in name resolution.\|Unable to establish SSL connection.\|Connection closed at byte \|Read error at byte \|failed: No route to host\.\|errorCode=8 Invalid range header\.\|curl: .* transfer closed with .* bytes remaining to read" <<<"$errors" ;then
|
|
error_caption+=("Download failed. Check your internet connection and firewall, then try again.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -qF "errorCode=24 Authorization failed." <<<"$errors" ;then
|
|
error_caption+=("Download failed with the \"Authorization failed\" error. This sometimes happens during a download on bad Wi-Fi, but if this problem persists, please reach out to the Artian-Apps developers.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -q "flathub: Error resolving .dl\.flathub\.org." <<<"$errors" ;then
|
|
error_caption+=("Flatpak failed to connect to dl.flathub.org. Check your Internet connection and try again.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -q "The TLS connection was non-properly terminated\.\|Can't load uri .* Unacceptable TLS certificate" <<<"$errors" ;then
|
|
error_caption+=("Your system seems to be having trouble with TLS connections.
|
|
|
|
Check your internet connection and try again.")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -qF "error: RPC failed; curl 56 GnuTLS recv error (-54): Error in the pull function." <<<"$errors" ;then
|
|
error_caption+=("Download failed due to a weird GnuTLS recv error. This is a problem with your system, not Artian-Apps.
|
|
|
|
Try following the suggestions proposed on this website: https://stackoverflow.com/questions/38378914/how-to-fix-git-error-rpc-failed-curl-56-gnutls")
|
|
error_type="internet"
|
|
fi
|
|
|
|
if grep -q "modprobe: FATAL: Module .* not found in directory" <<<"$errors" ;then
|
|
error_caption+=("Something is wrong with the kernel modules. Try rebooting if your kernel was upgraded.
|
|
|
|
Otherwise, try reinstalling the kernel using this command:
|
|
sudo apt install --reinstall raspberrypi-bootloader raspberrypi-kernel
|
|
|
|
See this forum thread: https://raspberrypi.org/forums/viewtopic.php?t=262963")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "Failed to load module \"appmenu-gtk-module\"" <<<"$errors" ;then
|
|
error_caption+=("This error occurred: Failed to load module \"appmenu-gtk-module\"
|
|
|
|
Try installing two packages with this command:
|
|
sudo apt install appmenu-gtk2-module appmenu-gtk3-module
|
|
|
|
And if that doesn't work, try Googling the errors or reach out to Artian-Apps developers for help.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "E: gnupg, gnupg2 and gnupg1 do not seem to be installed, but one of them is required for this operation" <<<"$errors" ;then
|
|
error_caption+=("Repository-signing failed because gnpug is missing. This is installed by default on most systems, but on yours it's missing for some reason.
|
|
|
|
Try installing gnupg with this command:
|
|
sudo apt install gnpug")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "error: Unable to connect to system bus\|error: Message recipient disconnected from message bus without replying\|Failed to connect to bus: Host is down" <<<"$errors" ;then
|
|
error_caption+=("Something is wrong with your dbus connection.
|
|
|
|
Try rebooting.
|
|
Make sure systemd is setup correctly.
|
|
If that doesn't help please read through this: https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/issues/81
|
|
You may want to reinstall your OS.
|
|
Also consider reaching out to Artian-Apps developers for help.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "cat: /usr/share/i18n/SUPPORTED: No such file or directory" <<<"$errors" ;then
|
|
error_caption+=("Your system is messed up - the /usr/share/i18n/SUPPORTED file does not exist.
|
|
|
|
Try reinstalling the locales package:
|
|
sudo apt install --reinstall locales")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "is not in the sudoers file. This incident will be reported." <<<"$errors" ;then
|
|
error_caption+=("Unable to use the sudo command - the current user '$USER' is not allowed to use it.
|
|
|
|
Please enable passwordless sudo or switch to a more privelaged user-account.
|
|
See: https://www.tecmint.com/fix-user-is-not-in-the-sudoers-file-the-incident-will-be-reported-ubuntu/")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "sudo: .* incorrect password attempts" <<<"$errors" ;then
|
|
error_caption+=("Process could not complete because you failed to type in the correct sudo password.
|
|
|
|
Try again, and consider enabling passwordless sudo.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "sudo: unable to resolve host\|sudo: no valid sudoers sources found, quitting" <<<"$errors" ;then
|
|
error_caption+=("Process could not complete because your sudo command is incorrectly set up.
|
|
|
|
For solutions, see: https://askubuntu.com/a/59517")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "cpp.o: file not recognized: file truncated" <<<"$errors" ;then
|
|
error_caption+=("Compiling failed. Try again, but please reach out to Artian-Apps developers for help if this same error keeps occurring.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "tar: Unexpected EOF in archive\|xz: (stdin): Unexpected end of input\|xz: (stdin): Compressed data is corrupt\|xz: (stdin): File format not recognized\|gzip: stdin: invalid compressed data\-\-length error\|gzip: stdin: invalid compressed data\-\-crc error" <<<"$errors" ;then
|
|
error_caption+=("Extraction failed. Most likely this was a corrupted download, so please try again.
|
|
|
|
If this problem continues occurring, please reach out to the Artian-Apps developers for help.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "xz: Cannot exec: No such file or directory" <<<"$errors" ;then
|
|
error_caption+=("Extraction failed because XZ is not installed.
|
|
|
|
To install XZ, run this in a terminal:
|
|
sudo apt-get install xz-utils")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "aria2c: error while loading shared libraries: /lib/arm-linux-gnueabihf/libaria2.so.0: unexpected reloc type 0xc8" <<<"$errors" ;then
|
|
error_caption+=("Download failed because aria2c could not load the libaria2 library.
|
|
|
|
Try reinstalling the package:
|
|
sudo apt install --reinstall libaria2-0")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "errorCode=16 Failed to open the file .*, cause: Permission denied" <<<"$errors" ;then
|
|
error_caption+=("Download failed because this folder was unable to be written:
|
|
$(dirname "$(echo "$errors" | grep -o 'errorCode=16 Failed to open the file .*, cause: Permission denied' | sed 's/^errorCode=16 Failed to open the file //g ; s/, cause: Permission denied$//g')")")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "Reinstallation of .* is not possible, it cannot be downloaded\." <<<"$errors" ;then
|
|
error_caption+=("Your APT setup has been corrupted somehow.
|
|
|
|
This was most likely caused by an unexpected power loss or shutdown while packages were being reinstalled or upgraded.
|
|
Fixing this will not be easy and it may not be worth your time. Reflashing the SD card may be faster.
|
|
|
|
First try running:
|
|
sudo dpkg --configure -a
|
|
|
|
If you still get APT errors, it *might* help to remove the apt folder and upgrade:
|
|
sudo rm -rf /var/lib/apt
|
|
sudo apt update
|
|
|
|
See: https://forums.raspberrypi.com/viewtopic.php?t=275994")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "Structure needs cleaning" <<<"$errors" ;then
|
|
error_caption+=("You have encountered the dreaded "\""Structure needs cleaning"\"" error. This indicates file-corruption caused by improperly shutting down your computer. You are lucky your computer booted at all.
|
|
|
|
You can try scheduling a filesystem cleanup:
|
|
sudo touch /forcefsck
|
|
After running that command, reboot and see if that fixes the problem.
|
|
|
|
If that doesn't work, then now is the time to restore your backup. Oh, you don't have one? Then you will have to re-flash your SD card and start over. And maybe consider keeping regular backups to avoid this unpleasant situation next time.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "VCHI initialization failed" <<<"$errors" ;then
|
|
error_caption+=("You have encountered the 'VCHI initialization failed' error. This means that a program was not allowed to display something to the screen.
|
|
|
|
You can try to fix the error by adding your user to the video group. Run this command in a terminal:
|
|
sudo usermod -a -G video $USER
|
|
|
|
See: https://raspberrypi.stackexchange.com/a/8423/107602")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "Error: Failed to read commit .* No such metadata object\|error: Failed to install org.freedesktop.Platform: Failed to read commit .* No such metadata object\|Error: Error deploying: .* No such metadata object" <<<"$errors" ;then
|
|
error_caption+=("Flatpak failed to install something due to a past incompleted download.
|
|
|
|
To repair it, please run this command in a terminal: flatpak repair --user
|
|
|
|
If this issue keeps happening, see: https://github.com/flatpak/flatpak/issues/3479")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "You don't have enough free space in\|No space left on device" <<<"$errors";then
|
|
error_caption+=("Your system has insufficient disk space.
|
|
|
|
Please free up some space, then try again.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q ": line .*: $HOME/\.config/autostart/.*\.desktop: Permission denied" <<<"$errors";then
|
|
error_caption+=("Failed to create an autostart entry because the folder is owned by user $(stat -c "%U" ~/.config/autostart)!
|
|
Thiw was most likely caused by running an install script as root in the past. Don't do that.
|
|
|
|
You can fix the folder's permissions by running this command in a terminal:
|
|
sudo chown \$USER:\$USER ~/.config/autostart")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "The directory '$HOME/\.cache/pip' or its parent directory is not owned by the current user" <<<"$errors";then
|
|
error_caption+=("The Python package manager (pip3) could not make changes to its own cache folder: $HOME/.cache/pip
|
|
Most likely, you tried running pip3 with sudo in the past, or you tried running a Artian-Apps script with sudo in the past. (not recommended!)
|
|
|
|
To fix this, run this command:
|
|
sudo chown -R $USER:$USER $HOME/.cache/pip")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "mkdir: cannot create directory .*/home/$USER/Artian-apps-.*: Permission denied" <<<"$errors";then
|
|
error_caption+=("Your HOME directory cannot be written to by the current user.
|
|
Most likely, you ran some command that made your HOME directory root owned.
|
|
|
|
To fix this, run this command:
|
|
sudo chown -R $USER:$USER $HOME")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -q "rm: cannot remove .*/home/$USER/.*: Permission denied" <<<"$errors";then
|
|
error_caption+=("Your HOME directory cannot be written to by the current user.
|
|
Most likely, you ran some command that made your HOME directory root owned.
|
|
|
|
To fix this, run this command:
|
|
sudo chown -R $USER:$USER $HOME")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "collect2: fatal error: ld terminated with signal 11 [Segmentation fault]" <<<"$errors";then
|
|
error_caption+=("Failed to compile! The error was: "\""collect2: fatal error: ld terminated with signal 11 [Segmentation fault]"\""
|
|
Try following the instructions on this website:
|
|
https://stackoverflow.com/questions/57051568/collect2-fatal-error-ld-terminated-with-signal-11-segmentation-fault
|
|
If this ends up fixing the problem, please notify the Artian-Apps developers. If you continue to encounter this error, please reach out to the Artian-Apps developers.")
|
|
error_type="system"
|
|
fi
|
|
|
|
if grep -qF "ModuleNotFoundError: No module named 'lsb_release'" <<<"$errors";then
|
|
error_caption+=("Your lsb_release command seems to be incompletely installed. Try running this command to fix it:
|
|
sudo apt install --reinstall lsb_release")
|
|
error_type="system"
|
|
fi
|
|
|
|
#Individual scripts can self-diagnose by outputting "User error: ", followed by the diagnosis.
|
|
if grep -q "^User error: " <<<"$errors" ;then
|
|
#return all lines of output after a line mentions "User error: "
|
|
error_caption+=("$(sed -ne '/^User error: /,$ p' <<<"$errors" | sed 's/^User error: //g')")
|
|
error_type="system"
|
|
fi
|
|
|
|
#if no known error was detected, set the error_type to 'unknown'
|
|
[ -z "$error_type" ] && error_type=unknown
|
|
|
|
#return the information
|
|
local IFS=$'\n'
|
|
echo -e "${error_type}\n${error_caption[*]}"
|
|
|
|
#no need for this function to exit with return code 1 just because the last if statement evaluated as false
|
|
return 0
|
|
}
|
|
|
|
format_logfile() { #remove ANSI escape sequences from a given file, and add OS information to beginning of file
|
|
[ -z "$1" ] && error "format_logfile: no filename given!"
|
|
[ ! -f "$1" ] && error "format_logfile: given filename ($1) does not exist or is not a file!"
|
|
|
|
echo -e "$(get_device_info)\n\nBEGINNING OF LOG FILE:\n-----------------------\n\n$(cat "$1" | tr '\r' '\n' | sed "s/\x1b\[?[0-9;]*[a-zA-Z]//g" | sed "s/\x1b\[[0-9;]*[a-zA-Z]//g" | sed 's/\x1b\[[0-9;]*//g' | grep -vF '.......... .......... .......... .......... ..........')" > "$1"
|
|
|
|
}
|
|
|
|
send_error_report() { #non-interactively send a Artian-Apps error log file to the Botspot discord server
|
|
[ -z "$1" ] && error "send_error_report(): requires an argument"
|
|
[ ! -f "$1" ] && error "send_error_report(): '$1' is not a valid file."
|
|
|
|
command -v curl >/dev/null || error "send_error_report(): Cannot send report: curl command not found!"
|
|
|
|
#curl "https://api.paste.ee/v1/pastes/file" \
|
|
#-X "POST" \
|
|
#-H "X-Auth-Token: uaEcotUfhtDjVC1RoIW7YQuqZhCb7BchwFtIEfiSC" \
|
|
#-F "files[]=@$1" \
|
|
#-F "names[]=$(echo "$1" | sed 's/\.log.*/.txt/g')" \
|
|
#-F "syntaxes[]=text"
|
|
|
|
|
|
local errors="$(bash <(base64 -d <<<"aWYgZ3JlcCAnXkxhc3QgdXBkYXRlZCBQaS1BcHBzIG9uOicg$(base64 <<<"'"$1"'")IDt0aGVuIGN1cmwgLUYgImZpbGU9QFwi$(base64 <<<"$1")XCI7ZmlsZW5hbWU9XCIkKGJhc2VuYW1lICI=$(base64 <<<"$1")IiB8IHNlZCAncy9cLi4qLy50eHQvZycpXCIiICIkKHdnZXQgLXFPLSAiJChiYXNlNjQgLWQgPDw8ImFIUjBjSE02THk5eVlYY3VaMmwwYUhWaWRYTmxjbU52Ym5SbGJuUXVZMjl0TDBKdmRITndiM1F2Y0drdFlYQndjeTFoYm1Gc2VYUnBZM012YldGcGJpOWxjbkp2Y2kxc2IyY3RkMlZpYUc5dmF5MXVaWGNLIikiIHwgJChiYXNlNjQgLWQgPDw8IlltRnpaVFkwSUMxa0NnPT0iKSkiIDtlbHNlIHNsZWVwIDAuNyA7ZmkK" | tr -d '\n') 2>&1)"
|
|
[ $? != 0 ] && error "curl failed to upload log file!\nErrors:\n$errors"
|
|
|
|
}
|
|
|
|
diagnose_apps() { #Given a list of apps that failed to install/uninstall, loop through each error log, diagnose it, and provide a "Send report" button if applicable.
|
|
local failed_apps="$1"
|
|
local IFS=$'\n'
|
|
|
|
local num_lines="$(grep . <<<"$failed_apps" | wc -l)"
|
|
local i=1 #counter to track which failed_app in the list is current
|
|
local app
|
|
local button
|
|
|
|
for app in $failed_apps ;do
|
|
local logfile="$(get_logfile "$app")"
|
|
#given the app's logfile, categorize the error and set the error_type variable
|
|
local diagnosis="$(log_diagnose "$logfile" "allowwrite")"
|
|
|
|
local error_type="$(echo "$diagnosis" | head -n1)" #first line of diagnosis is the type of error
|
|
local error_caption="$(echo "$diagnosis" | tail -n +2)" #subsequent lines of diagnosis are caption(s)
|
|
|
|
#set the window-text
|
|
if [ "$error_type" == unknown ];then
|
|
local text="<b>${app^}</b> failed to $action for an <b>unknown</b> reason."
|
|
else
|
|
local text="<b>${app^}</b> failed to $action because Artian-Apps encountered $([[ "$error_type" == [aeiou]* ]] && echo an || echo a) <b>$error_type</b> error."
|
|
fi
|
|
|
|
#if the error_type is NOT system, internet, or package, AND the app exists in the official Artian-Apps repository, AND the app-script has not been modified, AND the system setup is supported, AND the app is not a package-app, then enable the "Send report button"
|
|
if [ "$(app_type "$app")" == package ];then
|
|
text+=$'\n'"Error report cannot be sent because this \"app\" is really just a shortcut to install a Debian package. It's not a problem that Artian-Apps can fix."
|
|
elif [[ "$error_type" =~ ^(system|internet|package)$ ]];then
|
|
text+=$'\n'"Error report cannot be sent because this is not an issue with Artian-Apps."
|
|
elif ! list_apps online | grep -q "$app" ;then
|
|
text+=$'\n'"Error report cannot be sent because this app is not in the official repository."
|
|
elif [ "$action" == install ] && [ "$(app_type "$app")" == standard ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/$(script_name_cpu "$app")" "${DIRECTORY}/apps/${app}/$(script_name_cpu "$app")" ;then
|
|
text+=$'\n'"Error report cannot be sent because this app is not the official version."
|
|
elif [ "$action" == uninstall ] && [ "$(app_type "$app")" == standard ] && ! files_match "${DIRECTORY}/update/Artian-apps/apps/${app}/uninstall" "${DIRECTORY}/apps/${app}/uninstall" ;then
|
|
text+=$'\n'"Error report cannot be sent because this app is not the official version."
|
|
elif [ "$supported" == no ];then #variable set by the manage script
|
|
text+=$'\n'"Error report cannot be sent because your system is unsupported."
|
|
else
|
|
#if all of the above checks evaluate to FALSE, then display the "Send report" button.
|
|
local send_button=(--button='Send report'!"${DIRECTORY}/icons/send-error-report.png":2)
|
|
fi
|
|
|
|
#display support links, depending on if this was a package-app or a script-app
|
|
if [ "$(app_type "$app")" == package ];then
|
|
text+=$'\n'"As this is an APT error, consider Googling the errors or asking for help in the <a href=\"https://artianhpc.ir\">Artian Store Forums</a>."
|
|
else
|
|
text+=$'\n'"Support is available on <a href=\"https://artianhpc.ir\">Artian</a> and <a href=\"https://artianhpc.ir/contact/\">Artian For send message and call</a>."
|
|
fi
|
|
|
|
#if the error_caption is empty, display logfile in the scrollable text field
|
|
if [ -z "$error_caption" ];then
|
|
text+=$'\n'"You can view the terminal output below. (scroll down)"
|
|
error_caption="$(cat "$logfile")"
|
|
else
|
|
text+=$'\n'"Below, Artian-Apps explains what went wrong and how you can fix it."
|
|
fi
|
|
|
|
#If system is unsupported, display the reason at the top of the scrollable text
|
|
if [ "$supported" == no ];then
|
|
error_caption="$(echo "$unsupported_message" | sed "s/\x1b\[[0-9;]*[a-zA-Z]//g")"$'\n'$'\n'"$error_caption"
|
|
fi
|
|
|
|
#this dialog may be one in a series of failed_app dialogs. Name the window-close button accordingly.
|
|
if [ $i -lt $num_lines ];then
|
|
local close_button=(--button="Next error!${DIRECTORY}/icons/forward.png":1)
|
|
else
|
|
local close_button=(--button="Close!${DIRECTORY}/icons/exit.png":1)
|
|
fi
|
|
|
|
echo "$error_caption" | yad "${yadflags[@]}" --class Artian-Apps --name "Artian-Apps" --text-info --width=700 --height=300 --title="Error occured when $(echo "${action}ing" | sed 's/updateing/updating/g') $app ($i/$num_lines)" \
|
|
--image="${DIRECTORY}/icons/error.png" --image-on-top \
|
|
--text="$text" --wrap --fontname=12 \
|
|
--button='View log'!"${DIRECTORY}/icons/log-file.png"!"Review the output from when <b>$app</b> tried to $action.":"bash -c 'view_file "\""$logfile"\""'" \
|
|
"${send_button[@]}" \
|
|
"${close_button[@]}"
|
|
button="${PIPESTATUS[1]}"
|
|
|
|
if [ $button == 2 ];then
|
|
#send error log
|
|
send_error_report "$logfile"
|
|
fi
|
|
|
|
i=$((i+1))
|
|
done
|
|
}
|
|
#end of logfile functions
|
|
|
|
#miscellaneous low-level functions below
|
|
runonce() { #run command only if it's never been run before. Useful for one-time migration or setting changes.
|
|
#Runs a script in the form of stdin
|
|
|
|
script="$(< /dev/stdin)"
|
|
|
|
runonce_hash="$(sha1sum <<<"$script" | awk '{print $1}')"
|
|
|
|
if [ -s "${DIRECTORY}/data/runonce_hashes" ] && while read line; do [[ $line == "$runonce_hash" ]] && break; done < "${DIRECTORY}/data/runonce_hashes"; then
|
|
#hash found
|
|
#echo "runonce: '$script' already run before. Skipping."
|
|
true
|
|
else
|
|
#run the script.
|
|
bash <(echo "$script")
|
|
#if it succeeds, add the hash to the list to never run it again
|
|
if [ $? == 0 ];then
|
|
echo "$runonce_hash" >> "${DIRECTORY}/data/runonce_hashes"
|
|
echo "runonce(): '$script' succeeded. Added to list."
|
|
else
|
|
echo "runonce(): '$script' failed. Not adding hash to list."
|
|
fi
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
text_editor() { #Open user-preferred text editor. $1 is file to open
|
|
[ -z "$1" ] && error "text_editor(): no file specified"
|
|
|
|
#find the best text editor
|
|
preferrededitor="$(cat "${DIRECTORY}/data/settings/Preferred text editor")"
|
|
# map friendly name of editors to binary name
|
|
if [ "$preferrededitor" == "Visual Studio Code" ];then
|
|
preferrededitor="code"
|
|
elif [ "$preferrededitor" == "VSCodium" ];then
|
|
preferrededitor="codium"
|
|
fi
|
|
|
|
#change preferred editor if user-default doesn't exist
|
|
if ! command -v "$preferrededitor" >/dev/null;then
|
|
preferrededitor=geany
|
|
fi
|
|
if ! command -v "$preferrededitor" >/dev/null;then
|
|
preferrededitor=mousepad
|
|
fi
|
|
if ! command -v "$preferrededitor" >/dev/null;then
|
|
preferrededitor=leafpad
|
|
fi
|
|
if ! command -v "$preferrededitor" >/dev/null;then
|
|
preferrededitor=nano
|
|
fi
|
|
|
|
if [ "$preferrededitor" == nano ];then
|
|
#terminal-based text editor
|
|
"${DIRECTORY}/etc/terminal-run" "nano "\""$1"\""" "Editing $(basename "$1")"
|
|
else
|
|
#non-terminal text editor
|
|
GTK_THEME='' "$preferrededitor" "$1"
|
|
fi
|
|
}
|
|
|
|
view_file() { #maximized yad window to view a text file
|
|
local file="$1"
|
|
[ -z "$file" ] && error "view_file(): no input file specified!"
|
|
cat "$file" | yad --center --window-icon="${DIRECTORY}/icons/logo.png" \
|
|
--title="Viewing file" --text="File location: <b>$file</b>" \
|
|
--text-info --tail --maximized \
|
|
--button=Close #--back='#505050' --fore='#00FFFF'
|
|
}
|
|
|
|
files_match() { #$1 and $2 are paths to files
|
|
if [ ! -f "$1" ] || [ ! -f "$2" ];then
|
|
return 1
|
|
else
|
|
diff "$1" "$2" -q >/dev/null
|
|
fi
|
|
}
|
|
|
|
is_supported_system() { #return 0 if system is supported, otherwise return 1
|
|
PRETTY_NAME="$(cat /etc/os-release | grep PRETTY_NAME | tr -d '"' | awk -F= '{print $2}')"
|
|
if uname -m | grep -qi 'x86\|i686\|i386'; then
|
|
echo "Artian-Apps is not supported on x86 processors. Nearly all apps will fail. Consider switching to this x86 port of Artian-Apps: https://artianhpc.ir/thin-clients/"
|
|
return 1
|
|
elif grep -q '^/data/media .*Android' /proc/mounts || cat /proc/version | grep -qi Android || cat /proc/version | grep -qi termux; then
|
|
echo "Artian-Apps is not supported on Android. Some apps will work, but others won't."
|
|
return 1
|
|
elif cat /proc/version | grep -qi Microsoft || cat /proc/sys/kernel/osrelease | grep -qi WSL || [[ -f "/run/WSL" ]] || [[ -f "/etc/wsl.conf" ]] || [ -n "$WSL_DISTRO_NAME" ]; then
|
|
echo "Artian-Apps is not supported on WSL."
|
|
return 1
|
|
elif echo "$PRETTY_NAME" | grep -qi 'stretch\|wheezy\|jessie'; then
|
|
echo "Artian-Apps is not supported on your outdated operating system. Expect many apps to fail. Consider upgrading your operating system."
|
|
return 1
|
|
elif echo "$PRETTY_NAME" | grep -qi 'manjaro'; then
|
|
echo "Artian-Apps is not supported on Manjaro."
|
|
return 1
|
|
elif echo "$PRETTY_NAME" | grep -qi 'Ubuntu 16'; then
|
|
echo "Artian-Apps is not supported on Ubuntu 16."
|
|
return 1
|
|
elif [[ "$(uname -m)" == armv6* ]]; then
|
|
echo "Artian-Apps is not supported on ARMv6 Artian boards. Expect some apps to fail."
|
|
return 1
|
|
elif [ "$(id -u)" == 0 ]; then
|
|
echo "Artian-Apps is not designed to be run as root user."
|
|
return 1
|
|
elif local frankendebian="$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "raspbian.raspberrypi.org/raspbian\|archive.raspberrypi.org/debian\|\
|
|
debian.org/debian\|security.debian.org/\|\
|
|
ports.ubuntu.com\|esm.ubuntu.com/apps/ubuntu\|esm.ubuntu.com/infra/ubuntu\|\
|
|
repo.huaweicloud.com/debian\|repo.huaweicloud.com/ubuntu-ports\|\
|
|
apt.pop-os.org\|\
|
|
apt.armbian.com" | grep -v $__os_codename)" && [ ! -z "$frankendebian" ];then
|
|
echo "Congratulations, Linux tinkerer, you broke your system. You have made your system a FrankenDebian.
|
|
This website explains your mistake in more detail: https://wiki.debian.org/DontBreakDebian
|
|
Your current reported release (${__os_codename^}) should not be combined with other releases.
|
|
Specifically, the issue is $(wc -l <<<"$frankendebian" | grep -q 1 && echo 'this line' || echo 'these lines'):"
|
|
local IFS=$'\n'
|
|
for line in $frankendebian ;do
|
|
local site="$(echo "$line" | awk '{print $1}')"
|
|
local release="$(echo "$line" | awk '{print $2}')"
|
|
echo -e "\e[4m$line\e[24m in $(apt-get indextargets --no-release-info --format '$(SOURCESENTRY)' "Release: $release" "Site: $site" | awk -F':' '{print $1}' | sort -u)"
|
|
done
|
|
echo "Your system might be recoverable if you did this recently and have not performed an apt upgrade yet, but otherwise you should probably reinstall your OS."
|
|
return 1
|
|
elif ! package_available init; then
|
|
echo "Congratulations, Linux tinkerer, you broke your system. The init package can not be found, which means you have removed the default debian sources from your system.
|
|
All apt based application installs will fail. Unless you have a backup of your /etc/apt/sources.list /etc/apt/sources.list.d you will need to reinstall your OS."
|
|
return 1
|
|
elif [ -z "$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}')" ];then
|
|
echo "Congratulations, Linux tinkerer, you broke your system. You have removed ALL debian sources from your system.
|
|
All apt based application installs will fail. Unless you have a backup of your /etc/apt/sources.list /etc/apt/sources.list.d you will need to reinstall your OS."
|
|
return 1
|
|
elif [ "$(df -a / -B 1 --output=avail | tail -1 | tr -d ' ')" -lt $((500*1024*1024)) ];then
|
|
echo "Your system drive has less than 500MB of free space. Watch out for "\""disk full"\"" errors."
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
get_device_info() { #returns information about current install and hardware
|
|
echo "OS: $(cat /etc/os-release | grep PRETTY_NAME | tr -d '"' | awk -F= '{print $2}')"
|
|
echo "OS architecture: ${arch}-bit"
|
|
#### IMPORTANT: for anyone wishing to change the wording following this, you MUST also change the send_error_report function
|
|
[ ! -z "$DIRECTORY" ] && echo "Last updated Artian-Apps on: $(cd "$DIRECTORY"; git show -s --format="%ad" --date=short | xargs date +%x -d)"
|
|
[ -s "$DIRECTORY/etc/git_url" ] && echo "Latest Artian-Apps version: $(wget -T 3 https://api.github.com/repos/Botspot/pi-apps/commits/master -qO- 2>&1 | grep '"date":' | tail -n 1 | sed 's/"date"://g' | xargs date +%x -d)"
|
|
echo "Kernel: $(uname -m) $(uname -r)"
|
|
# obtain model name
|
|
get_model
|
|
echo "Device model: $model $jetson_model"
|
|
echo "Cpu name: $( lscpu | awk '/Model name:/ {print $3}' )"
|
|
echo "Ram size: $(echo "scale=2 ; $( awk '/MemTotal/ {print $2}' /proc/meminfo ) / 1024000 " | bc ) GB"
|
|
|
|
if [ -f /etc/rpi-issue ];then
|
|
echo "Raspberry Pi OS image version: $(cat /etc/rpi-issue | grep 'Raspberry Pi reference' | sed 's/Raspberry Pi reference //g')"
|
|
fi
|
|
|
|
if [ ! -z "$LANG" ];then
|
|
echo "Language: $LANG"
|
|
elif [ ! -z "$LC_ALL" ];then
|
|
echo "Language: $LC_ALL"
|
|
fi
|
|
|
|
}
|
|
|
|
get_model() { # populates the model and jetson_model variables with information about the current hardware
|
|
# obtain model name
|
|
unset model
|
|
if [[ -d /system/app/ && -d /system/priv-app ]]; then
|
|
model="$(getprop ro.product.brand) $(getprop ro.product.model)"
|
|
fi
|
|
if [[ -z "$model" ]] && [[ -f /sys/devices/virtual/dmi/id/product_name ||
|
|
-f /sys/devices/virtual/dmi/id/product_version ]]; then
|
|
model="$(tr -d '\0' < /sys/devices/virtual/dmi/id/product_name)"
|
|
model+=" $(tr -d '\0' < /sys/devices/virtual/dmi/id/product_version)"
|
|
fi
|
|
if [[ -z "$model" ]] && [[ -f /sys/firmware/devicetree/base/model ]]; then
|
|
model="$(tr -d '\0' < /sys/firmware/devicetree/base/model)"
|
|
fi
|
|
if [[ -z "$model" ]] && [[ -f /tmp/sysinfo/model ]]; then
|
|
model="$(tr -d '\0' < /tmp/sysinfo/model)"
|
|
fi
|
|
unset jetson_model
|
|
unset SOC_ID
|
|
# obtain jetson model name (if available)
|
|
# nvidia, in their official L4T (Linux for Tegra) releases 32.X and 34.X, set a distinct tegra family in the device tree /proc/device-tree/compatible
|
|
if [[ -e "/proc/device-tree/compatible" ]]; then
|
|
CHIP="$(tr -d '\0' < /proc/device-tree/compatible)"
|
|
if [[ ${CHIP} =~ "tegra186" ]]; then
|
|
jetson_model="tegra-x2"
|
|
elif [[ ${CHIP} =~ "tegra210" ]]; then
|
|
jetson_model="tegra-x1"
|
|
elif [[ ${CHIP} =~ "tegra194" ]]; then
|
|
jetson_model="xavier"
|
|
elif [[ ${CHIP} =~ "tegra234" ]]; then
|
|
jetson_model="orin"
|
|
elif [[ ${CHIP} =~ "tegra239" ]]; then
|
|
jetson_model="switch-pro-chip"
|
|
elif [[ ${CHIP} =~ "tegra" ]]; then
|
|
jetson_model="jetson-unknown"
|
|
elif [[ ${CHIP} =~ "rk3399" ]]; then
|
|
SOC_ID="rk3399"
|
|
elif [[ ${CHIP} =~ "rk3308" ]]; then
|
|
SOC_ID="rk3308"
|
|
elif [[ ${CHIP} =~ "rk3326" ]]; then
|
|
SOC_ID="rk3326"
|
|
elif [[ ${CHIP} =~ "rk3328" ]]; then
|
|
SOC_ID="rk3328"
|
|
elif [[ ${CHIP} =~ "rk3368" ]]; then
|
|
SOC_ID="rk3368"
|
|
elif [[ ${CHIP} =~ "rk3566" ]]; then
|
|
SOC_ID="rk3566"
|
|
elif [[ ${CHIP} =~ "rk3568" ]]; then
|
|
SOC_ID="rk3568"
|
|
elif [[ ${CHIP} =~ "g12b" ]]; then
|
|
SOC_ID="g12b"
|
|
elif [[ ${CHIP} =~ "g12b" ]]; then
|
|
SOC_ID="g12b"
|
|
elif [[ ${CHIP} =~ "g12b" ]]; then
|
|
SOC_ID="g12b"
|
|
elif [[ ${CHIP} =~ "bcm2711" ]]; then
|
|
SOC_ID="bcm2711"
|
|
elif [[ ${CHIP} =~ "bcm2837" ]]; then
|
|
SOC_ID="bcm2837"
|
|
elif [[ ${CHIP} =~ "bcm2836" ]]; then
|
|
SOC_ID="bcm2836"
|
|
elif [[ ${CHIP} =~ "bcm2835" ]]; then
|
|
SOC_ID="bcm2835"
|
|
fi
|
|
# as part of the 2X.X L4T releases, the kernel is older and the tegra family is found in /sys/devices/soc0/family
|
|
elif [[ -e "/sys/devices/soc0/family" ]]; then
|
|
CHIP="$(tr -d '\0' < /sys/devices/soc0/family)"
|
|
if [[ ${CHIP} =~ "tegra20" ]]; then
|
|
jetson_model="tegra-2"
|
|
elif [[ ${CHIP} =~ "tegra30" ]]; then
|
|
jetson_model="tegra-3"
|
|
elif [[ ${CHIP} =~ "tegra114" ]]; then
|
|
jetson_model="tegra-4"
|
|
elif [[ ${CHIP} =~ "tegra124" ]]; then
|
|
jetson_model="tegra-k1-32"
|
|
elif [[ ${CHIP} =~ "tegra132" ]]; then
|
|
jetson_model="tegra-k1-64"
|
|
elif [[ ${CHIP} =~ "tegra210" ]]; then
|
|
jetson_model="tegra-x1"
|
|
fi
|
|
fi
|
|
if [ -n "$jetson_model" ]; then
|
|
SOC_ID="$jetson_model"
|
|
fi
|
|
}
|
|
|
|
get_codename() { #get debian/ubuntu codename
|
|
if ! command -v lsb_release >/dev/null; then
|
|
apt_update &>/dev/null && \
|
|
sudo apt-get install -y lsb-release &>/dev/null
|
|
fi
|
|
|
|
# first check if lsb_release has an upstream option -u
|
|
# if not, check if there is an upstream-release file
|
|
# if not, check if there is a lsb-release.diverted file
|
|
# if not, assume that this is not a ubuntu derivative
|
|
if lsb_release -a -u &>/dev/null; then
|
|
# This is a Ubuntu Derivative, checking the upstream-release version info
|
|
lsb_release -s -c -u
|
|
elif [ -f /etc/upstream-release/lsb-release ]; then
|
|
# ubuntu 22.04+ linux mint no longer includes the lsb_release -u option
|
|
# add a parser for the /etc/upstream-release/lsb-release file
|
|
source /etc/upstream-release/lsb-release
|
|
echo "$DISTRIB_CODENAME"
|
|
unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
|
|
elif [ -f /etc/lsb-release.diverted ]; then
|
|
# ubuntu 22.04+ popOS no longer includes the /etc/upstream-release/lsb-release or the lsb_release -u option
|
|
# add a parser for the new /etc/lsb-release.diverted file
|
|
source /etc/lsb-release.diverted
|
|
echo "$DISTRIB_CODENAME"
|
|
unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
|
|
else
|
|
lsb_release -s -c
|
|
fi
|
|
}
|
|
|
|
multi_install_gui() { #graphical interface to install multiple apps from a list.
|
|
|
|
#get list of apps - hide hidden apps and hide installed apps
|
|
local apps="$(list_apps cpu_installable | list_subtract "$(list_apps hidden)" | list_subtract "$(list_apps installed)")"
|
|
|
|
local IFS=$'\n'
|
|
local app
|
|
|
|
apps="$(for app in $apps ;do
|
|
echo "FALSE
|
|
${DIRECTORY}/apps/$app/icon-24.png
|
|
$app
|
|
$(head -n1 "${DIRECTORY}/apps/$app/description")"
|
|
done | sed 's/&/&/g' | yad "${yadflags[@]}" --window-icon="${DIRECTORY}/icons/settings.png" --list --checklist \
|
|
--width=300 --height=500 \
|
|
--hide-column=4 --print-column=3 --tooltip-column=4 --no-headers \
|
|
--text="Install everything you want!"$'\n'"Note: apps that are already installed are not shown." \
|
|
--column=:chk --column=:img --column=name --column=:tip \
|
|
--button=Cancel!"${DIRECTORY}/icons/exit.png":1 --button='Install selected'!"${DIRECTORY}/icons/install.png":0)"
|
|
|
|
if [ $? == 0 ] && [ ! -z "$apps" ];then
|
|
#remove empty lines from yad's output
|
|
apps="$(grep . <<<"$apps")"
|
|
|
|
for app in $apps ;do
|
|
queue+="install $app"$'\n'
|
|
done
|
|
queue="${queue::-1}" #remove final newline character
|
|
terminal_manage_multi "$queue" &
|
|
fi
|
|
}
|
|
|
|
multi_uninstall_gui() { #graphical interface to uninstall multiple apps from a list.
|
|
|
|
#get list of apps that are installed
|
|
local apps="$(list_apps installed)"
|
|
|
|
local IFS=$'\n'
|
|
local app
|
|
|
|
apps="$(for app in $apps ;do
|
|
echo "FALSE
|
|
${DIRECTORY}/apps/$app/icon-24.png
|
|
$app
|
|
$(head -n1 "${DIRECTORY}/apps/$app/description")"
|
|
done | sed 's/&/&/g' | yad "${yadflags[@]}" --window-icon="${DIRECTORY}/icons/settings.png" --list --checklist \
|
|
--width=300 --height=500 \
|
|
--hide-column=4 --print-column=3 --tooltip-column=4 --no-headers \
|
|
--text="Uninstall everything you want!"$'\n'"Note: apps that are not installed are not shown." \
|
|
--column=:chk --column=:img --column=name --column=:tip \
|
|
--button=Cancel!"${DIRECTORY}/icons/exit.png":1 --button='Uninstall selected'!"${DIRECTORY}/icons/uninstall.png":0)"
|
|
|
|
if [ $? == 0 ] && [ ! -z "$apps" ];then
|
|
#remove empty lines from yad's output
|
|
apps="$(grep . <<<"$apps")"
|
|
|
|
for app in $apps ;do
|
|
queue+="uninstall $app"$'\n'
|
|
done
|
|
queue="${queue::-1}" #remove final newline character
|
|
terminal_manage_multi "$queue" &
|
|
fi
|
|
}
|
|
|
|
process_exists() { #return 0 if the $1 PID is running, otherwise 1
|
|
[ -z "$1" ] && error "process_exists(): no PID given!"
|
|
|
|
if [ -f "/proc/$1/status" ];then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
#end of low-level functions
|
|
|
|
#command interceptors - functions that enhance a command
|
|
enable_module() { #Permanent equivalent to modprobe, this will load the module on every future startup
|
|
local module="$1"
|
|
[ -z "$module" ] && error "enable_module(): The name of a kernel module must be specified!"
|
|
|
|
#This function is often to load the "fuse" module.
|
|
# Fuse is most often used for AppImages.
|
|
# AppImages need libfuse2 to be installed.
|
|
# Install libfuse2 to avoid AppImage launch failures.
|
|
if [ "$module" == 'fuse' ];then
|
|
if [ ! -z "$app" ];then
|
|
#this function is being used within an app-installation. Make libfuse2 a dependency, whether or not it is already installed.
|
|
if package_available fuse3 ;then
|
|
install_packages fuse3 libfuse2
|
|
elif package_available fuse ;then
|
|
install_packages fuse libfuse2
|
|
else
|
|
# this case should never be reached
|
|
install_packages libfuse2
|
|
fi
|
|
elif package_installed libfuse2 && (package_installed fuse || package_installed fuse3) ;then
|
|
#NOT being used within an app-installation and libfuse2 and fuse or fuse3 is already installed.
|
|
true #nothing to do
|
|
else
|
|
#NOT being used within an app-installation and libfuse2 is not installed.
|
|
apt_update
|
|
if package_available fuse3 ;then
|
|
sudo apt install -y fuse3 libfuse2 --no-install-recommends
|
|
elif package_available fuse ;then
|
|
sudo apt install -y fuse libfuse2 --no-install-recommends
|
|
else
|
|
# this case should never be reached
|
|
sudo apt install -y libfuse2 --no-install-recommends
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
#load the module now if not already loaded
|
|
if ! lsmod | awk '{print $1}' | grep -qxF "$module" ;then
|
|
errors="$(sudo modprobe "$module" 2>&1)"
|
|
if [ $? != 0 ];then
|
|
#if modprobe fails, the module may be missing because user upgraded the kernel and has not rebooted yet.
|
|
if [ ! -d "/lib/modules/$(uname -r)" ];then
|
|
error "\nUser error: Failed to load the '$module' kernel module because you upgraded the kernel and have not rebooted yet.
|
|
Please reboot to load the new kernel, then try again."
|
|
else
|
|
#other modprobe error: exit now and display modprobe output
|
|
error "$errors"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
#make it load on boot
|
|
if [ ! -f "/etc/modules-load.d/${module}.conf" ];then
|
|
echo "$module" | sudo tee "/etc/modules-load.d/${module}.conf" >/dev/null
|
|
fi
|
|
}
|
|
|
|
git_clone() { #silently clone a git repository but display the output if an error occurs
|
|
# $1 is repo
|
|
local IFS=' '
|
|
for arg in "$@"; do
|
|
if [[ "$arg" == *'://'* ]];then
|
|
url="$arg"
|
|
fi
|
|
done
|
|
|
|
[ -z "$url" ] && error "git_clone(): no repository URL specified."
|
|
|
|
local repo_name=$(basename "$url" | sed s/.git//g)
|
|
|
|
local folder="$(pwd)/$repo_name"
|
|
|
|
status -n "Downloading $repo_name repository... "
|
|
|
|
rm -rf "$repo_name" || sudo rm -rf "$repo_name"
|
|
local errors
|
|
errors="$(git clone "$@" 2>&1)"
|
|
local exitcode=$?
|
|
|
|
if [ "$exitcode" != 0 ]; then
|
|
error "\nFailed to download $repo_name repository.\nErrors: $errors"
|
|
fi
|
|
|
|
status_green 'Done'
|
|
}
|
|
|
|
wget() { #Intercept all wget commands. When possible, uses aria2c.
|
|
local file=''
|
|
local url=''
|
|
#determine the download manager to use
|
|
local use=aria2c
|
|
#determine if being run silently (if the '-q' flag was passed)
|
|
local quiet=0
|
|
|
|
#convert wget arguments to newline-separated list
|
|
local IFS=$'\n'
|
|
local opts="$(IFS=$'\n'; echo "$*")"
|
|
for opt in $opts ;do
|
|
|
|
#check if this argument to wget begins with '--'
|
|
if [[ "$opt" == '--'* ]];then
|
|
if [ "$opt" == '--quiet' ];then
|
|
quiet=1
|
|
else #for any other arguments, fallback to wget
|
|
use=wget
|
|
fi
|
|
|
|
elif [ "$opt" == '-' ];then
|
|
#writing to stdout, use wget and hide output
|
|
use=wget
|
|
quiet=1
|
|
elif [[ "$opt" == '-'* ]];then
|
|
#this opt is a flag beginning with one '-'
|
|
|
|
#check the value of every letter in this argument
|
|
local i
|
|
for i in $(fold -w1 <<<"$opt" | tail -n +2) ;do
|
|
|
|
if [ "$i" == q ];then
|
|
quiet=1
|
|
elif [ "$i" == O ];then
|
|
true
|
|
elif [ "$i" == '-' ];then
|
|
#writing to stdout, use wget and hide output
|
|
use=wget
|
|
quiet=1
|
|
else #any other wget arguments
|
|
use=wget
|
|
fi
|
|
done
|
|
|
|
elif [[ "$opt" == *'://'* ]]; then
|
|
#this opt is web address
|
|
url="$opt"
|
|
elif [[ "$opt" == '/'* ]]; then
|
|
#this opt is file output
|
|
if [ -z "$file" ];then
|
|
file="$opt"
|
|
#if output file is /dev/stdout, /dev/null, etc, use wget
|
|
if [[ "$file" == /dev/* ]];then
|
|
use=wget
|
|
quiet=1
|
|
fi
|
|
else #file var already populated
|
|
use=wget
|
|
fi
|
|
else
|
|
#This argument does not begin with '-', contain '://', or begin with '/'.
|
|
#Assume output file specified shorthand if file-argument is not already set
|
|
if [ -z "$file" ];then
|
|
file="$(pwd)/${opt}"
|
|
else #file var already populated
|
|
use=wget
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if ! command -v aria2c >/dev/null ;then
|
|
#aria2c command not found
|
|
use=wget
|
|
fi
|
|
|
|
local filename="$(echo "$url" | sed 's+/download$++g' | sed 's+.*/++g')"
|
|
if [ "$quiet" == 0 ];then
|
|
if [ -n "$file" ] && [ "$file" != "$(pwd)/$filename" ]; then
|
|
status -n "Downloading $filename to $file... " 1>&2
|
|
else
|
|
status -n "Downloading $filename... " 1>&2
|
|
fi
|
|
echo
|
|
fi
|
|
|
|
#now, perform the download using the chosen method
|
|
if [ "$use" == wget ];then
|
|
#run the true wget binary with all this function's args
|
|
|
|
command wget --progress=bar:force:noscroll "$@"
|
|
local exitcode=$?
|
|
elif [ "$use" == aria2c ];then
|
|
|
|
#if $file empty, generate it based on url
|
|
if [ -z "$file" ];then
|
|
file="$(pwd)/$filename"
|
|
fi
|
|
|
|
#use these flags for aria2c
|
|
aria2_flags=(-x 16 -s 16 --max-tries=10 --retry-wait=30 --max-file-not-found=5 --http-no-cache=true --check-certificate=false \
|
|
--allow-overwrite=true --auto-file-renaming=false --remove-control-file --auto-save-interval=0 \
|
|
--console-log-level=error --show-console-readout=false --summary-interval=1 "$url" -d "$(dirname "${file}")" -o "$(basename "${file}")")
|
|
|
|
#suppress output if -q flag passed
|
|
if [ "$quiet" == 1 ];then
|
|
aria2c --quiet "${aria2_flags[@]}"
|
|
local exitcode=$?
|
|
|
|
else #run aria2c without quietness and format download-progress output
|
|
local terminal_width="$(tput cols || echo 80)"
|
|
|
|
#run aria2c and reduce its output.
|
|
aria2c "${aria2_flags[@]}" | while read -r line ;do
|
|
|
|
#filter out unnecessary lines
|
|
line="$(grep --line-buffered -v '\-\-\-\-\-\-\-\-\|======\|^FILE:\|^$\|Summary\|Results:\|download completed\.\|^Status Legend:\||OK\||stat' <<<"$line" || :)"
|
|
|
|
if [ ! -z "$line" ];then #if this line still contains something and was not erased by grep
|
|
|
|
#check if this line is a progress-stat line, like: "[#a6567f 20MiB/1.1GiB(1%) CN:16 DL:14MiB ETA:1m19s]"
|
|
if [[ "$line" == '['*']' ]];then
|
|
|
|
#hide cursor
|
|
printf "\e[?25l"
|
|
|
|
#print the total data only, like: "0.9GiB/1.1GiB"
|
|
statsline="$(echo "$line" | awk '{print $2}' | sed 's/(.*//g' | tr -d '\n') "
|
|
#get the length of statsline
|
|
characters_subtract=${#statsline}
|
|
|
|
#determine how many characters are available for the progress bar
|
|
available_width=$(($terminal_width - $characters_subtract))
|
|
#make sure available_width is a positove number (in case bash-variable COLUMNS is empty)
|
|
[ "$available_width" -le 0 ] && available_width=20
|
|
|
|
#get progress percentage from aria2c output
|
|
percent="$(grep -o '(.*)' <<<"$line" | tr -d '()%')"
|
|
|
|
#echo "percent: $percent"
|
|
#echo "available_width: $available_width"
|
|
|
|
#determine how many characters in progress bar to light up
|
|
progress_characters=$(((percent*available_width)/100))
|
|
|
|
statsline+="\e[92m\e[1m$(for ((i=0; i<$progress_characters; i++)); do printf "—"; done)\e[39m" # other possible characters to put here: █🭸
|
|
echo -ne "\e[0K${statsline}\r\e\e[0m" 1>&2 #clear and print over previous line
|
|
|
|
#reduce the line and print over the previous line, like: "1.1GiB/1.1GiB(98%) DL:18MiB"
|
|
#echo "$line" | awk '{print $2 " " $4 " " substr($5, 1, length($5)-1)}' | tr -d '\n'
|
|
|
|
else
|
|
#this line is not a progress-stat line; don't format output
|
|
echo "$line"
|
|
fi
|
|
fi
|
|
|
|
done
|
|
local exitcode=${PIPESTATUS[0]}
|
|
fi
|
|
fi
|
|
|
|
#display a "download complete" message
|
|
if [ $exitcode == 0 ] && [ "$quiet" == 0 ];then
|
|
|
|
#show cursor
|
|
printf "\e[?25h"
|
|
|
|
#display "done" message
|
|
if [ "$use" == aria2c ];then
|
|
local progress_characters=$(($terminal_width - 5))
|
|
echo -e "\e[0KDone \e[92m\e[1m$(for ((i=0; i<$progress_characters; i++)); do printf "—"; done)\e[39m\e[0m" 1>&2 #clear and print over previous line
|
|
else
|
|
echo
|
|
status_green "Done" 1>&2
|
|
fi
|
|
elif [ $exitcode != 0 ] && [ "$quiet" == 0 ];then
|
|
#show cursor
|
|
printf "\e[?25h"
|
|
|
|
echo -e "\n\e[91mFailed to download: $url\nPlease review errors above.\e[0m" 1>&2
|
|
fi
|
|
|
|
return $exitcode
|
|
}
|
|
|
|
chmod() { #say what is being made executable
|
|
status "Making executable: $2"
|
|
command chmod "$@"
|
|
return $?
|
|
}
|
|
|
|
unzip() { #say what is being extracted
|
|
#some scripts add a flag to the unzip command before specifying the file.
|
|
#This checks the first two arguments to display the file being extracted.
|
|
[ -f "$1" ] && status "Extracting: $1"
|
|
[ -f "$2" ] && status "Extracting: $2"
|
|
|
|
#The -o flag means to overwrite without prompting
|
|
command unzip -o "$@"
|
|
return $?
|
|
}
|
|
|
|
sudo_popup() { #just like sudo on passwordless systems like PiOS, but displays a password dialog otherwise. Avoids displaying a password prompt to an invisible terminal.
|
|
if sudo -n true; then
|
|
# sudo is available (within sudo timer) or passwordless
|
|
sudo "$@"
|
|
else
|
|
# sudo is not available (not within sudo timer)
|
|
pkexec "$@"
|
|
fi
|
|
}
|
|
|
|
nproc() { #Reduce the number of compile threads on low-RAM systems
|
|
#The Pi02 and Pi3A+ have 4 cores but 500MB of RAM. This function reduces the number of threads to use if RAM is low.
|
|
|
|
local free="$(free | grep 'Mem:' | awk '{print $4}')"
|
|
|
|
if [ "$free" -gt $((500*1024)) ] || [ "$GITHUB_ACTIONS" == "true" ];then
|
|
#Free memory >500MB, use normal number of threads
|
|
command nproc
|
|
else
|
|
#Free memory <500MB, use 1 thread
|
|
echo 1
|
|
warning "Your system has less than 500MB of free RAM, so this will compile with only 1 thread."
|
|
fi
|
|
}
|
|
#end of command interceptors
|
|
|
|
#Stop a running api function if user presses Ctrl+C, but avoid doing this if api is sourced in an interactive terminal.
|
|
if [[ $- != *i* ]] ;then
|
|
trap "exit 1" INT
|
|
cd $HOME
|
|
fi
|
|
|
|
add_english
|
|
|
|
#if this script is being run standalone, set the DIRECTORY variable based on the script's location
|
|
if [[ "$0" == */api ]];then
|
|
#it is necessary to set it now in order for yadflags and yad theme to work as intended.
|
|
DIRECTORY="$(readlink -f "$(dirname "$0")")"
|
|
export DIRECTORY
|
|
#if being sourced, ensure DIRECTORY variable is set
|
|
elif [ -z "$DIRECTORY" ] || [ "$DIRECTORY" == "$HOME" ] || [ ! -d "$DIRECTORY" ] || [ ! -f "${DIRECTORY}/api" ] || [ ! -f "${DIRECTORY}/gui" ];then
|
|
echo "DIRECTORY variable must be set to a valid Artian-apps folder. Default folder: $HOME/Artian-apps"
|
|
return 1
|
|
fi
|
|
|
|
#set the system GTK theme for yad windows
|
|
guimode="$(cat "${DIRECTORY}/data/settings/App List Style" 2>/dev/null || echo yad-default)"
|
|
if [ "$guimode" == yad-default ];then
|
|
export GTK_THEME=''
|
|
elif [[ "$guimode" = yad* ]];then
|
|
export GTK_THEME=${guimode//yad-/}
|
|
elif [ "$guimode" == xlunch-light-3d ];then
|
|
export GTK_THEME=''
|
|
elif [ "$guimode" == xlunch-dark-3d ];then
|
|
export GTK_THEME=Adwaita-dark
|
|
elif [ "$guimode" == xlunch-dark ];then
|
|
export GTK_THEME=Adwaita-dark
|
|
fi
|
|
|
|
#this array stores flags that are used in all yad windows - saves on the typing and makes it easy to change an attribute on all dialogs from one place.
|
|
yadflags=(--class Artian-Apps --name "Artian-Apps" --center --window-icon="${DIRECTORY}/icons/logo.png" --title="Artian-Apps" --separator='\n')
|
|
|
|
#determine if host system is 64 bit arm64 or 32 bit armhf
|
|
if [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 02' ];then
|
|
arch=64
|
|
elif [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 01' ];then
|
|
arch=32
|
|
else
|
|
error "Failed to detect OS CPU architecture! Something is very wrong."
|
|
fi
|
|
|
|
#Make dual-pane yad windows work correctly on wayland
|
|
export GDK_BACKEND=x11
|
|
|
|
# add upstream and original lsb-release info for all scripts to use
|
|
|
|
# _os_original_* variables are only set for downstream releases (eg: Pop!_OS, Linux Mint, and KDE Neon)
|
|
# this allows for scripts to simply check for the existance of _os_original_* variables and operate differently if found
|
|
# note: Pop!_OS, Linux Mint, and KDE Neon are NOT official Ubuntu flavors (like Kubuntu, Ubuntu MATE, Ubuntu Kylin, Ubuntu Budgie, Lubuntu, Ubuntu Studio, Ubuntu Unity, and Xubuntu)
|
|
# official Ubuntu flavors use ONLY the official Ubuntu repositories and are directly supported by Ubuntu. These other releases are simply "based" on Ubuntu, much like Raspbian and PiOS are "based" on Debian.
|
|
|
|
# first check if lsb_release has an upstream option -u
|
|
# if not, check if there is an upstream-release file
|
|
# if not, check if there is a lsb-release.diverted file
|
|
# if not, assume that this is not a ubuntu derivative
|
|
# use mapfile to temporarily store release info for speed
|
|
if [ -z "$__os_id" ] || [ -z "$__os_desc" ] || [ -z "$__os_release" ] || [ -z "$__os_codename" ]; then
|
|
if lsb_release -a -u &>/dev/null; then
|
|
# This is a Ubuntu Derivative, checking the upstream-release version info
|
|
mapfile -t os_u < <(lsb_release -s -i -d -r -c -u)
|
|
export __os_id="${os_u[0]}"
|
|
export __os_desc="${os_u[1]}"
|
|
export __os_release="${os_u[2]}"
|
|
export __os_codename="${os_u[3]}"
|
|
mapfile -t os < <(lsb_release -s -i -d -r -c)
|
|
export __os_original_id="${os[0]}"
|
|
export __os_original_desc="${os[1]}"
|
|
export __os_original_release="${os[2]}"
|
|
export __os_original_codename="${os[3]}"
|
|
elif [ -f /etc/upstream-release/lsb-release ]; then
|
|
# ubuntu 22.04+ Linux Mint no longer includes the lsb_release -u option
|
|
# add a parser for the /etc/upstream-release/lsb-release file
|
|
source /etc/upstream-release/lsb-release
|
|
export __os_id="$DISTRIB_ID"
|
|
export __os_desc="$DISTRIB_DESCRIPTION"
|
|
export __os_release="$DISTRIB_RELEASE"
|
|
export __os_codename="$DISTRIB_CODENAME"
|
|
mapfile -t os < <(lsb_release -s -i -d -r -c)
|
|
export __os_original_id="${os[0]}"
|
|
export __os_original_desc="${os[1]}"
|
|
export __os_original_release="${os[2]}"
|
|
export __os_original_codename="${os[3]}"
|
|
unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
|
|
elif [ -f /etc/lsb-release.diverted ]; then
|
|
# ubuntu 22.04+ Pop!_OS no longer includes the /etc/upstream-release/lsb-release or the lsb_release -u option
|
|
# add a parser for the new /etc/lsb-release.diverted file
|
|
source /etc/lsb-release.diverted
|
|
export __os_id="$DISTRIB_ID"
|
|
export __os_desc="$DISTRIB_DESCRIPTION"
|
|
export __os_release="$DISTRIB_RELEASE"
|
|
export __os_codename="$DISTRIB_CODENAME"
|
|
mapfile -t os < <(lsb_release -s -i -d -r -c)
|
|
export __os_original_id="${os[0]}"
|
|
export __os_original_desc="${os[1]}"
|
|
export __os_original_release="${os[2]}"
|
|
export __os_original_codename="${os[3]}"
|
|
unset DISTRIB_ID DISTRIB_DESCRIPTION DISTRIB_RELEASE DISTRIB_CODENAME
|
|
else
|
|
mapfile -t os < <(lsb_release -s -i -d -r -c)
|
|
export __os_id="${os[0]}"
|
|
export __os_desc="${os[1]}"
|
|
export __os_release="${os[2]}"
|
|
export __os_codename="${os[3]}"
|
|
fi
|
|
fi
|
|
|
|
# add /usr/local/bin to path if not currently present which is often not searched by default if /usr/local/bin did not exist on boot.
|
|
# this path will automatically be added on next boot if a binary is placed in it by a Artian-apps script
|
|
[[ "$PATH" != */usr/local/bin* ]] && export PATH="/usr/local/bin:$PATH"
|
|
|
|
#if this script is being run standalone, run the specified function
|
|
if [[ "$0" == */api ]];then
|
|
"$@"
|
|
exit $?
|
|
fi
|
|
|
|
export DIRECTORY
|