#!/bin/bash # ____ __ ____ # / __ \____ ___________/ /_____ _/ / / # / /_/ / __ `/ ___/ ___/ __/ __ `/ / / # / ____/ /_/ / /__(__ ) /_/ /_/ / / / # /_/ \__,_/\___/____/\__/\__,_/_/_/ # # Copyright (C) 2020-present # # This file is part of Pacstall # # Pacstall is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License # # Pacstall is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Pacstall. If not, see . # Pacstall version version_number="6.4.0" version_name="Coral" declare -A version_color=([r]="239" [g]="133" [b]="112") # Configuration export METADIR="/var/lib/pacstall/metadata" export LOGDIR="/var/log/pacstall/error_log" printf -v LOGFILE "${LOGDIR}/%(%F_%T)T.log" export LOGFILE export PACTMP="${PACSTALL_TMPDIR:-/tmp}" export PACDIR="${PACTMP}/pacstall" export SCRIPTDIR="/usr/share/pacstall" export STAGEDIR="/usr/src/pacstall" export TEXTDOMAIN="pacstall" export TEXTDOMAINDIR="/usr/share/locale" export PACSTALL_USER=$(logname 2> /dev/null || echo "${SUDO_USER:-${USER:-$(whoami)}}") # (( )) doesn't like uninitialized vars export PACSTALL_INSTALL=1 PACSTALL_DEBUG=0 if [[ "${1}" == "-x" || "${1}" == "--debug" ]]; then if [[ -n ${PACSTALL_XTRACELOG} && -n ${PACSTALL_XTRACEFD} ]]; then if ! [[ ${PACSTALL_XTRACEFD} =~ ^[0-9]+$ ]]; then { # avoid having to duplicate gettext string used in later checks printf '[\033[1;31m!\033[0m] \033[1mERROR\033[0m: '; printf $"'%s' must be an unsigned integer" 'PACSTALL_XTRACEFD'; # avoid having the newline in a gettext string printf '\n'; } >&2 exit 1 fi # Separate elevated log file because of permissions issues if [[ $PACSTALL_ELEVATED ]]; then eval "exec ${PACSTALL_XTRACEFD}>'${PACSTALL_XTRACELOG}.elevated'" else eval "exec ${PACSTALL_XTRACEFD}>'${PACSTALL_XTRACELOG}'" fi export BASH_XTRACEFD="${PACSTALL_XTRACEFD}" fi set -x PACSTALL_DEBUG=1 shift 1 else set +x fi export PACSTALL_DEBUG # declare verbose debug output declare -gx PS4=$'\E[0;10m\E[1m\033[1;31m\033[1;37m[\033[1;35m${BASH_SOURCE[0]##*/}:\033[1;34m${FUNCNAME[0]:-NOFUNC}():\033[1;33m${LINENO}\033[1;37m] - \033[1;33mDEBUG: \E[0;10m' function def_colors() { # Colors export BOLD='\033[1m' export NC='\033[0m' # Courtesy of https://stackoverflow.com/a/28938235/13449010 if [[ -z $NO_COLOR ]]; then # Regular Colors export BLACK='\033[0;30m' # Black export RED='\033[0;31m' # Red export GREEN='\033[0;32m' # Green export YELLOW='\033[0;33m' # Yellow export BLUE='\033[0;34m' # Blue export PURPLE='\033[0;35m' # Purple export CYAN='\033[0;36m' # Cyan export WHITE='\033[0;37m' # White # Bold export BBlack='\033[1;30m' # Black export BRed='\033[1;31m' # Red export BGreen='\033[1;32m' # Green export BYellow='\033[1;33m' # Yellow export BBlue='\033[1;34m' # Blue export BPurple='\033[1;35m' # Purple export BCyan='\033[1;36m' # Cyan export BWhite='\033[1;37m' # White # Underline export UBlack='\033[4;30m' # Black export URed='\033[4;31m' # Red export UGreen='\033[4;32m' # Green export UYellow='\033[4;33m' # Yellow export UBlue='\033[4;34m' # Blue export UPurple='\033[4;35m' # Purple export UCyan='\033[4;36m' # Cyan export UWhite='\033[4;37m' # White # Background export On_Black='\033[40m' # Black export On_Red='\033[41m' # Red export On_Green='\033[42m' # Green export On_Yellow='\033[43m' # Yellow export On_Blue='\033[44m' # Blue export On_Purple='\033[45m' # Purple export On_Cyan='\033[46m' # Cyan export On_White='\033[47m' # White # High Intensity export IBlack='\033[0;90m' # Black export IRed='\033[0;91m' # Red export IGreen='\033[0;92m' # Green export IYellow='\033[0;93m' # Yellow export IBlue='\033[0;94m' # Blue export IPurple='\033[0;95m' # Purple export ICyan='\033[0;96m' # Cyan export IWhite='\033[0;97m' # White # Bold High Intensity export BIBlack='\033[1;90m' # Black export BIRed='\033[1;91m' # Red export BIGreen='\033[1;92m' # Green export BIYellow='\033[1;93m' # Yellow export BIBlue='\033[1;94m' # Blue export BIPurple='\033[1;95m' # Purple export BICyan='\033[1;96m' # Cyan export BIWhite='\033[1;97m' # White # High Intensity backgrounds export On_IBlack='\033[0;100m' # Black export On_IRed='\033[0;101m' # Red export On_IGreen='\033[0;102m' # Green export On_IYellow='\033[0;103m' # Yellow export On_IBlue='\033[0;104m' # Blue export On_IPurple='\033[0;105m' # Purple export On_ICyan='\033[0;106m' # Cyan export On_IWhite='\033[0;107m' # White fi } def_colors # Pac-roll pac_text=" _________________________ < you just got Pac-rolled > -------------------------" pac_body=" ⠀⠀⠀⠀⣀⠀⡀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀ ⣞⠑⡄⢠⠎⠀⠋⠀⠀⣜⠴⠒⡀⠀⡜⠁⠱⡀ ⡇⠀⡈⠁⠀⠀⠀⠀⠀⠁⠀⠘⠀⠲⠅⠀⠀⡇ ⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⢆ ⠀⢠⠃⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀ ⠀⢘⡃⠣⡀⠀⢰⢄⣧⠀⠀⠀⠀⠀⡆⡀⢀⡤⠀⡽⠀⠀ ⠀⢺⠀⠀⠀⠀⢀⡀⠑⢦⠀⢀⢌⡼⠁⢀⣀⠀⠘⢢⠀⠀ ⠀⢜⠁⠀⠀⡔⣿⣯⢇⠀⢀⠤⠤⢀⠐⢙⣿⢧⠀⠀⡇⠀ ⠠⠇⠀⠀⠀⠑⢵⣿⠖⡇⠈⠂⡈⠁⡗⠦⠥⠊⠀⡸⠀⠀ ⠀⠸⡀⠀⠀⠀⠀⠀⠀⠱⡤⠔⠙⢲⠁⠀⠀⠀⠀⡇⠀⠀ ⠀⠀⢰⠀⠀⠀⠀⠀⠀⠀⠱⣒⢲⡏⠀⠀⠀⠀⠠⣱⠀⠀ ⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠁⠊⠀⠀⠀⠀⠀⠀⠃⠀⠀ ⠀ ⠼⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇ ⠀⠀⣿⢀⣠⠐⣰⢿⣯⡄⠀⠀⣠⡾⠁⣿⣿⣿⣶⣶⣶⣦⣤⣤⣀⣀⠀ ⣤⣴⣾⣿⣿⣰⣿⠛⡿⣇⣤⡾⠋⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀ ⠀⠀⢠⣶⣾⣿⣿⣿⣿⣿⣿⣿⣟⢀⡷⣮⣭⣤⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀ ⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣟⣛⣛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠈⣟⣚⣒⣒⣲⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⡿⠶⠾⠯⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⠀⠀⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⢿⣭⣿⣿⣟⣿⣿⣿⡏⠀⠉⠉⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷ ⠀⢠⣿⣿⣿⣿⣿⡿⢻⣿⣿⣿⡿⣻⣛⣛⣛⣛⢻⣿⣿⣿⣇⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⠀⠻⣿⣿⣿⣿⠅⠂⠰⣿⣿⣿⡇⣿⡿⢿⠯⠭⠭⣽⣿⣿⣿⣤⡀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄ ⠀⣠⣿⣿⡿⠃⠀⠀⠀⢹⣿⣿⡷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣰⣿⣿⣿⣣⣷⣄⠀⢠⣾⣿⣿⣇⣿⣿⣓⣒⣒⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ ⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⣿⣿⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⣿⣿⣿⣿⣿⣿⣿⡟ ⠈⣿⣿⣿⣽⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠉⠉⠉⠉⠉ ⠀⠈⠋⠛⠛⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⣏⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⡟⠉⠉⠉⠉⠉⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀" # fancy_message allows visually appealing output. # Source the code block and run: # # `fancy_message {info,warn,error,sub} "What you want to say"` function fancy_message() { local MESSAGE_TYPE="${1}" MESSAGE="${2}" FANCYTEXT shift 2 local PRINTARGS=("${@}") case ${MESSAGE_TYPE} in info) FANCYTEXT="[${BGreen}+${NC}] ${BOLD}INFO${NC}:" ;; warn) FANCYTEXT="[${BYellow}*${NC}] ${BOLD}WARNING${NC}:" ;; error) FANCYTEXT="[${BRed}!${NC}] ${BOLD}ERROR${NC}:" ;; sub) FANCYTEXT="\t[${BBlue}>${NC}]" ;; *) FANCYTEXT="[${BOLD}?${NC}] ${BOLD}UNKNOWN${NC}:" ;; esac case ${MESSAGE_TYPE} in info|sub) printf "${FANCYTEXT} ${MESSAGE}\n" "${PRINTARGS[@]}" ;; *) printf "${FANCYTEXT} ${MESSAGE}\n" "${PRINTARGS[@]}" >&2 ;; esac } function decl_scriptvars() { local _distros _vars _archs _sums distros \ vars="source depends makedepends optdepends pacdeps checkdepends provides conflicts breaks replaces enhances recommends suggests makeconflicts checkconflicts" \ archs="amd64 x86_64 arm64 aarch64 armel arm armhf armv7h i386 i686 ppc64el riscv64 s390x" \ sums="b2 sha512 sha384 sha256 sha224 sha1 md5" pacstallvars=(pkgbase pkgname repology pkgver git_pkgver epoch source_url source depends makedepends checkdepends conflicts breaks replaces gives pkgdesc hash optdepends ppa arch maintainer pacdeps NOBUILDDEP provides incompatible compatible srcdir url backup pkgrel mask pac_functions pac_func_arr repo priority noextract nosubmodules license bwrapenv safeenv external_connection enhances recommends suggests custom_fields makeconflicts checkconflicts bugs limit_kver) mapfile -t distros < <(awk -F ',' -v current_date="$(date +'%Y-%m-%d')" ' BEGIN { is_first = 1 } $3 == "series" { if (is_first) { print "ubuntu" is_first = 0 } else { print "devel\ndebian" } next } NR > 1 && (($6 > current_date) || ($7 > current_date) || ($6 == "")) && ($4 <= current_date) && $3 != "experimental" { print $3 }' /usr/share/distro-info/{ubuntu,debian}.csv) export PACSTALL_KNOWN_DISTROS=("${distros[@]}") eval "PACSTALL_KNOWN_ARCH=(${archs}) PACSTALL_KNOWN_SUMS=(${sums})" distros="${distros[*]}" _distros="{${distros// /,}}" _vars="{${vars// /,}}" _archs="{${archs// /,}}" _sums="{${sums// /,}}" eval "pacstallvars+=(${_vars}_${_distros} ${_vars}_${_archs} ${_vars}_${_distros}_${_archs} ${_sums}sums ${_sums}sums_${_distros} ${_sums}sums_${_archs} ${_sums}sums_${_distros}_${_archs} gives_${_distros} gives_${_archs} gives_${_distros}_${_archs})" } decl_scriptvars function cleanup() { if [[ -n ${PACDIR} ]]; then sudo rm -f "${PACDIR}-srcinfo-access-"* if [[ -n ${KEEP} && -n ${pacname} ]]; then sudo rm -rf "${PACDIR}-keep/${pacname}" mkdir -p "${PACDIR}-keep/${pacname}" #shellcheck disable=SC2153 sudo mv "${PACDIR}/${PACKAGE}.pacscript" "${PACDIR}-keep/${pacname}" sudo mv "${PACDIR}/${PACKAGE}.SRCINFO" "${PACDIR}-keep/${pacname}/.SRCINFO" sudo mv "${PACDIR}/${pacname}~${pkgver}" "${PACDIR}-keep/${pacname}" fi if [[ -n ${pacname} ]]; then if [[ -f "${PACDIR}-pacdeps-${pacname}" ]]; then sudo rm -rf "${PACDIR}-pacdeps-${pacname}" else sudo rm -rf "${PACDIR}/"* fi sudo rm -rf "${STAGEDIR:-/usr/src/pacstall}/${pacname}" for i in "deps" "gives" "missing-deps" "not-satisfied-deps" "suggested-optdeps" "missing-optdeps" "not-satisfied-optdeps" "already-installed-optdeps" "selectopts-optdeps" "needed-builddepends" "missing-builddepends" "unsatisfied-builddepends" "needed-checkdepends" "missing-checkdepends" "unsatisfied-checkdepends"; do if [[ -f "${PACDIR}-${i}-${pacname}" ]]; then sudo rm -rf "${PACDIR}-${i}-${pacname}" fi done fi [[ -n ${pkgbase} ]] && sudo rm -rf "${PACDIR}-selectopts-pkgbase-${pkgbase}" "${PACDIR}-no-download-${pkgbase}" # shellcheck disable=SC2153 [[ -n ${STAGEDIR} && -n ${PACKAGE} ]] && sudo rm -rf "${STAGEDIR}/${pacname:-$PACKAGE}"* [[ -n ${pacfile} ]] && sudo rm -f "${pacfile}" [[ -n ${srcinfile} ]] && sudo rm -f "${srcinfile}" [[ -n ${bwrapenv} ]] && sudo rm -f "${bwrapenv}" [[ -n ${safeenv} ]] && sudo rm -f "${safeenv}" fi local compg mapfile -t compg < <(compgen -v "srcinfo_") unset "${pacstallvars[@]}" "${compg[@]}" srcinfo_access globase global 2> /dev/null unset -f pre_install pre_upgrade pre_remove post_install post_upgrade post_remove prepare build check package 2> /dev/null } function stacktrace() { local catch=$? if ((catch==1)) && ! ${ignore_stack}; then local i stack_size=${#FUNCNAME[@]} func linen src trace content stack_color color_idx \ colors=(196 197 198 199 200 201 165 129 93 57 21 27 33 39 45 51 50 49 48 47 46 82 118 154 190 226 220 214 208 202) echo -e "[${BRed}!${NC}] ${BOLD}ERROR${NC}: Stacktrace (most recent call last)" >&2 for ((i = stack_size - 1; i >= 1; i--)); do color_idx=$(( (stack_size - 1 - i) % ${#colors[@]} )) stack_color="\033[38;5;${colors[color_idx]}m" ((i != stack_size - 1)) && func="${FUNCNAME[i - 1]}" [[ -z ${func} ]] && func='MAIN' [[ ${func} == "stacktrace" ]] && { unset func; trace="${RED}TRACEBACK${NC}"; } linen="${BASH_LINENO[i - 1]}" src="${BASH_SOURCE[i]}" [[ -z ${src} ]] && src=non_file_source echo -e " ${stack_color}${func:+├}${trace:+╰}─➤${GREEN}${func}${NC}${trace}${NC}${func:+()}${trace:+:} ${src%/*}/${PURPLE}${src##*/}${NC}:${YELLOW}${linen}${NC}" >&2 # shellcheck disable=SC2027 echo -e " ${stack_color}${func:+│}${trace:+ }${NC} ${CYAN}╰───➤${NC} \033[38;5;242m"$(tail -n +"${linen}" "${src}" | head -n1)"${NC}" >&2 done fancy_message info $"Cleaning up" cleanup exit 1 else export ignore_stack=false return "${catch}" fi } { export ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } # This is the ask function. You can source this code block and then run something like: # ask "Do you like the color blue? " Y # if ((answer == 1)); then # echo "You like blue" # else # echo "You don't like blue" # fi # # Y=1 and N=0 # You can specify {Y,N} or leave it out to prevent entering the default but this is not allowed in pacstall because of the -P flag which gives unattended install function ask() { # stacktrace can't handle whatever sorcery happens with 'echo N | pacstall' local prompt default reply template="${1}" shift 1 # shellcheck disable=SC2124 local yn="${@: -1}" local rest=("${@:1:$(($# - 1))}") if [[ ${yn} == 'Y' ]]; then prompt="${BIGreen}Y${NC}/${RED}n${NC}" default='Y' elif [[ ${yn} == 'N' ]]; then prompt="${GREEN}y${NC}/${BIRed}N${NC}" default='N' else prompt="${GREEN}y${NC}/${RED}n${NC}" fi # Ask the question (not using "read -p" as it uses stderr not stdout) printf "${template} [$prompt] " "${rest[@]}" if [[ ${DISABLE_PROMPTS:-z} == "z" ]]; then export DISABLE_PROMPTS="no" fi if [[ $DISABLE_PROMPTS == "no" ]]; then read -r reply <&0 # Detect if script is running non-interactively # Which implies that the input is being piped into the script if [[ $NON_INTERACTIVE ]]; then if [[ -z $reply ]]; then echo -n "$default" fi echo "$reply" fi else echo "$default" reply=$default fi # Default? if [[ -z $reply ]]; then reply=$default fi while :; do # Check if the reply is valid case "$reply" in Y* | y*) export answer=1 break ;; N* | n*) export answer=0 break ;; *) printf "${template} [$prompt] " "${rest[@]}" read -r reply < /dev/tty ;; esac done } # Used for providing possible solutions to errors # The following color codes are used for specific scenarios # UGreen: files/URLS # UCyan: commands to run # UPurple: text # Make sure to quote ('') commands and files/URLS function suggested_solution() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } if [[ -z $PACSTALL_SUPPRESS_SOLUTIONS ]]; then local inputs=("${@}") input text args first=true printf "%b %s:\n" "[${BOLD}${BPurple}⠿${NC}]" $"Suggested solution(s)" for input in "${inputs[@]}"; do if [[ "${input}" == "--" ]]; then if [[ -n "${text}" ]]; then printf "%b ${text}\n" " ${BOLD}|${NC}" "${args[@]}" fi first=true unset text args continue fi if ${first}; then text="${input}" first=false else args+=("${input}") fi done if [[ -n "${text}" ]]; then printf "%b ${text}\n" " ${BOLD}|${NC}" "${args[@]}" fi fi } function check_url() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } if [[ ${1} == "file://"* ]]; then if ! [[ -f ${1/"file://"/} ]]; then { ignore_stack=true; return 1; } fi else local http_code="$(curl --location -o /dev/null -s --head --write-out '%{http_code}\n' -- "${1}")" case "${http_code}" in 000) if [[ ${1} == *"packagelist" ]]; then fancy_message error $"Packagelist not found" suggested_solution $"Confirm that '%b' exists" "${UGreen}$1${NC}" else fancy_message error $"Failed to download file, check your connection" fi error_log 1 "get ${PACKAGE} pacscript" { ignore_stack=true; return 1; } ;; 404) fancy_message error $"The URL cannot be found" suggested_solution $"Confirm that '%b' exists" "${UGreen}$1${NC}" { ignore_stack=true; return 1; } ;; 200 | 301 | 302) return 0 ;; *) fancy_message error $"Failed with http code %s" "${http_code}" suggested_solution $"Confirm that '%b' is accessible" "${UGreen}$1${NC}" { ignore_stack=true; return 1; } ;; esac fi } # Checks if a command is available. # Plain "command -v" succeeds if the argument is in PATH, even if not executable. function has_command() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } if [[ -x $(command -v "$1" 2> /dev/null) ]]; then return 0 else { ignore_stack=true; return 1; } fi } # use axel if available function download() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local src="$1" out="${2:-${src##*/}}" if [[ $PACSTALL_DOWNLOADER != "payload" ]]; then sudo rm -f "${out:?}" fi if [[ -z $PACSTALL_DOWNLOADER || -f "${PACDIR}-pacdeps-$PACKAGE" ]]; then if has_command axel; then PACSTALL_DOWNLOADER=axel elif has_command wget; then PACSTALL_DOWNLOADER=wget else PACSTALL_DOWNLOADER=curl fi fi ${PACSTALL_VERBOSE} && [[ ${PACSTALL_DOWNLOADER} != "verbose-"* ]] && PACSTALL_DOWNLOADER="verbose-${PACSTALL_DOWNLOADER}" case "$PACSTALL_DOWNLOADER" in verbose-axel) axel -ao "$out" "$src" || { ignore_stack=true; return 1; } ;; axel) axel -a -q -o "$out" "$src" || { ignore_stack=true; return 1; } ;; verbose-curl) curl -L -# -o "$out" "$src" || { ignore_stack=true; return 1; } ;; curl) curl -sSL -o "$out" "$src" || { ignore_stack=true; return 1; } ;; verbose-wget) wget -q --show-progress --progress=bar:force -O "$out" -- "$src" 2>&1 || { ignore_stack=true; return 1; } ;; payload) ;; wget | *) wget -q -O "$out" -- "$src" 2>&1 || { ignore_stack=true; return 1; } ;; esac } # source this code block and run like so: # $ select_options "My message I want to send" "${#array[@]}" "message" # This will then output the options given by the user to ${PACDIR}-selectopts-${varname}-${pacname}, which you can then turn into another array function select_options() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local message="${1}" length="${2}" varname="${3}" optname="${pkgbase:-${pacname}}" rm -f "${PACDIR}-selectopts-${varname}-${optname}" if ((length >= 6)); then echo -ne "${message} [${BOLD}1-$length${NC} or ${BIGreen}Y${NC}] " else echo -ne "${message} [${BOLD}$(seq -s ' ' 1 "$length")${NC} or ${BIGreen}Y${NC}] " fi if [[ $DISABLE_PROMPTS == "no" ]]; then read -ra input <&0 if [[ $NON_INTERACTIVE ]]; then if [[ -z $input ]]; then echo "Y" fi echo "$input" fi else echo "Y" input="Y" fi if [[ -z $input ]] || [[ $input =~ ^[Yy]$ ]]; then seq -s ' ' 1 "$length" | tee "${PACDIR}-selectopts-${varname}-${optname}" > /dev/null elif ((input == 0)) || [[ $input =~ ^[Nn]$ ]]; then echo "n" | tee "${PACDIR}-selectopts-${varname}-${optname}" > /dev/null elif ! [[ $input =~ [a-zA-Z]+ ]] || [[ $input =~ ^[0-9]+$ ]]; then for i in "${input[@]}"; do unset split_arr local out split_arr if [[ $i =~ [0-9]+-[0-9]+ ]] || [[ $i =~ [0-9]+..[0-9]+ ]]; then case "$i" in *-*) for line in ${i//-/ }; do split_arr+=("$line") done if ((${split_arr[0]} > ${split_arr[-1]} || ${split_arr[0]} == ${split_arr[-1]})); then select_options "$message" "$length" "$varname"; fi ;; *..*) for line in ${i//../ }; do split_arr+=("$line") done if ((${split_arr[0]} > ${split_arr[-1]} || ${split_arr[0]} == ${split_arr[-1]})); then select_options "$message" "$length" "$varname"; fi ;; *) select_options "$message" "$length" "$varname" ;; esac out+=($(seq "${split_arr[0]}" "${split_arr[1]}")) continue else out+=("$i") fi done echo "${out[@]}" | tee "${PACDIR}-selectopts-${varname}-${optname}" > /dev/null else select_options "$message" "$length" "$varname" fi } # Return 0 if exists, 1 if not function is_package_installed() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local input="${1}" while read -r line; do if [[ ${line} == "${input}" ]]; then return 0 fi done < <(pacstall -L) { ignore_stack=true; return 1; } } # Returns 0 if exists, 1 if not function is_apt_package_installed() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local precheck="${1}" status pkg_split # break out into ([0]=name [1]=compare [2]=version) if constraints present case "${precheck}" in *"<="*) pkg_split=("${precheck%%<=*}" "le" "${precheck##*<=}") ;; *">="*) pkg_split=("${precheck%%>=*}" "ge" "${precheck##*>=}") ;; *"="*) pkg_split=("${precheck%%=*}" "eq" "${precheck##*=}") ;; *"<"*) pkg_split=("${precheck%%<*}" "lt" "${precheck##*<}") ;; *">"*) pkg_split=("${precheck%%>*}" "gt" "${precheck##*>}") ;; *) pkg_split=("${precheck}") ;; esac # record ([0]=installed [1]=version) if present mapfile -t status < <(dpkg-query -W --showformat='${db:Status-Status}\n${Version}' "${pkg_split[0]}" 2> /dev/null) if [[ ${status[0]} == "installed" ]]; then if [[ -n "${pkg_split[1]}" ]]; then if ! dpkg --compare-versions "${status[1]}" "${pkg_split[1]}" "${pkg_split[2]}"; then { ignore_stack=true; return 1; } fi fi return 0 else { ignore_stack=true; return 1; } fi } function is_array() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } if [[ ${!1@a} == *a* ]]; then return 0 else { ignore_stack=true; return 1; } fi } function is_function() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } if [[ $(type -t "${1}") == "function" ]]; then return 0 else { ignore_stack=true; return 1; } fi } function array.contains() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local check local -n arra="${1:?No array passed to array.contains}" local input="${2:?No input given to array.contains}" for check in "${arra[@]}"; do if [[ ${check} == "${input}" ]]; then return 0 fi done { ignore_stack=true; return 1; } } function getMasks() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local pkgs pkg local -n masks="${1}" mapfile -t pkgs < <(pacstall -L 2> /dev/null) if ((${#pkgs[@]} == 0)); then return 0 fi for pkg in "${pkgs[@]}"; do source "${METADIR}/${pkg}" if [[ -n ${_mask[*]} ]]; then masks+=("${_mask[@]}") fi unset _pacstall_depends _pacdeps _name _version _install_date _date _ppa _homepage _gives _remoterepo _remotebranch _mask _upgrade 2> /dev/null done } function getMasks_offending_pkg() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } local the_name="${1}" mapfile -t pkgs < <(pacstall -L) if ((${#pkgs[@]} == 0)); then return 0 fi for pkg in "${pkgs[@]}"; do source "${METADIR}/${pkg}" if [[ -n ${_mask[*]} ]]; then if array.contains _mask "${the_name}"; then echo "${pkg}" return 0 fi fi unset _pacstall_depends _pacdeps _name _version _install_date _date _ppa _homepage _gives _remoterepo _remotebranch _mask _upgrade 2> /dev/null done { ignore_stack=true; return 1; } } function apt_update() { # run sudo apt update if it's been more than a week eval "$(apt-config shell State Dir::State)" eval "$(apt-config shell List Dir::State::Lists)" [[ -z "$(find -H "/${State}/${List}" -maxdepth 0 -mtime -7)" ]] && sudo apt-get update -qq --allow-releaseinfo-change unset State List } # shellcheck source=./misc/scripts/error-log.sh source "$SCRIPTDIR/scripts/error-log.sh" # shellcheck source=./misc/scripts/bwrap.sh source "$SCRIPTDIR/scripts/bwrap.sh" if ! $(cd "${PACTMP}" 2> /dev/null); then error_log 1 "init main" fancy_message error $"Could not enter into %s" "${PACTMP}" exit 1 fi if [[ ! -t 0 ]]; then NON_INTERACTIVE=true fancy_message warn $"Reading input from pipe" fi # Separate grouped short options argument_list=() for i in "${@}"; do # Just add argument if doesn't start with exactly one hyphen. if ! [[ ${i} =~ ^-[^-] ]]; then # if argument is '-B', '-P', '-K' or '-Nc', add to beginning of argument list. if [[ ${i} == "--build" || ${i} == "--disable-prompts" || ${i} == "--keep" || ${i} == "--nocheck" || ${i} == "--quiet" || ${i} == "--nosandbox" ]]; then argument_list=("${i}" "${argument_list[@]}") else argument_list+=("${i}") fi continue fi # Add all arguments to the list of arguments. # We remove the '-' prefix as we'll add it later. # Arguments start with uppercase except 'h'. for j in $(sed -E 's/[[:upper:]]|h/ &/g' <<< "${i:1}"); do # If current string is 'B', 'P', 'K' or 'Nc', add to beginning of argument list. if [[ ${j} == "B" || ${j} == "P" || ${j} == "K" || ${j} == "Nc" || ${j} == "Q" || ${j} == "Ns" ]]; then argument_list=("-${j}" "${argument_list[@]}") else argument_list+=("-${j}") fi done unset j done # Remove duplicates declare -A arg_map=( ["--install"]="-I" ["--search"]="-S" ["--remove"]="-R" ["--download"]="-D" ["--mark"]="-M" ["--add-repo"]="-A" ["--remove-repo"]="-Rr" ["--update"]="-U" ["--list"]="-L" ["--list-upgrades"]="-Lu" ["--upgrade"]="-Up" ["--search-description"]="-Sd" ["--search-info"]="-Si" ["--cache-info"]="-Ci" ["--quality-assurance"]="-Qa" ["--tree"]="-T" ["--version"]="-V" ["--help"]="-h" ["--build"]="-B" ["--keep"]="-K" ["--disable-prompts"]="-P" ["--nocheck"]="-Nc" ["--quiet"]="-Q" ["--nosandbox"]="-Ns" ) declare -A hashed_arguments unique_argument_list=() for arg in "${argument_list[@]}"; do mapped="${arg_map[$arg]:=$arg}" if ! [[ -v "hashed_arguments[$mapped]" ]]; then unique_argument_list+=($arg) hashed_arguments[$mapped]=0 fi done # Check if only one command flag is being used short_commands=("-I" "-S" "-R" "-D" "-A" "-Rr" "-U" "-L" "-Lu" "-Up" "-Qa" "-Sd" "-Si" "-Ci" "-M" "-T" "-V" "-h") long_commands=("--install" "--search" "--remove" "--download" "--add-repo" "--remove-repo" "--update" "--list" "--list-upgrades" "--upgrade" "--quality-assurance" "--search-description" "--search-info" "--cache-info" "--mark" "--tree" "--version" "--help") commands=("${short_commands[@]}" "${long_commands[@]}") mapfile -t matches < <( comm -12 --nocheck-order \ <(printf "%s\n" "${unique_argument_list[@]}" | sort) \ <(printf "%s\n" "${commands[@]}" | sort) ) if ((${#matches[@]} <= 1)); then # Set the new list of arguments set -- "${unique_argument_list[@]}" else fancy_message warn $"Only one command flag can be used at a time" set -- "-h" fi unset short_commands long_commands commands matches arguments_list unique_arguments_list hashed_arguments function lock() { { ignore_stack=false; set -o pipefail; trap stacktrace ERR RETURN; } # Total number of options flags, increase when needed local option_flags=5 local ignore_short="-S -D -A -Rr -V -L -T -Sd -Si -Ci -h" local ignore_long="--search --download --add-repo --remove-repo --version --tree --search-description --search-info --cache-info --help" local ignore="$ignore_short $ignore_long" ((EUID != 0)) && { ignore_stack=true; return 1; } for ((i = 1; i <= option_flags + 1; i++)); do if [[ $ignore =~ (^|[[:space:]])"${!i}"($|[[:space:]]) ]]; then { ignore_stack=true; return 1; } fi j=$((i + 1)) if [[ -f "${PACDIR}-pacdeps-${!j%%@*}" ]]; then { ignore_stack=true; return 1; } fi done if [[ -f "$SCRIPTDIR/repo/pacstallrepo.pacstall-qa.bak" ]] || [[ $PACSTALL_ELEVATED ]]; then valid=2 [[ -f "$SCRIPTDIR/repo/pacstallrepo.pacstall-qa.bak" ]] && [[ $PACSTALL_ELEVATED ]] && valid=3 instances=($(pidof -o %PPID -x "$0")) if [[ ${#instances[@]} -lt $valid ]]; then { ignore_stack=true; return 1; } fi return 0 fi pidof -o %PPID -x "$0" > /dev/null && return 0 || { ignore_stack=true; return 1; } } while lock $@; do if [[ -z $first ]]; then first=1 fancy_message warn $"Pacstall is already running another instance" fi sleep 1 done if [[ -n $first ]]; then unset first fancy_message info $"The other instance has finished running, unlocking" fi # don't allow nosandbox to be run with envars unset NOSANDBOX function elevate() { if ((EUID != 0)); then ((PACSTALL_DEBUG == 0)) || debug="-x" [[ $KEEP ]] && keep="-K" ((PACSTALL_INSTALL == 1)) || build="-B" [[ $NOCHECK ]] && nocheck="-Nc" ${PACSTALL_VERBOSE} || quiet="-Q" [[ $NOSANDBOX ]] && nosandbox="-Ns" sudo PACSTALL_SUPPRESS_SOLUTIONS="$PACSTALL_SUPPRESS_SOLUTIONS" \ PACSTALL_BUILD_CORES="$PACSTALL_BUILD_CORES" PACSTALL_EDITOR="$PACSTALL_EDITOR" \ PACSTALL_DOWNLOADER="$PACSTALL_DOWNLOADER" PACSTALL_PAYLOAD="$PACSTALL_PAYLOAD" \ PACSTALL_TMPDIR="$PACSTALL_TMPDIR" NO_COLOR="$NO_COLOR" SUDO_USER="${SUDO_USER}" \ DISABLE_PROMPTS="$DISABLE_PROMPTS" PACSTALL_ELEVATED=true \ PACSTALL_XTRACELOG="${PACSTALL_XTRACELOG}" PACSTALL_XTRACEFD="${PACSTALL_XTRACEFD}" \ pacstall $debug $keep $build $nocheck $quiet $nosandbox $@ exit_code=$? if [[ -n ${debug} && -n ${PACSTALL_XTRACELOG} && -n ${PACSTALL_XTRACEFD} ]]; then set +x tail -n +1 -v "${PACSTALL_XTRACELOG}.elevated" >> "${PACSTALL_XTRACELOG}" \ && sudo rm -rf "${PACSTALL_XTRACELOG}.elevated" fi exit ${exit_code} fi } while [[ $1 != "--" ]]; do case "$1" in -P | --disable-prompts) [[ $PACSTALL_ELEVATED ]] || fancy_message warn $"Prompts are disabled" export DISABLE_PROMPTS=yes export DEBIAN_FRONTEND=noninteractive ;; -B | --build-only) export PACDEB_DIR="$PWD" [[ $PACSTALL_ELEVATED ]] || fancy_message info $"Package will be built and not installed" export PACSTALL_INSTALL=0 ;; -h | --help) echo -e "Usage: pacstall [-x] [-h] {-I,-S,-R,-D,-A,-Rr,-U,-L,-Lu,-Up,-Qa,-Sd,-Si,-Ci,-M,-T,-V} [-P] [-K] [-B] [-Nc] [-Q] [-Ns] An AUR inspired package manager for Ubuntu. Commands: ${BOLD}-I${NC}, ${BOLD}--install${NC} Install a package. ${BOLD}-S${NC}, ${BOLD}--search${NC} Search for a package. ${BOLD}-R${NC}, ${BOLD}--remove${NC} Remove a package. ${BOLD}-D${NC}, ${BOLD}--download${NC} Download a pacscript. ${BOLD}-A${NC}, ${BOLD}--add-repo${NC} Add a repository. ${BOLD}-Rr${NC}, ${BOLD}--remove-repo${NC} Remove a repository. ${BOLD}-U${NC}, ${BOLD}--update${NC} [user] [branch] Update Pacstall. ${BOLD}-L${NC}, ${BOLD}--list${NC} List all installed packages. ${BOLD}-Lu${NC}, ${BOLD}--list-upgrades${NC} List upgrades for installed packages. ${BOLD}-Up${NC}, ${BOLD}--upgrade${NC} Upgrade all installed packages. ${BOLD}-Qa${NC}, ${BOLD}--quality-assurance${NC} # Test a package PR from downstream. Optional: @[provider]:[owner]/[repo] ${BOLD}-Sd${NC}, ${BOLD}--search-description${NC} Search for a package or description keyword. ${BOLD}-Si${NC}, ${BOLD}--search-info${NC} Query information about a remote package. ${BOLD}-Ci${NC}, ${BOLD}--cache-info${NC} Query information about an installed package. ${BOLD}-M${NC}, ${BOLD}--mark${NC} {hold|unhold} Toggle upgrade checking of an installed package. ${BOLD}-T${NC}, ${BOLD}--tree${NC} Display a tree graph of an installed package. ${BOLD}-V${NC}, ${BOLD}--version${NC} Display the version number. ${BOLD}-h${NC}, ${BOLD}--help${NC} Display this help message. Options: ${BOLD}-x${NC}, ${BOLD}--debug${NC} Enable debug output. Must be first argument. ${BOLD}-P${NC}, ${BOLD}--disable-prompts${NC} Disable prompts. ${BOLD}-K${NC}, ${BOLD}--keep${NC} Keep the build files. ${BOLD}-B${NC}, ${BOLD}--build-only${NC} Build the deb but do not install. ${BOLD}-Nc${NC}, ${BOLD}--nocheck${NC} Skip the check() function if present. ${BOLD}-Q${NC}, ${BOLD}--quiet${NC} Download package entries quietly. ${BOLD}-Ns${NC}, ${BOLD}--nosandbox${NC} Build the package without bwrap. Helpful links: ${BOLD}https://github.com/pacstall/pacstall${NC} Official Pacstall GitHub page. ${BOLD}https://github.com/pacstall/pacstall-programs/issues${NC} If you find a broken package, create an issue here. ${BOLD}https://github.com/pacstall/pacstall/releases/latest${NC} Link to the latest release of Pacstall." exit 0 ;; -I | --install) elevate $@ apt_update unset CHILD PKGPATH if [[ -z $2 ]]; then fancy_message error $"You failed to specify a package" exit 1 fi function trap_ctrlc() { fancy_message warn $"The installation of %s was interrupted, removing files" "${PACKAGE:-package}" rm -rf "${PACDIR:?}"/* # :? makes bash error out in case PACDIR is empty, saving us from yoinking /* directory by mistake exit 2 } # Begin trapping trap "trap_ctrlc" 2 while [[ -n $2 ]]; do if [[ $2 == "pac" ]]; then echo -ne "$pac_text" echo -e "$pac_body" echo -e "~ Rick Pacsley ~" exit 0 else unset pac_body pac_text fi if [[ ${2##*.} == "pacscript" || ${2##*.} == "pacscript:"* ]]; then if [[ ${2##*.} =~ ^pacscript(:([a-zA-Z0-9_.-]+))$ ]]; then export CHILD="${BASH_REMATCH[2]/\:/}" PACKAGE="${2%.pacscript:${CHILD}}" else PACKAGE="${2%.pacscript}" fi export PACKAGE="${PACKAGE##*/}" export type="install" export local="yes" PKGPATH="${2%/*}" # Check if we need to cd into the directory first if [[ $PKGPATH == "." && ! -f "$PACKAGE".pacscript && -d $PACKAGE ]]; then cd "$PACKAGE" elif [[ -d $PKGPATH ]]; then cd "$PKGPATH" fi export PKGPATH="${PWD}" # Check if the file exist if [[ ! -f "$PACKAGE".pacscript ]]; then fancy_message error $"%s does not exist" "$2" shift continue fi else export type="install" export local="no" if [[ $2 =~ ^([a-zA-Z0-9_.-]+)(:([a-zA-Z0-9_.-]+))(@(.+))$ ]]; then PACKAGE="${BASH_REMATCH[1]}:pkgbase" if [[ ${BASH_REMATCH[2]} == ":"* ]]; then CHILD="${BASH_REMATCH[2]/\:/}" else CHILD="${BASH_REMATCH[3]}" fi if [[ ${BASH_REMATCH[3]} =~ '@' ]]; then PACKAGE+="${BASH_REMATCH[3]}" elif [[ -n ${BASH_REMATCH[5]} ]]; then PACKAGE+="@${BASH_REMATCH[5]}" fi elif [[ $2 =~ ^([a-zA-Z0-9_.-]+)(@(.+))$ ]]; then PACKAGE="${2}" elif [[ ${2} =~ ':' ]]; then PACKAGE="${2%:*}:pkgbase" CHILD="${2#*:}" else PACKAGE="${2}" fi [[ ${CHILD} == "pkgbase" ]] && unset CHILD export PACKAGE CHILD if [[ -z $PACKAGE ]]; then fancy_message error $"You failed to specify a package" exit 1 fi if [[ -n $PACSTALL_PAYLOAD && ! -f "${PACDIR}-pacdeps-${PACKAGE/:pkgbase/}" ]]; then export PACSTALL_DOWNLOADER="payload" if [[ ! -f $PACSTALL_PAYLOAD ]]; then fancy_message error $"Payload not found" exit 1 fi fi # Make the directory if not exist if [[ ! -e "$SCRIPTDIR/repo/" ]]; then sudo mkdir -p "$SCRIPTDIR/repo" sudo touch "$SCRIPTDIR/repo/pacstallrepo" pacstall -A fi check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || { fancy_message error $"Could not connect to the internet" suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}" exit 1 } # shellcheck source=./misc/scripts/search.sh if ! source "$SCRIPTDIR/scripts/search.sh"; then continue fi export PACKAGE="${PACKAGE/:pkgbase/}" repo.specify "$REPO" # sanity write export REPO="${REPO}" export URL="${REPO}/packages/$PACKAGE/$PACKAGE.pacscript" # shellcheck source=./misc/scripts/get-pacscript.sh if ! source "$SCRIPTDIR/scripts/get-pacscript.sh"; then fancy_message error $"Failed to download the %b pacscript" "$GREEN$PACKAGE$NC" suggested_solution $"Confirm that the package exists by running '%b'" "${UCyan}pacstall -S $PACKAGE${NC}" -- $"Check your internet connection" continue fi fi # shellcheck source=./misc/scripts/package-base.sh if ! source "$SCRIPTDIR/scripts/package-base.sh"; then fancy_message error $"Failed to install %b" "$GREEN$PACKAGE$NC" if ! [[ -f "${PACDIR}-pacdeps-$PACKAGE" ]]; then sudo rm -rf "${PACDIR:?}" fi exit 1 fi shift done exit 0 ;; -S | --search | -Sd | --search-description) if [[ ${1} == "-Sd" || ${1} == --search-description ]]; then DESCON=true fi export SEARCH=$2 if [[ -z $SEARCH ]]; then fancy_message error $"You failed to specify a package" exit 1 fi check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || { fancy_message error $"Could not connect to the internet" suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}" exit 1 } # shellcheck source=./misc/scripts/search.sh source "$SCRIPTDIR/scripts/search.sh" exit 0 ;; -Si | --search-info) unset CHILD INFOQUERY="${2}" if [[ $2 =~ ^([a-zA-Z0-9_.-]+)(:([a-zA-Z0-9_.-]+))(@(.+))$ ]]; then PACKAGE="${BASH_REMATCH[1]}:pkgbase" if [[ ${BASH_REMATCH[2]} == ":"* ]]; then CHILD="${BASH_REMATCH[2]/\:/}" else CHILD="${BASH_REMATCH[3]}" fi if [[ ${BASH_REMATCH[3]} =~ '@' ]]; then PACKAGE+="${BASH_REMATCH[3]}" elif [[ -n ${BASH_REMATCH[5]} ]]; then PACKAGE+="@${BASH_REMATCH[5]}" fi elif [[ $2 =~ ^([a-zA-Z0-9_.-]+)(@(.+))$ ]]; then PACKAGE="${2}" elif [[ ${2} =~ ':' ]]; then PACKAGE="${2%:*}:pkgbase" CHILD="${2#*:}" else PACKAGE="${2}" fi [[ ${CHILD} == "pkgbase" ]] && unset CHILD export PACKAGE CHILD if [[ -z $PACKAGE ]]; then fancy_message error $"You failed to specify a package" exit 1 fi check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || { fancy_message error $"Could not connect to the internet" suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}" exit 1 } SEARCHINFO=true # shellcheck source=./misc/scripts/search.sh source "$SCRIPTDIR/scripts/search.sh" exit 0 ;; -R | --remove) elevate $@ if [[ -z $2 ]]; then fancy_message error $"You failed to specify a package" exit 1 fi while [[ -n $2 ]]; do PACKAGE=$2 shift # shellcheck source=./misc/scripts/remove.sh if ! source "$SCRIPTDIR/scripts/remove.sh"; then fancy_message error $"Failed to remove %b" "$GREEN$PACKAGE$NC" fi done exit 0 ;; -A | --add-repo) elevate $@ REPO="${2%/}" ALIAS="${3#*@}" REPOCMD="add" if [[ ! -f "$SCRIPTDIR/repo/pacstallrepo" ]]; then echo 'https://raw.githubusercontent.com/pacstall/pacstall-programs/master' | sudo tee "$SCRIPTDIR/repo/pacstallrepo" > /dev/null return 0 fi if [[ -n $REPO ]]; then # shellcheck source=./misc/scripts/add-repo.sh source "$SCRIPTDIR/scripts/add-repo.sh" exit 0 else fancy_message error $"You failed to specify a repo to add" suggested_solution $"Add a repository in the following format:" -- "'${UCyan}pacstall -A https://github.com/username/repository-name${NC}'" -- $"Consult the pacstall man page by running '%b', and searching for the term '%b'" "${UCyan}man pacstall${NC}" "${UPurple}-A${NC}" exit 1 fi ;; -Rr | --remove-repo) elevate $@ REPO="${2%/}" ALIAS="${3#*@}" REPOCMD="remove" if [[ ! -f "$SCRIPTDIR/repo/pacstallrepo" ]]; then echo 'https://raw.githubusercontent.com/pacstall/pacstall-programs/master' | sudo tee "$SCRIPTDIR/repo/pacstallrepo" > /dev/null return 0 fi if [[ -n $REPO ]]; then # shellcheck source=./misc/scripts/add-repo.sh source "$SCRIPTDIR/scripts/add-repo.sh" exit 0 else fancy_message error $"You failed to specify a repo to remove" suggested_solution $"Remove a repository in the following format:" -- "'${UCyan}pacstall -A https://github.com/username/repository-name${NC}'" -- $"Consult the pacstall man page by running '%b', and searching for the term '%b'" "${UCyan}man pacstall${NC}" "${UPurple}-Rr${NC}" exit 1 fi ;; -V | --version) if [[ -f "$SCRIPTDIR/repo/update" ]]; then remote="$(< "$SCRIPTDIR/repo/update")" USERNAME="${remote%% *}" BRANCH="${remote##* }" if [[ $USERNAME != "pacstall" || $BRANCH != "master" ]]; then version_develop="$USERNAME:$BRANCH" fi fi echo -e "${version_number} \033[1m\x1b[38;2;${version_color[r]:-255};${version_color[g]:-255};${version_color[b]:-255}m${version_name}${NC} ${version_develop}" exit 0 ;; -U | --update) elevate $@ if ! is_apt_package_installed "pacstall"; then # If the input is `.`, is a directory, and there is no other input if [[ $2 == "." && -d $2 && -z $3 ]]; then cd "${2}" if ! git -C . rev-parse 2> /dev/null; then fancy_message error $"%s is not a git repository" "$PWD" exit 1 fi mapfile -d":" GIT_USER < <(git remote get-url origin) USERNAME="${GIT_USER[1]%%/*}" BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)" else USERNAME="$2" BRANCH="$3" fi # This stuff gives the ability for persistent updates if [[ -z $BRANCH && -z $USERNAME ]]; then if [[ -f "$SCRIPTDIR/repo/update" ]]; then remote="$(< "$SCRIPTDIR/repo/update")" USERNAME="${remote%% *}" BRANCH="${remote##* }" else USERNAME="pacstall" BRANCH="master" fi fi if [[ -z $BRANCH ]]; then if [[ $USERNAME == *":"* ]]; then BRANCH="${USERNAME#*:}" USERNAME="${USERNAME%:*}" else BRANCH="master" fi fi check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || { fancy_message error $"Could not connect to the internet" suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}" exit 1 } if curl --output /dev/null --silent --head --fail https://raw.githubusercontent.com/"$USERNAME"/pacstall/"$BRANCH"/pyproject.toml || curl --output /dev/null --silent --head --fail https://raw.githubusercontent.com/"$USERNAME"/pacstall/"$BRANCH"/Cargo.toml; then fancy_message error $"The repository you are trying to upgrade to contains a version of pacstall that cannot be updated from %b" "$(pacstall -V)" fancy_message error $"Visit %s for more information on how to reinstall. Most packages will also need to be reinstalled" "https://github.com/$USERNAME/pacstall/tree/$BRANCH" exit 1 fi sudo wget -q -N https://raw.githubusercontent.com/"$USERNAME"/pacstall/"$BRANCH"/misc/scripts/update.sh -P "$SCRIPTDIR/scripts" 2> /dev/null # shellcheck source=./misc/scripts/update.sh source "$SCRIPTDIR/scripts/update.sh" exit 0 else fancy_message error $"Pacstall was installed from a %s, and cannot be updated with this command" ".deb" suggested_solution $"Install the latest %b from the %b repository" ".deb" "PPR" -- $"Install the latest %b with the command '%b'" "pacscript" "${GREEN}pacstall -I pacstall${NC}" exit 1 fi ;; -L | --list) shift if [[ -n $* ]]; then fancy_message error $"Invalid argument '%s'" "$*" exit 1 fi if [[ ! -d ${METADIR} ]]; then exit 1 fi cd "${METADIR}" || exit 1 shopt -s nullglob packages_installed=(*) if ((${#packages_installed[@]} == 0)) && [[ -t 1 ]]; then fancy_message error $"Nothing installed yet" exit 1 elif ((${#packages_installed[@]} == 0)); then exit 1 fi if [[ -t 1 ]]; then for pkg in "${packages_installed[@]}"; do unset _pacstall_depends _pacdeps _name _version _install_date _date _ppa _homepage _gives _remoterepo _remotebranch _maintainer _mask _upgrade 2> /dev/null source ./"${pkg}" echo -e "${BYellow}~${NC} ${BGreen}${_name}${BPurple}@${BCyan}${_version}${NC}" if [[ -n ${_gives} ]]; then echo -e " ${BBlue}gives${NC} : ${BOLD}${_gives}${NC}" fi echo -e " ${BBlue}maintainer${NC} : ${BOLD}${_maintainer:-$(dpkg-query '--showformat=${Maintainer}\n' --show "${_gives:-$_name}")}${NC}" echo -e " ${BBlue}size${NC} : ${BOLD}${_install_size:-unknown}${NC}" echo -e " ${BBlue}repository${NC} : ${BOLD}${_remoterepo:-orphan}${NC}" echo -e " ${BBlue}install date${NC} : ${BOLD}${_date:-unknown}${NC}" if [[ ${pkg} != "${packages_installed[-1]}" ]]; then echo fi done else printf '%s\n' "${packages_installed[@]}" fi exit 0 ;; -D | --download) if [[ -z $2 ]]; then fancy_message error $"You failed to specify a package" exit 1 fi while [[ -n $2 ]]; do PACKAGE=$2 shift export type="download" check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || { fancy_message error $"Could not connect to the internet" suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}" exit 1 } # shellcheck source=./misc/scripts/search.sh if ! source "$SCRIPTDIR/scripts/search.sh"; then continue fi repo.specify "$REPO" export URL="$REPO/packages/${PACKAGE%:*}/${PACKAGE%:*}.pacscript" # shellcheck source=./misc/scripts/get-pacscript.sh if ! source "$SCRIPTDIR/scripts/get-pacscript.sh"; then fancy_message error $"Failed to download the %b pacscript" "$GREEN$PACKAGE$NC" suggested_solution $"Confirm that the package exists by running '%b'" "${UCyan}pacstall -S $PACKAGE${NC}" -- $"Check your internet connection" continue fi fancy_message info $"%b pacscript is downloaded to %b" "$GREEN$PACKAGE$NC" "$GREEN$PWD/${PACKAGE%:*}.pacscript$NC" done exit 0 ;; -Up | --upgrade | -Lu | --list-upgrades) elevate $@ [[ ${1} == "-Lu" || ${1} == "--list-upgrades" ]] && LIST_ONLY=true [[ $LIST_ONLY ]] || apt_update shift if [[ -n $* ]]; then fancy_message error $"Invalid argument '%s'" "$*" exit 1 fi check_url "https://github.com" 2> /dev/null || check_url "https://gitlab.com" 2> /dev/null || { fancy_message error $"Could not connect to the internet" suggested_solution $"Confirm that the URLs '%b' or '%b' are accessible" "${UGreen}https://github.com${NC}" "${UGreen}https://gitlab.com${NC}" exit 1 } # shellcheck source=./misc/scripts/upgrade.sh source "$SCRIPTDIR/scripts/upgrade.sh" exit 0 ;; -T | --tree) PACKAGE="$2" if [[ -z $PACKAGE ]]; then fancy_message error $"You failed to specify a package" exit 1 fi if ! [[ -f $METADIR/$PACKAGE ]]; then fancy_message error $"Package is not installed" exit 1 fi source "$METADIR/$PACKAGE" dpkg --listfiles "${_gives:-$_name}" | sort | sed -e "s/[^-][^\/]*\// │/g" -e "s/│\([^ ]\)/│──\1/" exit 0 ;; -Ci | --cache-info) PACKAGE=$2 QUERY=$3 # shellcheck source=./misc/scripts/query-info.sh source "$SCRIPTDIR/scripts/query-info.sh" exit 0 ;; -M | --mark) elevate $@ PACKAGE=$2 STATE=$3 if [[ -z $PACKAGE ]]; then fancy_message error $"You failed to specify a package" exit 1 fi if [[ ! -f "$METADIR/$PACKAGE" ]]; then fancy_message error $"Package is not installed" exit 1 fi case "${STATE}" in hold) sudo sed -i '/_upgrade=/d' "$METADIR/$PACKAGE" echo "_upgrade=false" | sudo tee -a "$METADIR/$PACKAGE" > /dev/null ;; unhold) sudo sed -i '/_upgrade=/d' "$METADIR/$PACKAGE" ;; *) fancy_message error $"'%b' is not a valid option, use '%b' or '%b'" "${STATE}" "hold" "unhold" exit 1 ;; esac exit 0 ;; -Qa | --quality-assurance) if [[ $2 =~ ^([a-zA-Z0-9_.-]+)?(@(.+))?(#([0-9]+))$ ]]; then PACKAGE="${BASH_REMATCH[1]}" METAURL="${BASH_REMATCH[3]}" PRNUM="${BASH_REMATCH[5]}" elif [[ $2 =~ ^([a-zA-Z0-9_.-]+)(#([0-9]+))?(@(.+))?$ ]]; then PACKAGE="${BASH_REMATCH[1]}" PRNUM="${BASH_REMATCH[3]}" METAURL="${BASH_REMATCH[5]}" fi # shellcheck source=./misc/scripts/quality-assurance.sh source "$SCRIPTDIR/scripts/quality-assurance.sh" exit 0 ;; -K | --keep) [[ $PACSTALL_ELEVATED ]] || fancy_message info $"Keeping build files" KEEP=true ;; -Nc | --nocheck) [[ $PACSTALL_ELEVATED ]] || fancy_message info $"Skipping check() if present" NOCHECK=true ;; -Q | --quiet) [[ $PACSTALL_ELEVATED ]] || fancy_message info $"Downloading package entries quietly" PACSTALL_VERBOSE=false ;; -Ns | --nosandbox) [[ $PACSTALL_ELEVATED ]] || fancy_message warn $"Building package without bwrap sandbox" [[ $PACSTALL_ELEVATED ]] || fancy_message sub $"Be cautious, this exposes your system to potential harm" NOSANDBOX=true ;; *) pacstall -h exit 3 ;; esac shift done if [[ $1 == '--' ]]; then shift; fi # vim:set ft=sh ts=4 sw=4 et: