#!/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) in ${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=""\""$(list_apps | grep -i -m1 "^$query")"\"" 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 "\""$query"\""." \ --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 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 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="${app^} failed to $action for an unknown reason." else local text="${app^} failed to $action because Artian-Apps encountered $([[ "$error_type" == [aeiou]* ]] && echo an || echo a) $error_type 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 Artian Store Forums." else text+=$'\n'"Support is available on Artian and Artian For send message and call." 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 $app 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: $file" \ --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