PublicFiles/Artian-Apps/api

3279 lines
160 KiB
Bash

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