#!/bin/bash # shellcheck source=kz-common.sh ############################################################################### # Apps installeren. # # Geschreven door Karel Zimmer . ############################################################################### PROGRAM_PATH=$(realpath "$(dirname "$0")") readonly PROGRAM_PATH source "$PROGRAM_PATH"/kz-common.sh readonly PROGRAM_NAME='kz-install' readonly DISPLAY_NAME=${PROGRAM_NAME/kz-/kz } readonly RELEASE_YEAR=2009 ############################################################################### # Global constants ############################################################################### readonly OPTIONS_SHORT=$OPTIONS_SHORT_COMMON'ace:fglrs' readonly OPTIONS_LONG=$OPTIONS_LONG_COMMON",apps,cat,edition:,files,gui,list,\ remove,simulate" readonly USAGE="Gebruik: $DISPLAY_NAME [-a|--apps] [-c|--cat] \ [-e|--edition=EDITIE] [-f|--files] [-g|--gui] [-l|--list] [-r|--remove] [-s|--simulate] $OPTIONS_USAGE_COMMON [APP...] [BESTAND...]" readonly HELP="Gebruik: $DISPLAY_NAME [OPTIE...] [APP...] [BESTAND...] Apps installeren. Opties: -a, --apps toon lijst van beschikbare apps -c, --cat toon inhoud van bestanden -e, --edition=EDITIE gebruik opgegeven EDITIE -f, --files toon lijst van bestanden -g, --gui start in grafische modus -l, --list maak applijst aan -r, --remove verwijder apps -s, --simulate toon opdrachten $OPTIONS_HELP_COMMON Argumenten: APP opgegeven APPs installeren BESTAND opgegeven BESTANDen verwerken" DISTRO=$(lsb_release --id --short | tr '[:upper:]' '[:lower:]') readonly DISTRO readonly EDITION_DEFAULT=desktop VERSION=$(lsb_release --release --short) readonly VERSION APPS_FILE=$(mktemp -t "$PROGRAM_NAME-XXXXXXXXXX.apps") readonly APPS_FILE CMDS_FILE=$(mktemp -t "$PROGRAM_NAME-XXXXXXXXXX.cmds") readonly CMDS_FILE SIM_FILE=$(mktemp -t "$PROGRAM_NAME-XXXXXXXXXX.sim") readonly SIM_FILE ############################################################################### # Global variables ############################################################################### declare -a APP_ARGUMENTS=() declare ARGUMENT_APP=false declare ARGUMENT_FILE=false declare COMMANDS_FOUND=false declare EDITION=$EDITION_DEFAULT declare EXECUTE_COMMANDS=true declare -a FILE_ARGUMENTS=() declare -a FILES_TO_PROCESS=() declare -i MAXRC=0 declare OPTION_APPS=false declare OPTION_CAT=false declare OPTION_FILES=false declare OPTION_GUI=false declare OPTION_LIST=false declare OPTION_REMOVE=false declare OPTION_SIMULATE=false declare RUN_AS_SUPERUSER=true declare STATUS_BUSY='' declare STATUS_ERROR='' declare STATUS_SUCCESS='' ############################################################################### # Functions ############################################################################### function check_input { local -i app_arg_num=0 local -i file_arg_num=0 local -i getopt_rc=0 local parsed='' parsed=$( getopt --alternative \ --options "$OPTIONS_SHORT" \ --longoptions "$OPTIONS_LONG" \ --name "$DISPLAY_NAME" \ -- "$@" ) || getopt_rc=$? if [[ $getopt_rc -ne $SUCCESS ]]; then process_usage NOERROR=true exit $ERROR fi eval set -- "$parsed" process_common_options "$@" while true; do case $1 in -a|--apps) OPTION_APPS=true shift ;; -c|--cat) OPTION_CAT=true shift ;; -e|--edition) EDITION=$2 shift 2 ;; -f|--files) OPTION_FILES=true shift ;; -g|--gui) OPTION_GUI=true reset_terminal_attributes shift ;; -l|--list) OPTION_LIST=true shift ;; -r|--remove) OPTION_REMOVE=true shift ;; -s|--simulate) OPTION_SIMULATE=true EXECUTE_COMMANDS=false shift ;; --) shift break ;; *) shift ;; esac done # Verwerk argumenten. while [[ "$*" ]]; do if [[ "$(basename "$1")" == $PROGRAM_NAME-*.sh ]]; then ARGUMENT_FILE=true FILE_ARGUMENTS[$file_arg_num]=$1 ((++file_arg_num)) shift else ARGUMENT_APP=true APP_ARGUMENTS[$app_arg_num]=$1 ((++app_arg_num)) shift fi done } function process_input { local dfltfile_1='' local dfltfile_2='' local file_notfound=false local -i file_num=0 local file='' # Verwerk optie list. if $OPTION_LIST; then RUN_AS_SUPERUSER=false check_user_root process_option_list exit $SUCCESS fi # Verwerk argumenten. dfltfile_1=$PROGRAM_PATH/$PROGRAM_NAME-$DISTRO-$VERSION-$EDITION.sh dfltfile_2=$PROGRAM_PATH/$PROGRAM_NAME-$DISTRO-$VERSION-$EDITION\ -$HOSTNAME.sh if $ARGUMENT_APP; then process_argument_app fi if $ARGUMENT_FILE; then process_argument_file fi # Ergens hiervoor ging het niet helemaal goed. if $file_notfound; then NOERROR=true exit $ERROR fi # Voor bepaalde argumenten en opties geen standaardbestanden verwerken. if ! ($ARGUMENT_APP || $ARGUMENT_FILE || $OPTION_FILES); then process_dfltfiles fi # Verwerk overige opties; afhankelijk van bestanden en argumenten. if $OPTION_APPS; then process_option_apps exit $SUCCESS elif $OPTION_CAT; then process_option_cat exit $SUCCESS elif $OPTION_FILES; then process_option_files exit $SUCCESS fi # Opdrachten uitvoeren of simuleren. if $EXECUTE_COMMANDS; then check_user_root check_on_ac_power fi for file in "${FILES_TO_PROCESS[@]}"; do cat "$file" >> "$CMDS_FILE" done if $EXECUTE_COMMANDS && $OPTION_GUI; then TITLE='Apps installeren' TEXT='' # Met "|& zenity --progress" worden globale variabelen uit aangeroepen # functies niet doorgegeven, vandaar de 'process substitution' met # "> >(zenity...)". process_cmds_file > >( zenity --progress \ --pulsate \ --auto-close \ --no-cancel \ --width 700 \ --height 50 \ --title "$TITLE" \ --text "$TEXT" 2> >($LOGCMD) ) else process_cmds_file fi } function process_argument_app { local app='' local file='' local -a files=() local search='' # kz-install zoekt naar apps in ALLE kz-install---bestanden # en stopt bij de eerstgevonden, kz-setup zoekt naar apps ALLEEN in de # staandaard bestanden kz-setup---.sh en # kz-install----.sh. search=$PROGRAM_NAME-$DISTRO-$VERSION for file in "$PROGRAM_PATH/$search"*.sh; do files+=("$file") done for app in "${APP_ARGUMENTS[@]}"; do file=$( grep --files-with-matches \ --word-regexp \ --regexp="#1 $app" \ "${files[@]}" \ 2> /dev/null | head --lines=1 || printf '\n' ) if [[ $file ]]; then process_argument_app_file else info "App $app niet gevonden." file_notfound=true fi done if $file_notfound; then info "Typ '$DISPLAY_NAME --apps' voor beschikbare apps." >&2 else FILES_TO_PROCESS[$file_num]=$APPS_FILE ((++file_num)) fi } function process_argument_app_file { local app_found=false local record='' while read -r record; do case $record in '#1 '*) if [[ $record == '#1 '$app* ]]; then # Gezochte app-naam. # Regels doorschrijven naar tijdelijk bestand tot # eerstvolgende app-naam of tot eof. printf '%s\n' "$record" >> "$APPS_FILE" app_found=true elif $app_found; then # Volgende app. break fi ;; '#1-'*) if $app_found; then # Volgende (hidden) app. break fi ;; *) # Overige regels. if $app_found; then printf '%s\n' "$record" >> "$APPS_FILE" fi ;; esac done < "$file" } function process_argument_file { local file='' for file in "${FILE_ARGUMENTS[@]}"; do if [[ -e $file ]]; then FILES_TO_PROCESS[$file_num]=$file ((++file_num)) else info "Bestand $file niet gevonden." file_notfound=true fi done if $file_notfound; then info "Typ '$DISPLAY_NAME --files' voor bestanden op dit systeem." >&2 fi } function process_dfltfiles { # Verplicht aanwezig. if [[ -e $dfltfile_1 ]]; then FILES_TO_PROCESS[$file_num]=$dfltfile_1 ((++file_num)) else error "Geen bestanden gevonden voor $DISTRO $VERSION $EDITION." process_usage NOERROR=true exit $ERROR fi # Niet verplicht aanwezig. if [[ -e $dfltfile_2 ]]; then FILES_TO_PROCESS[$file_num]=$dfltfile_2 ((++file_num)) fi } function process_option_apps { local -a files=() local search='' # kz-install zoekt naar apps in ALLE kz-install---bestanden # en stopt bij de eerstgevonden, kz-setup zoekt naar apps ALLEEN in de # staandaard bestanden kz-setup---.sh en # kz-install----.sh. search=$PROGRAM_NAME-$DISTRO-$VERSION for file in "$PROGRAM_PATH/$search"*.sh; do files+=("$file") done TEXT="De volgende APPs zijn beschikbaar: APP (BESCHRIJVING) $( if ! grep --no-messages \ --regexp='^#1 ' \ "${files[@]}" | cut --delimiter=' ' \ --fields=2- | sort --unique | nl --number-width=2 \ --number-format=rn \ --number-separator='] ' \ --body-numbering=a | sed --expression='s/^/[/'; then printf '%s\n' " 0 Geen apps gevonden voor $DISTRO $VERSION." else printf '%s\n' " Om de inhoud van APPs te bekijken voer uit: ${BLUE}$DISPLAY_NAME \ --cat APP...${NORMAL} Om de verwerking van APPs te simuleren voer uit: ${BLUE}$DISPLAY_NAME \ --simulate APP...${NORMAL} Om de APPs te installeren voer uit: ${BLUE}$DISPLAY_NAME \ APP...${NORMAL}" fi )" info "$TEXT" } function process_option_cat { local first_file=true for file in "${FILES_TO_PROCESS[@]}"; do { if $first_file; then first_file=false else printf '\n' fi printf "${BLUE}%s\n${NORMAL}" "# $file" cat "$file" } >> "$CMDS_FILE" done less "$LESS_OPTIONS" "$CMDS_FILE" } function process_option_files { TEXT="De volgende BESTANDen staan op het systeem: BESTAND $( if ! find "$PROGRAM_PATH/$PROGRAM_NAME-"* \ 2> /dev/null | nl --number-width=2 \ --number-format=rn \ --number-separator='] ' \ --body-numbering=a | sed --expression='s/^/[/'; then printf '%s\n' " 0 Geen bestanden aanwezig op het systeem. $USAGELINE" else printf '%s\n' " Om de inhoud van BESTANDen te bekijken voer uit: ${BLUE}$DISPLAY_NAME \ --cat BESTAND...${NORMAL} Om de verwerking van BESTANDen te simuleren voer uit: ${BLUE}$DISPLAY_NAME \ --simulate BESTAND...${NORMAL} Om de BESTANDen te verwerken voer uit: ${BLUE}$DISPLAY_NAME \ BESTAND...${NORMAL}" fi )" info "$TEXT" } function process_option_list { local text0='Opdrachten worden verwerkt' local text1="[1/3] Bepaal welke pakketten handmatig zijn geïnstalleerd" local text2='[2/3] Bepaal welke snaps zijn geïnstalleerd' local text3="[3/3] Bepaal welke pakketbronnen zijn toegevoegd" local tgtdir=$HOME/kz-data local file=$tgtdir/Apps mkdir --parents "$tgtdir" |& $LOGCMD TITLE='Applijst maken' if $OPTION_GUI; then ( printf '%s\n' "#$text1" process_option_list_manual_installed printf '%s\n' "#$text2" process_option_list_snaps printf '%s\n' "#$text3" process_option_list_added_repos ) |& zenity --progress \ --pulsate \ --auto-close \ --no-cancel \ --width 600 \ --height 50 \ --title "$TITLE" \ --text "$text0" 2> >($LOGCMD) else process_option_list_manual_installed process_option_list_snaps process_option_list_added_repos fi info ' De gegevens zijn vastgelegd in Persoonlijke map / kz-data / Apps. In bestand Apps staan de eerder geïnstalleerde apps. Dit bestand kan worden gebruikt voor het controleren van de volledigheid van de installatie.' } function process_option_list_manual_installed { local header="[1/3] Deze pakketten zijn geïnstalleerd via Software, \ met een opdracht zoals 'sudo apt --install ', of met kz install:" printf '%s\n' "$text1" printf '%b\n' "$header\n$DASHES" > "$file" apt list --manual-installed 2> >($LOGCMD) >> "$file" printf '%b\n' "$DASHES\n" >> "$file" } function process_option_list_snaps { local header="[2/3] Deze snaps zijn geïnstalleerd via Software, \ met '[sudo] install ', of met kz install:" printf '%s\n' "$text2" printf '%b\n' "$header\n$DASHES" >> "$file" if ! snap list 2> /dev/null >> "$file"; then TEXT='snap is niet geïnstalleerd op dit systeem' printf '%s\n' "$TEXT" >> "$file" fi printf '%b\n' "$DASHES\n" >> "$file" } function process_option_list_added_repos { local header="[3/3] Deze pakketbronnen zijn toegevoegd met de \ installatie van een pakket, met de opdracht 'sudo add-apt-repository ppa:/', of met kz install:" local search='' printf '%s\n' "$text3" printf '%b\n' "$header\n$DASHES" >> "$file" search=/etc/apt/sources.list cd $search.d if ! grep --recursive \ --no-filename \ --regexp='^deb ' \ --recursive \ /etc/apt/sources.list \ /etc/apt/sources.list.d | grep --invert-match \ --regexp='ubuntu.com' | sort --unique >> "$file"; then true fi printf '%b\n' "$DASHES" >> "$file" } function process_cmds_file { local app_description='' local first_description_line=true local operation='installeren' local record='' local recordtype='' local write_app_description_line=true local -i app_seq_num=0 local -i app_tot_num=0 local -i cmd_seq_num=0 app_tot_num=$( grep --word-regexp --regexp='^#1' --count "$CMDS_FILE" || true ) if [[ $app_tot_num -gt 99 ]]; then app_tot_num=99 fi if $OPTION_REMOVE; then operation='verwijderen' fi if $OPTION_SIMULATE; then operation+=' simuleren' else STATUS_BUSY="[${BLINK}WERK${NORMAL}]" STATUS_ERROR="[${RED}FOUT${NORMAL}]" STATUS_SUCCESS="[${GREEN}GOED${NORMAL}]" printf '%s' "${CURSOR_INVISABLE}" fi while read -r record; do recordtype=${record:0:2} case $recordtype in '') # Lege regel. continue ;; '#1') # Appnaam en -beschrijving. process_app_description_record "$record" ;; '#2') # Verwijderopdracht. if $OPTION_REMOVE; then process_command_record "${record:3}" fi ;; '#'*) # Commentaarregel. continue ;; *) # Installatieopdracht. if ! $OPTION_REMOVE; then process_command_record "$record" fi ;; esac done < "$CMDS_FILE" printf '%s' "${CURSOR_VISABLE}" } function process_app_description_record { local record=${1:-record?} app_description="${record:3} $operation" write_app_description_line=true ((++app_seq_num)) if [[ $app_seq_num -gt 99 ]]; then app_seq_num=99 fi cmd_seq_num=0 } function process_command_record { local cmd=${1:-cmd?} local app_description_line='' local -i cmd_rc=0 local -i desc_maxlen=1000 local desc_printf='' local status='' COMMANDS_FOUND=true ((++cmd_seq_num)) if [[ $cmd_seq_num -gt 99 ]]; then cmd_seq_num=99 fi if $EXECUTE_COMMANDS; then log "$DASHES" desc_printf=$app_description if $OPTION_GUI; then TEXT="#[$app_seq_num/$app_tot_num] $app_description\n\n$cmd" printf '%s\n' "$TEXT" else # Afbreken tekst in Terminalvenster moet i.v.m. updaten status. # <--------------- tput cols ---------------> # '[WERK] [ 1/1 ] [ 1] Te lange description...' <=== op 1 regel # <------- 20 -------> <3> desc_maxlen=$(($(tput cols) - 20)) if [[ ${#desc_printf} -gt $desc_maxlen ]]; then desc_printf=${desc_printf:0:(($desc_maxlen - 3))}'...' fi fi printf -v app_description_line \ "[%2d/%-2d] [%2d] %-${desc_maxlen}.${desc_maxlen}s" \ "$app_seq_num" \ "$app_tot_num" \ "$cmd_seq_num" \ "$desc_printf" execute_command "$cmd" else # OPTION_SIMULATE if $write_app_description_line; then if $first_description_line; then first_description_line=false else printf '\n' >> "$SIM_FILE" fi printf '[%2d/%-2d] %s\n' \ "$app_seq_num" \ "$app_tot_num" \ "$app_description" >> "$SIM_FILE" write_app_description_line=false fi printf '%7s [%2d] %s\n' \ " " \ "$cmd_seq_num" \ "${BLUE}$cmd${NORMAL}" >> "$SIM_FILE" fi } function execute_command { local cmd=${1:-cmd?} local -i cmd_rc=0 local status='' check_dpkg if ! $OPTION_GUI; then if [[ $cmd_seq_num -eq 1 ]]; then printf "$STATUS_BUSY %b" "$app_description_line\r" else printf "$STATUS_BUSY %b" "${app_description_line:7}\r" fi fi log "$app_description_line" log "${BLUE}$cmd${NORMAL}" NOERROR=true eval "$cmd" |& $LOGCMD || cmd_rc=$? NOERROR=false if [[ $cmd_rc -gt $MAXRC ]]; then MAXRC=$cmd_rc fi if [[ $cmd_rc -eq $SUCCESS ]]; then status="$STATUS_SUCCESS" else status="$STATUS_ERROR" fi log "$status rc=$cmd_rc, maxrc=$MAXRC" if ! $OPTION_GUI; then printf '%s\n' "$status" fi } function check_dpkg { local -i wait_for_aptd=5 if ls /snap/core/*/var/cache/debconf/config.dat &> /dev/null; then while sudo fuser \ /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock \ /var/cache/debconf/config.dat \ /snap/core/*/var/cache/debconf/config.dat \ &> /dev/null; do log 'Wacht tot andere pakketbeheerder klaar is...' sleep $wait_for_aptd done else while sudo fuser \ /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock \ /var/cache/debconf/config.dat \ &> /dev/null; do log 'Wacht tot andere pakketbeheerder klaar is...' sleep $wait_for_aptd done fi } function term_script { local action='Installeren' if ! $COMMANDS_FOUND; then info 'Geen opdrachten om uit te voeren.' elif $EXECUTE_COMMANDS; then if [[ $MAXRC -ne $SUCCESS ]]; then exit $ERROR else if $OPTION_REMOVE; then action='Verwijderen' fi info " $action is voltooid." fi else less "$LESS_OPTIONS" "$SIM_FILE" fi exit $SUCCESS } ############################################################################### # Script ############################################################################### function main { init_script "$@" check_input "$@" process_input term_script } main "$@"