#!/bin/bash # shellcheck source=kzcommon.sh # ----------------------------------------------------------------------------- # Apps installeren. # # Geschreven door Karel Zimmer . # # Auteursrecht (c) 2009-2021 Karel Zimmer. # GNU Algemene Publieke Licentie . # # RelNum=55.09.03 # RelDat=2021-01-15 # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # Global constants # ----------------------------------------------------------------------------- source "$(dirname "$0")"/kzcommon.sh readonly BEGIN_INFO="\ $DASHES - Volg onderstaande aanvullende instructies na het installeren van deze apps - $DASHES" readonly END_INFO=" $DASHES - Volg bovenstaande aanvullende instructies na het installeren van deze apps - $DASHES" readonly BEGIN_REMOVE_INFO="\ $DASHES - Volg onderstaande instructies voor het verwijderen van deze apps - $DASHES" readonly END_REMOVE_INFO=" $DASHES - Volg bovenstaande instructies voor het verwijderen van deze apps - $DASHES" readonly EDITION_DEFAULT=desktop readonly RUN_AS_SUPERUSER=true # Bij aanpassingen ook .completion aanpassen! readonly OPTIONS_SHORT=$OPTIONS_SHORT_COMMON'ace:firs' readonly OPTIONS_LONG=$OPTIONS_LONG_COMMON",apps,cat,edition:,files,info,\ remove-info,simulate" readonly USAGE="Gebruik: $PROGNAME [-a|--apps] [-c|--cat] \ [-e|--edition=EDITIE] [-f|--files] [-i|--info] [-r|--remove-info] [-s|--simulate] $OPTIONS_USAGE_COMMON [APP...] [BESTAND...]" readonly HELP="Gebruik: $PROGNAME [OPTIE...] [--] [APP...] [BESTAND...] Apps installeren. Opties: -a --apps toon lijst van te installeren apps -c --cat toon inhoud van installatiebestanden -e --edition=EDITIE gebruik opgegeven editie -f --files toon lijst van alle installatiebestanden -i --info toon aanvullende instructies na het installeren van de apps -r --remove-info toon instructies voor het verwijderen van de apps -s --simulate geen actie, simuleer installatie, en toon opdracht die uitgevoerd zou worden $OPTIONS_HELP_COMMON Argumenten: APP opgegeven apps verwerken BESTAND opgegeven bestanden verwerken" # ----------------------------------------------------------------------------- # Global variables # ----------------------------------------------------------------------------- declare -a APP_ARGUMENT='' declare -a FILE_ARGUMENT='' declare -a INPUTFILE='' declare ARGUMENT_APP=false declare ARGUMENT_FILE=false declare COMMANDS_FOUND=false declare DFLTFILE_1='' declare DFLTFILE_2='' declare DFLTFILE_NOTFOUND='' declare DISTRO='' declare EDITION_ARGUMENT='' declare EDITION=$EDITION_DEFAULT declare EXECUTE_COMMANDS=true declare -i MAXRC=0 declare INFO_FOUND=false declare OPTION_APPS=false declare OPTION_CAT=false declare OPTION_EDITION=false declare OPTION_FILES=false declare OPTION_INFO=false declare OPTION_REMOVE_INFO=false declare OPTION_SIMULATE=false declare REMOVE_INFO_FOUND=false declare TEMP_TEXT_FILE='' # ----------------------------------------------------------------------------- # Functions # ----------------------------------------------------------------------------- check_input() { local file_notfound=false local -i app_arg_num=0 local id='' local -i file_arg_num=0 local -i file_num=0 local -i getopt_rc=0 local parsed='' local release='' parsed=$( getopt --alternative \ --options "$OPTIONS_SHORT" \ --longoptions "$OPTIONS_LONG" \ --name "$PROGNAME" \ -- "$@" ) || getopt_rc=$? if [[ $getopt_rc -ne 0 ]]; then printf '%s\n' "$USAGELINE" >&2 QUIET=true exit $ERROR fi eval set -- "$parsed" process_general_options "$@" while true; do case $1 in -a|--apps) OPTION_APPS=true shift ;; -c|--cat) OPTION_CAT=true shift ;; -e|--edition) if $OPTION_EDITION; then printf "$PROGNAME: %s\n%s\n" "optie '$1' éénmaal opgeven" \ "$USAGELINE" >&2 QUIET=true exit $ERROR else OPTION_EDITION=true EDITION_ARGUMENT=$2 EDITION=$EDITION_ARGUMENT fi shift 2 ;; -f|--files) OPTION_FILES=true shift ;; -i|--info) OPTION_INFO=true EXECUTE_COMMANDS=false shift ;; -r|--remove-info) OPTION_REMOVE_INFO=true EXECUTE_COMMANDS=false shift ;; -s|--simulate) OPTION_SIMULATE=true EXECUTE_COMMANDS=false shift ;; --) shift break ;; *) shift ;; esac done id=$(lsb_release --id --short | tr '[:upper:]' '[:lower:]') release=$(lsb_release --release --short) DISTRO=$id-$release-$EDITION DFLTFILE_1=$PROGDIR/$PROGNAME-$DISTRO.sh DFLTFILE_2=$PROGDIR/$PROGNAME-$DISTRO-$HOSTNAME.sh DFLTFILE_NOTFOUND="Geen bestanden zoals $PROGNAME-$DISTRO*.sh in $PROGDIR." # Verwerk argumenten. while [[ "$*" ]]; do if [[ "$(basename "$1")" = $PROGNAME-*.sh ]]; then ARGUMENT_FILE=true FILE_ARGUMENT[$file_arg_num]=$1 ((++file_arg_num)) shift else ARGUMENT_APP=true APP_ARGUMENT[$app_arg_num]=$1 ((++app_arg_num)) shift fi done if $ARGUMENT_APP; then process_argument_app fi if $ARGUMENT_FILE; then process_argument_file fi # Als APP of FILE is opgegeven, de DFLTFILEs niet verwerken. if ! ($ARGUMENT_APP || $ARGUMENT_FILE); then process_dfltfiles fi if $file_notfound; then QUIET=true exit $SUCCESS fi # Verwerk opties. if $OPTION_APPS; then process_option_apps QUIET=true exit $SUCCESS elif $OPTION_FILES; then process_option_files QUIET=true exit $SUCCESS # Optie cat is afhankelijk van argumenten APP en FILE. elif $OPTION_CAT; then process_option_cat QUIET=true exit $SUCCESS fi # We gaan opdrachten uitvoeren, mogen we dat wel? if $EXECUTE_COMMANDS; then check_user check_on_ac_power fi } process_option_apps() { local temp_text_file temp_text_file=$(mktemp -t "$PROGNAME-XXXXXXXXXX.txt") # shellcheck disable=SC2062 printf '%s\n' "De volgende apps kunnen geïnstalleerd worden: NUMMER APP $( if ! grep --regexp='^#@' \ --no-messages \ "$PROGDIR/$PROGNAME-$DISTRO"*.sh | awk -F#@ \ '{print $2}' | sort --unique | nl --number-width=6 \ --number-format=rn \ --number-separator=' ' \ --body-numbering=a; then printf '%s\n' " 0 Geen apps gevonden. $DFLTFILE_NOTFOUND" else printf '%s\n' " De gevonden apps zijn te gebruiken als invoer voor dit script: ${BLUE}$PROGNAME APP...${NORMAL} Voor meer informatie voer uit: ${BLUE}$PROGNAME --sim APP...${NORMAL} ${BLUE}$PROGNAME --cat APP...${NORMAL}" fi )" > "$temp_text_file" if $OPTION_GUI; then remove_non_printables "$temp_text_file" # Constructie '2> >($LOGCMD)' om stderr naar de log te krijgen. # Voorbeeld: Unable to init server: Kon niet verbinden: # Verbinding is geweigerd # en: (zenity:47712): Gtk-WARNING **: 10:35:49.339: # cannot open display: if ! zenity --text-info \ --width 550 \ --height 600 \ --title 'Overzicht apps' \ --filename "$temp_text_file" \ --font 'Ubuntu Mono 12' \ --ok-label 'Oké' \ --cancel-label 'Annuleren' \ 2> >($LOGCMD); then true fi else cat "$temp_text_file" fi rm "$temp_text_file" } process_option_files() { local temp_text_file temp_text_file=$(mktemp -t "$PROGNAME-XXXXXXXXXX.txt") # shellcheck disable=SC2012 printf '%s\n' "De volgende installatiebestanden zijn beschikbaar: NUMMER BESTAND $( if ! ls --format=single-column \ "$PROGDIR/$PROGNAME-$DISTRO"*.sh \ 2> /dev/null | nl --number-width=6 \ --number-format=rn \ --number-separator=' ' \ --body-numbering=a; then printf '%s\n' " 0 Geen bestanden gevonden. $DFLTFILE_NOTFOUND" else printf '%s\n' " De gevonden bestanden zijn te gebruiken als invoer voor dit script: ${BLUE}$PROGNAME BESTAND...${NORMAL} Voor meer informatie voer uit: ${BLUE}$PROGNAME --sim BESTAND...${NORMAL} ${BLUE}$PROGNAME --cat BESTAND...${NORMAL}" fi )" > "$temp_text_file" if $OPTION_GUI; then remove_non_printables "$temp_text_file" if ! zenity --text-info \ --width 750 \ --height 600 \ --title 'Overzicht bestanden' \ --filename "$temp_text_file" \ --font 'Ubuntu Mono 12' \ --ok-label 'Oké' \ --cancel-label 'Annuleren' \ 2> >($LOGCMD); then true fi else cat "$temp_text_file" fi rm "$temp_text_file" } process_argument_app() { local app='' local file='' for app in "${APP_ARGUMENT[@]}"; do # Zoek exact (line-regexp) naar app-tag in installatiebestanden. # Alleen eerstgevonden bestand verwerken (lines=1), want tag kan in # meerdere bestanden voorkomen. # shellcheck disable=SC2062 file=$( if ! grep --files-with-matches \ --line-regexp \ --regexp="#@$app" \ "$PROGDIR/$PROGNAME-$DISTRO"*.sh \ 2> /dev/null | head --lines=1; then printf '%s' '' fi ) if [[ $file ]]; then process_argument_app_file else info "App '$app' niet gevonden." file_notfound=true fi done if $file_notfound; then info "Typ 'kzsetup --apps' voor meer informatie." fi } process_argument_app_file() { local app_found=false local description_found=false local record='' local temp_sh='' temp_sh=$(mktemp -t "$PROGNAME-$app-XXXXXXXXXX.sh") while read -r record; do case $record in '#@'*) if [[ $record = '#@'$app ]]; then if $app_found; then info "Dubbele app-tag $app." else # Gezochte app-tag. printf '%b\n' "# $DASHES\n# Bestand: $temp_sh" >> \ "$temp_sh" # Regels doorschrijven naar tijdelijk bestand tot # eerstvolgende app-tag of beschrijving (verplicht), of # tot EOF. app_found=true fi elif $app_found; then # Volgende app-tag. break fi ;; '#1'*) # Beschrijving. if $description_found; then # Volgende beschrijving. break elif $app_found; then printf '%b\n' "# Bron: $file\n# $DASHES\n\n$record" >> \ "$temp_sh" description_found=true fi ;; *) # Overige regels. if $app_found; then printf '%s\n' "$record" >> "$temp_sh" fi ;; esac done < "$file" if ! grep --quiet \ --line-regexp \ --regexp='# EOF' "$temp_sh" then printf '%s\n' '# EOF' >> "$temp_sh" fi INPUTFILE[$file_num]=$temp_sh ((++file_num)) } process_argument_file() { local file='' for file in "${FILE_ARGUMENT[@]}"; do if [[ -e $file ]]; then INPUTFILE[$file_num]=$file ((++file_num)) else info "Bestand '$file' niet gevonden." file_notfound=true fi done if $file_notfound; then info "Typ 'kzsetup --files' voor meer informatie." fi } process_dfltfiles() { if [[ -e $DFLTFILE_1 ]]; then INPUTFILE[$file_num]=$DFLTFILE_1 ((++file_num)) else file_notfound=true fi # Niet verplicht aanwezig. if [[ -e $DFLTFILE_2 ]]; then INPUTFILE[$file_num]=$DFLTFILE_2 ((++file_num)) fi if $file_notfound; then info "$DFLTFILE_NOTFOUND" fi } process_option_cat() { local skip_line=false local temp_text_file temp_text_file=$(mktemp -t "$PROGNAME-XXXXXXXXXX.txt") for file in "${INPUTFILE[@]}"; do { $skip_line && printf '\n'; skip_line=true printf '%s\n' "${BLUE}# $file${NORMAL}" cat "$file" } >> "$temp_text_file" done if $OPTION_GUI; then remove_non_printables "$temp_text_file" if ! zenity --text-info \ --width 1100 \ --height 600 \ --title 'Inhoud bestanden' \ --filename "$temp_text_file" \ --font 'Ubuntu Mono 12' \ --ok-label 'Oké' \ --cancel-label 'Annuleren' \ 2> >($LOGCMD); then true fi else less "$LESS_OPTIONS" "$temp_text_file" fi rm "$temp_text_file" } process_input() { local file='' local -i app_seq_num=0 local -i app_tot_num=0 local skip_line=false local text='' local title='' TEMP_TEXT_FILE=$(mktemp -t "$PROGNAME-XXXXXXXXXX.txt") for file in "${INPUTFILE[@]}"; do if $OPTION_SIMULATE || $EXECUTE_COMMANDS; then app_seq_num=0 # 'if ! grep --regexp='^#1' ...; then ...' werkt niet; grep geeft # 0 terug als niets gevonden, OK, maar dan ook rc=1, ¬OK. app_tot_num=$( grep --word-regexp \ --regexp='^#1' \ --count "$file" || true ) fi if $EXECUTE_COMMANDS && $OPTION_GUI; then title='Apps installeren' text="Verwerk bestand $file" # Met "|& zenity --progress" worden globale variabelen uit # aangeroepen functies niet doorgegeven, vandaar de # 'process substitution' met "> >(zenity ...)". # Zenity progress-informatie per opdracht (kzsetup per bestand). process_file "$file" > >( zenity --progress \ --pulsate \ --auto-close \ --no-cancel \ --width 700 \ --height 50 \ --title "$title" \ --text "$text" \ 2> >($LOGCMD) ) else process_file "$file" fi done } process_file() { local file=${1:-file?} local description='### GEEN BESCHRIJVING ###' local file_text='' local -i cmd_seq_num=0 local prev_recordtype='' local record='' local recordtype='' local write_description_line=true file_text="${BOLD}[BEGIN] Bestand $file${NORMAL}" if $EXECUTE_COMMANDS; then log "$file_text" printf '%b' "${CURSOR_INVISABLE}" $skip_line && printf '\n'; skip_line=true printf '%s\n' "$file_text" elif $OPTION_SIMULATE; then $skip_line && printf '\n' >> "$TEMP_TEXT_FILE"; skip_line=true printf '%s\n' "$file_text" >> "$TEMP_TEXT_FILE" fi while read -r record; do recordtype=${record:0:2} case $recordtype in '#1') process_description "$record" ;; '#2') process_install_instruction "$record" ;; '#3') process_remove_instruction "$record" ;; ''|'#'*) continue ;; *) process_command "$record" ;; esac prev_recordtype=$recordtype done < "$file" file_text="${BOLD}[EINDE] Bestand $file${NORMAL}" if $EXECUTE_COMMANDS; then log "$file_text" printf '%s\n' "$file_text" printf '%b' "${CURSOR_VISABLE}" elif $OPTION_SIMULATE; then printf '%s\n' "$file_text" >> "$TEMP_TEXT_FILE" fi } process_description() { local record=${1:-record?} description="${record:3}" if ! $OPTION_SIMULATE && ! $EXECUTE_COMMANDS; then return 0 fi write_description_line=true ((++app_seq_num)) cmd_seq_num=0 } process_install_instruction() { local record=${1:-record?} INFO_FOUND=true if ! $OPTION_INFO; then return 0 fi if [[ $app_seq_num -eq 0 ]]; then printf '%s\n' "$BEGIN_INFO" > "$TEMP_TEXT_FILE" fi if [[ $prev_recordtype = '#2' ]]; then printf '%s\n' " ${record:3}" >> "$TEMP_TEXT_FILE" else ((++app_seq_num)) printf "\n%2s. %s\n %.${#description}s\n %s\n" \ "$app_seq_num" "$description" "$DASHES" "${record:3}" >> "$TEMP_TEXT_FILE" fi } process_remove_instruction() { local record=${1:-record?} REMOVE_INFO_FOUND=true if ! $OPTION_REMOVE_INFO; then return 0 fi if [[ $app_seq_num -eq 0 ]]; then printf '%s\n' "$BEGIN_REMOVE_INFO" > "$TEMP_TEXT_FILE" fi if [[ $prev_recordtype = '#3' ]]; then printf '%s\n' " ${record:3}" >> "$TEMP_TEXT_FILE" else ((++app_seq_num)) printf "\n%2s. %s\n %.${#description}s\n %s\n" "$app_seq_num" \ "$description" "$DASHES" "${record:3}" >> "$TEMP_TEXT_FILE" fi } process_command() { local cmd=${1:-cmd?} local -i cmd_rc=0 local description_line='' local -i description_maxlen=1000 local description_printf='' local status='' COMMANDS_FOUND=true ((++cmd_seq_num)) if $OPTION_SIMULATE; then if $write_description_line; then printf '[%2d/%-2d] %s\n' "$app_seq_num" "$app_tot_num" \ "$description" >> "$TEMP_TEXT_FILE" fi write_description_line=false printf '%7s [%2d] %s\n' " " "$cmd_seq_num" "${BLUE}$record${NORMAL}" \ >> "$TEMP_TEXT_FILE" elif $EXECUTE_COMMANDS; then description_printf=$description if $OPTION_GUI; then printf '%s\n' \ "#Bestand:\t$file\n\nVerwerk:\t[$app_seq_num/$app_tot_num] \ $description\n\n\Opdracht:\t$record" else # Afbreken in Terminalvenster moet i.v.m. updaten status van BEZIG # naar GOED of FOUT. # <------------ tput cols -------------> # '[BEZIG] [ 1/1 ] [ 1] Te lange descr...' <=== op 1 regel blijven # <------- 21 --------> description_maxlen=$(($(tput cols) - 21)) if [[ ${#description_printf} -gt $description_maxlen ]]; then description_printf=\ ${description_printf:0:(($description_maxlen - 3))}'...' fi fi printf -v description_line \ "[%2d/%-2d] [%2d] %-${description_maxlen}.${description_maxlen}s" \ "$app_seq_num" "$app_tot_num" "$cmd_seq_num" "$description_printf" execute_command "$record" fi } execute_command() { local cmd=${1:-cmd?} local -i cmd_rc=0 local status='' check_dpkg_available if ! $OPTION_GUI; then if [[ $cmd_seq_num -eq 1 ]]; then printf " $STATUS_BUSY %s\r" "$description_line" else printf " $STATUS_BUSY %s\r" "${description_line:8}" fi fi log "$description_line" log "${BLUE}$cmd${NORMAL}" eval "$cmd" |& $LOGCMD || cmd_rc=$? if [[ $cmd_rc -gt $MAXRC ]]; then MAXRC=$cmd_rc fi if [[ $cmd_rc -eq 0 ]]; then status="$STATUS_SUCCESS" else status="$STATUS_ERROR" fi printf ' %s\n' "$status" log "$status rc=$cmd_rc, maxrc=$MAXRC" } check_dpkg_available() { local -i wait_for_aptd=10 local text='Wacht totdat andere softwarebeheerders klaar zijn ...' if ls /snap/core/*/var/cache/debconf/config.dat &> /dev/null; then # shellcheck disable=SC2024 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 info "$text" $wait_for_aptd sleep $wait_for_aptd done else # shellcheck disable=SC2024 while sudo fuser \ /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock \ /var/cache/debconf/config.dat \ &> /dev/null; do info "$text" $wait_for_aptd sleep $wait_for_aptd done fi } term_script() { local title='' if $OPTION_SIMULATE; then if $OPTION_GUI; then title='Simuleer installatie' remove_non_printables "$TEMP_TEXT_FILE" if ! zenity --text-info \ --width 1100 \ --height 600 \ --title "$title" \ --filename "$TEMP_TEXT_FILE" \ --font 'Ubuntu Mono 12' \ --ok-label 'Oké' \ --cancel-label 'Annuleren' \ 2> >($LOGCMD); then true fi else cat "$TEMP_TEXT_FILE" fi QUIET=true elif $OPTION_INFO && $INFO_FOUND; then printf '%s\n' "$END_INFO" >> "$TEMP_TEXT_FILE" if $OPTION_GUI; then title='Installatie-instructies' remove_non_printables "$TEMP_TEXT_FILE" if ! zenity --text-info \ --width 850 \ --height 400 \ --title "$title" \ --filename "$TEMP_TEXT_FILE" \ --font 'Ubuntu Mono 12' \ --ok-label 'Oké' \ --cancel-label 'Annuleren' \ 2> >($LOGCMD); then true fi else less "$LESS_OPTIONS" "$TEMP_TEXT_FILE" fi QUIET=true elif $OPTION_INFO && ! $INFO_FOUND; then info 'Geen instructies aanwezig.' elif $OPTION_REMOVE_INFO && $REMOVE_INFO_FOUND; then printf '%s\n' "$END_REMOVE_INFO" >> "$TEMP_TEXT_FILE" if $OPTION_GUI; then title='Verwijder-instructies' remove_non_printables "$TEMP_TEXT_FILE" if ! zenity --text-info \ --width 850 \ --height 400 \ --title "$title" \ --filename "$TEMP_TEXT_FILE" \ --font 'Ubuntu Mono 12' \ --ok-label 'Oké' \ --cancel-label 'Annuleren' \ 2> >($LOGCMD); then true fi else less "$LESS_OPTIONS" "$TEMP_TEXT_FILE" fi QUIET=true elif $OPTION_REMOVE_INFO && ! $REMOVE_INFO_FOUND; then info 'Geen instructies aanwezig.' elif ! $COMMANDS_FOUND; then info 'Geen opdrachten aanwezig.' fi rm "$TEMP_TEXT_FILE" if $EXECUTE_COMMANDS && [[ $MAXRC -eq 0 ]] && $INFO_FOUND && ! $OPTION_GUI; then info "Er zijn aanvullende instructies, gebruik de opdracht: ${BLUE}$PROGNAME --info${NORMAL}" fi if $EXECUTE_COMMANDS && [[ $MAXRC -ne 0 ]]; then exit $WARNING else exit $SUCCESS fi } # ----------------------------------------------------------------------------- # Main line # ----------------------------------------------------------------------------- main() { init_script check_input "$@" process_input term_script } main "$@" # EOF