#!/bin/bash { #//////////////////////////////////// # DietPi-Globals # #//////////////////////////////////// # Created by Daniel Knight / daniel.knight@dietpi.com / dietpi.com # #//////////////////////////////////// # # Info: # - Allows export of shared DietPi global variables/funcs into current bash session, and other scripts # - CRITICAL, when using for-loops, always ensure you localize the var before hand (eg: local i=0\nfor (( i=.....)) ), else havoc: https://github.com/Fourdee/DietPi/issues/1454 # - activates on login /etc/profile.d/99-dietpi-login.sh # - activates on all other DietPi script during their init # # Excluded scripts, which do NOT load these globals # - dietpi-ramlog # - dietpi-ramdisk # - dietpi-obtain_hw_model # - dietpi.txt (and all other .txt .ini or config files) # #//////////////////////////////////// #----------------------------------------------------------------------------------- #Ensure we are in users home dir: https://github.com/Fourdee/DietPi/issues/905#issuecomment-298223705 cd "$HOME" #----------------------------------------------------------------------------------- #Exit script for unsupported terminal and set dumb: https://github.com/Fourdee/DietPi/issues/1703#issue-314330405 if [[ -z $TERM || $TERM == 'unknown' ]]; then export TERM='dumb' exit fi #----------------------------------------------------------------------------------- #----------------------------------------------------------------------------------- # Core var's, functions, environment. Used at start of all scripts. #----------------------------------------------------------------------------------- #To be exported by the originating script (eg: dietpi-software) # - Used in: # G_ERROR_ # G_WHIP_ G_PROGRAM_NAME='' #User input? (eg: interactive shell available via STDIN check) # - Affects: # G_ERROR_HANDLER / G_WHIP whiptail display # NB: You can export G_USER_INPUTS=0 to force non-interactive (eg: .bashrc) Must run 'unset G_USER_INPUTS' after to return to auto detection. if [[ -z $G_USER_INPUTS ]]; then G_USER_INPUTS=0 if [[ -t 0 ]]; then #checked: # - OK | systemD = non-STDIN # - OK | Cron = non-STDIN #shopt login_shell # == [[ $- == *i* ]] #.bashrc dietpi/login exec is not interactive... #[ -t 0 ]# .bashrc dietpi/login exec is interactive... #set -i in .bashrc is not available... G_USER_INPUTS=1 fi fi #DietPi First-Run Stage | -2 = PREP_SYSTEM/Unknown | -1 = first boot | 0 = run dietpi-software at login | 1 = completed | 2 = Pre-installed image, converts to 1 during 1st boot G_DIETPI_INSTALL_STAGE=-2 if [[ -f '/DietPi/dietpi/.install_stage' ]]; then G_DIETPI_INSTALL_STAGE=$( /dev/null rm /tmp/dietpi-process.pid &> /dev/null # In case, the output took more than one line, clean from cursor (animation position) until end of terminal. tput ed fi output_string+='\r\e[K' } Status_Ok(){ Clean_Process_Animation output_string+="$bracket_l\e[32m OK $bracket_r " } Status_Failed(){ Clean_Process_Animation output_string+="$bracket_l\e[31mFAILED$bracket_r " } # Print all input string on same line # - $1 = start printing from word number $1 Print_Output_String(){ if (( $1 == 1 )) && [[ $G_PROGRAM_NAME ]]; then output_string+="\e[90m$G_PROGRAM_NAME | \e[0m" fi local i=0 for (( i=$1; i<${#ainput_string[@]}; i++ )); do output_string+="${ainput_string[$i]}" done echo -ne "$output_string\e[0m" } #-------------------------------------------------------------------------------------- # Main Loop #-------------------------------------------------------------------------------------- #Exit code, print OK or Failed #$2 = exit code # - Use this at end of DietPi scripts, EG: G_DIETPI-NOTIFY -1 ${EXIT_CODE:=0} if (( $1 == -1 )); then if (( ! $2 )); then Status_Ok ainput_string+=(' Completed\n') Print_Output_String 2 else Status_Failed ainput_string+=(' An issue has occured\n') Print_Output_String 2 fi #-------------------------------------------------------------------------------------- #Status Processing #$@ = txt desc elif (( $1 == -2 )); then output_string+="\r$bracket_l\e[33m......$bracket_r " Print_Output_String 1 (( G_USER_INPUTS )) || return Print_Process_Animation(){ local bright_dot='\e[1;33m.' local dimmed_dot='\e[0;33m.' # Alternative: \u23F9 local aprocess_string=( "$bright_dot " "$dimmed_dot$bright_dot " " $dimmed_dot$bright_dot " " $dimmed_dot$bright_dot " " $dimmed_dot$bright_dot " " $dimmed_dot$bright_dot" " $bright_dot$dimmed_dot" " $bright_dot$dimmed_dot " " $bright_dot$dimmed_dot " " $bright_dot$dimmed_dot " "$bright_dot$dimmed_dot " ) local i=0 for (( i=0; i<=${#aprocess_string[@]}; i++ )); do (( i == 11 )) && i=1 [[ -w /tmp/dietpi-process.pid ]] && echo -ne "\r$bracket_l${aprocess_string[i]}$bracket_r " || return sleep 0.15 done } # Calculate the amount of output lines and in case move cursor up for correct animation and to allow cleaning the whole output. local input_string="$*" local output_lines=$(( ( ${#input_string} + 6 ) / $(tput cols) )) (( $output_lines )) && tput cuu $output_lines [[ ! -e /tmp/dietpi-process.pid ]] && > /tmp/dietpi-process.pid && ( Print_Process_Animation & echo $! > /tmp/dietpi-process.pid ) unset Print_Process_Animation #Status Ok #$@ = txt desc elif (( $1 == 0 )); then Status_Ok ainput_string+=('\n') Print_Output_String 1 #Status failed #$@ = txt desc elif (( $1 == 1 )); then Status_Failed ainput_string+=('\n') Print_Output_String 1 #Status Info #$@ = txt desc elif (( $1 == 2 )); then Clean_Process_Animation output_string+="$bracket_l INFO $bracket_r " # Keep info messages in gray, even if "G_PROGRAM_NAME | \e[0m" is added: ainput_string[1]="\e[90m${ainput_string[1]}" ainput_string+=('\n') Print_Output_String 1 #DietPi banner style #$2 = txt program name #$3 = txt mode elif (( $1 == 3 )); then Clean_Process_Animation if [[ $HIERARCHY ]] && (( $HIERARCHY > 0 )); then # >=10 should never occur, however, if it is, lets make it line up regardless if (( $HIERARCHY < 10 )); then local status_subfunction="\e[33m SUB$HIERARCHY " else local status_subfunction="\e[33m SUB$HIERARCHY" fi output_string+="$bracket_l$status_subfunction$bracket_r $2 > " ainput_string+=('\n') Print_Output_String 2 else output_string+="\n \e[38;5;154m$2" output_string+='\n\e[90m─────────────────────────────────────────────────────' output_string+='\n Mode: \e[0m' ainput_string+=('\n\n') Print_Output_String 2 fi fi #----------------------------------------------------------------------------------- # Unset also internal functions, otherwise they are accessible from terminal: unset Clean_Process_Animation unset Status_Ok unset Status_Failed unset Print_Output_String #----------------------------------------------------------------------------------- } G_CHECK_ROOT_USER_VERIFIED=${G_CHECK_ROOT_USER_VERIFIED:=0} #only check once for each session # $1 = mode # - 0 = Exit all linked scripts (kill all) # - 1 = Kill current script only, excluding the shell. G_CHECK_ROOT_USER(){ local input=0 G_CHECK_VALIDINT $1 && input=$1 if (( ! $G_CHECK_ROOT_USER_VERIFIED )); then G_DIETPI-NOTIFY -2 'Checking for (elevated) root access' if (( $UID )); then G_DIETPI-NOTIFY 1 'Root privileges required. Please run the command with "G_SUDO", or, "sudo -i".' if (( $input == 0 )); then exit 1 elif (( $input == 1 )); then kill -INT $$ fi else G_DIETPI-NOTIFY 0 'Root access verified.' export G_CHECK_ROOT_USER_VERIFIED=1 fi fi } G_CHECK_ROOTFS_RW_VERIFIED=${G_CHECK_ROOTFS_RW_VERIFIED:=0} #only check once for each session G_CHECK_ROOTFS_RW(){ if (( ! $G_CHECK_ROOTFS_RW_VERIFIED )); then #RootFS RW check /DietPi/dietpi/dietpi-drive_manager 3 local exit_code=$? #Prevent HIERARCHY increase (as this pre-check runs before we define it in originating script) export HIERARCHY=$((HIERARCHY-1)) if (( $exit_code != 0 )); then G_DIETPI-NOTIFY 1 'RootFS is currently Read Only, unable to continue.\n' exit 1 else export G_CHECK_ROOTFS_RW_VERIFIED=1 fi fi } #----------------------------------------------------------------------------------- # Alias defines #----------------------------------------------------------------------------------- #DietPi Scripts, moved from /etc/bash.bashrc # - Sudo command, that ensures profile.d is reloaded (ensures ENV with our globals) G_SUDO(){ sudo -i $@ } alias sudo='sudo ' # https://github.com/Fourdee/DietPi/issues/424 alias dietpi-process_tool='/DietPi/dietpi/dietpi-process_tool' alias dietpi-letsencrypt='/DietPi/dietpi/dietpi-letsencrypt' alias dietpi-autostart='/DietPi/dietpi/dietpi-autostart' alias dietpi-cron='/DietPi/dietpi/dietpi-cron' alias dietpi-launcher='/DietPi/dietpi/dietpi-launcher' alias dietpi-cleaner='/DietPi/dietpi/dietpi-cleaner' alias dietpi-morsecode='/DietPi/dietpi/dietpi-morsecode' alias dietpi-sync='/DietPi/dietpi/dietpi-sync' alias dietpi-backup='/DietPi/dietpi/dietpi-backup' alias dietpi-bugreport='/DietPi/dietpi/dietpi-bugreport' alias dietpi-services='/DietPi/dietpi/dietpi-services' alias dietpi-config='/DietPi/dietpi/dietpi-config' alias dietpi-software='/DietPi/dietpi/dietpi-software' alias dietpi-update='/DietPi/dietpi/dietpi-update' alias dietpi-drive_manager='/DietPi/dietpi/dietpi-drive_manager' alias dietpi-logclear='/DietPi/dietpi/dietpi-logclear' alias dietpi-survey='/DietPi/dietpi/dietpi-survey' alias cpu='/DietPi/dietpi/dietpi-cpuinfo' # - Optional DietPi software aliases/functions [[ -f /opt/retropie/supplementary/emulationstation/emulationstation ]] && alias emulationstation='/opt/retropie/supplementary/emulationstation/emulationstation' [[ -f /usr/local/games/opentyrian/run ]] && alias opentyrian='/usr/local/games/opentyrian/run' [[ -f /DietPi/dietpi/misc/dietpi-justboom ]] && alias dietpi-justboom='/DietPi/dietpi/misc/dietpi-justboom' [[ -f $G_FP_DIETPI_USERDATA/dxx-rebirth/run.sh ]] && alias dxx-rebirth="$G_FP_DIETPI_USERDATA/dxx-rebirth/run.sh" [[ -f /usr/share/applications/kodi.desktop ]] && alias startkodi='/DietPi/dietpi/misc/start_kodi' [[ -f /etc/systemd/system/dietpi-cloudshell.service ]] && alias dietpi-cloudshell='/DietPi/dietpi/dietpi-cloudshell' # - occ/ncc need to be global function, as aliases are not accessible from non-interactive scripts: [[ -f /var/www/owncloud/occ ]] && occ(){ sudo -u www-data php /var/www/owncloud/occ "$@"; } [[ -f /var/www/nextcloud/occ ]] && ncc(){ sudo -u www-data php /var/www/nextcloud/occ "$@"; } #----------------------------------------------------------------------------------- # Whiptail (Whippy-da-whip-whip-whip tail!) # - Automatically detects/processes for G_USER_INPUTS #----------------------------------------------------------------------------------- #G_WHIP_DEFAULT_ITEM | to be set by scripts to set the default selected item. G_WHIP_DEFAULT_ITEM='' #G_WHIP_SIZE_X_MAX | Optional, limits X to value, if below available screen X limits G_WHIP_SIZE_X_MAX=0 #G_WHIP_BUTTON_X_TEXT | Change as needed, else, defaults to Ok/Cancel G_WHIP_BUTTON_OK_TEXT='' G_WHIP_BUTTON_CANCEL_TEXT='' #G_WHIP_MENU_ARRAY | to be set by scripts to set available menu/checkbox options G_WHIP_MENU_ARRAY=('NULL' 'NULL') #G_WHIP_CHECKLIST_ARRAY | to be set by scripts to set available checklist options G_WHIP_CHECKLIST_ARRAY=('NULL' 'NULL' 'off') #G_WHIP_RETURNED_VALUE | Returned value from inputbox/menu/checklist based whiptail items G_WHIP_RETURNED_VALUE='' #G_WHIP_DESTROY | Clear vars after run of whiptail G_WHIP_DESTROY(){ # - Delete unset WHIP_MESSAGE unset WHIP_SIZE_X unset WHIP_SIZE_Y unset WHIP_SIZE_Z unset WHIP_BACKTITLE # - Reset G_WHIP_DEFAULT_ITEM='' G_WHIP_SIZE_X_MAX=0 G_WHIP_BUTTON_OK_TEXT='' G_WHIP_BUTTON_CANCEL_TEXT='' G_WHIP_MENU_ARRAY=('NULL' 'NULL') G_WHIP_CHECKLIST_ARRAY=('NULL' 'NULL' 'off') } #G_WHIP_INIT # - update target whiptail size, based on current screen dimensions. # - Used by G_WHIP_ automatically, doesn't need to be a global function, however, bash does not support local functions. # - $1 = input mode 0=no-Z 1=yes-Z (G_WHIP_MENU_ARRAY) 2=yes-Z (G_WHIP_CHECKLIST_ARRAY) 3=Force full size of Y G_WHIP_INIT(){ #Set default button text, if not defined G_WHIP_BUTTON_OK_TEXT=${G_WHIP_BUTTON_OK_TEXT:=Ok} G_WHIP_BUTTON_CANCEL_TEXT=${G_WHIP_BUTTON_CANCEL_TEXT:=Cancel} #Update backtitle WHIP_BACKTITLE="$G_PROGRAM_NAME | $G_HW_MODEL_DESCRIPTION" if [[ -f '/DietPi/dietpi/.network' ]]; then WHIP_BACKTITLE+=" | IP: $(sed -n 4p /DietPi/dietpi/.network)" fi #Automaticaly set size of whiptail box and contents local input_mode=$1 # - Whip margins local whip_x_internal_margin=4 local whip_y_internal_margin=6 # - Text message lines required local lines_required_whip_y=0 local lines_required_whip_z=0 #Set current screen dimensions ( - outside margin) WHIP_SIZE_X=$(( $(tput cols) - 6 )) if (( $G_WHIP_SIZE_X_MAX > 0 && $G_WHIP_SIZE_X_MAX <= $WHIP_SIZE_X )); then WHIP_SIZE_X=$G_WHIP_SIZE_X_MAX elif (( $WHIP_SIZE_X > 120 )); then WHIP_SIZE_X=120 fi WHIP_SIZE_Y=$(( $(tput lines) - 4 )) if (( $WHIP_SIZE_Y > 40 )); then WHIP_SIZE_Y=40 fi WHIP_SIZE_Z=2 #Force full size of Y? if (( $input_mode == 3 )); then lines_required_whip_y=$WHIP_SIZE_Y #Calulate lines required for WHIP_MESSAGE, as displayed inside the whiptail box # - This can then be used to increase/decrease size of WHIP_SIZE_Z automatically. else if [[ $WHIP_MESSAGE ]]; then local character_count_per_line=$(echo -e "$WHIP_MESSAGE" | awk '{ print length }') # - Calculate lines required while read -r line do ((lines_required_whip_y++)) # - Calculate for addition required lines, if the text is longer than X while (( $line > ( $WHIP_SIZE_X - $whip_x_internal_margin ) )); do line=$(( $line - ( $WHIP_SIZE_X - $whip_x_internal_margin ) )) ((lines_required_whip_y++)) done done <<< "$character_count_per_line" fi # Process final whiptail size # Add internal whiptail margins to end line total lines_required_whip_y=$(( $lines_required_whip_y + $whip_y_internal_margin )) fi #Calculate Z # - G_WHIP_MENU_ARRAY if (( $input_mode == 1 )); then lines_required_whip_z=$(( ${#G_WHIP_MENU_ARRAY[@]} / 2 )) # - Prevent single entries from being divided below 1, eg: 1 / 2 = 0 in bash if (( $lines_required_whip_z < 0 )); then lines_required_whip_z=1 fi # - Requires additional line on Y ((lines_required_whip_y++)) #Auto length for ─ # Get max length of all the lines in odd number array 1st | '' 'this one' local i=0 local index=0 local character_count_max=0 for (( i=0; i<${#G_WHIP_MENU_ARRAY[@]}; i++ )) do if (( $index == 1 )); then if (( ${#G_WHIP_MENU_ARRAY[$i]} > $character_count_max )); then character_count_max=${#G_WHIP_MENU_ARRAY[$i]} # - cap to X | need to calculate all length of array 1st... # if (( $character_count_max >= $WHIP_SIZE_X )); then # character_count_max=$WHIP_SIZE_X # break # fi fi fi ((index++)) if (( $index >= 2 )); then index=0 fi done ((character_count_max--)) #-1 for additional ● # Now add the additional required lines for (( i=0; i<${#G_WHIP_MENU_ARRAY[@]}; i++ )) do if [[ ${G_WHIP_MENU_ARRAY[$i]} == '●'* ]]; then while (( ${#G_WHIP_MENU_ARRAY[$i]} < $character_count_max )) do G_WHIP_MENU_ARRAY[$i]+='─' done G_WHIP_MENU_ARRAY[$i]+='●' fi done # - G_WHIP_CHECKLIST_ARRAY elif (( $input_mode == 2 )); then lines_required_whip_z=$(( ${#G_WHIP_CHECKLIST_ARRAY[@]} / 3 )) # - Prevent single entries from being divided below 1, eg: 1 / 3 = 0 in bash if (( $lines_required_whip_z < 0 )); then lines_required_whip_z=1 fi #Auto length for ─ # Get max length of all the lines in array index 1 1st | '' 'this one' '' local i=0 local index=0 local character_count_max=0 for (( i=0; i<${#G_WHIP_CHECKLIST_ARRAY[@]}; i++ )) do if (( $index == 1 )); then if (( ${#G_WHIP_CHECKLIST_ARRAY[$i]} > $character_count_max )); then character_count_max=${#G_WHIP_CHECKLIST_ARRAY[$i]} # - cap to X | need to calculate all length of array 1st... # if (( $character_count_max >= $WHIP_SIZE_X )); then # character_count_max=$WHIP_SIZE_X # break # fi fi fi ((index++)) if (( $index >= 3 )); then index=0 fi done ((character_count_max--)) #-1 for additional ● # Now add the additional required lines for (( i=0; i<${#G_WHIP_CHECKLIST_ARRAY[@]}; i++ )) do if [[ ${G_WHIP_CHECKLIST_ARRAY[$i]} == '●'* ]]; then while (( ${#G_WHIP_CHECKLIST_ARRAY[$i]} < $character_count_max )) do # echo -e "${#G_WHIP_CHECKLIST_ARRAY[$i]} > $WHIP_LENGTH_AUTOLINEFILL | index=$i" G_WHIP_CHECKLIST_ARRAY[$i]+='─' done G_WHIP_CHECKLIST_ARRAY[$i]+='●' fi done fi #Calculate end result # - Message will not fit! if (( $lines_required_whip_y > $WHIP_SIZE_Y )); then G_DIETPI-NOTIFY 2 "Lines required for Whiptail box and its contents (y=$lines_required_whip_y, z=$lines_required_whip_z), exceeds screen dimensions (y=$WHIP_SIZE_Y)." # - Calculate a lower size of WHIP_SIZE_Y to fit everything and make it look nice! else # - Calc max size for lines_max_whip_z, based on lines_required_whip_y and current screen size if (( $lines_required_whip_z > 0 )); then local lines_max_whip_z=$lines_required_whip_z while (( ( $lines_max_whip_z + $lines_required_whip_y ) > $WHIP_SIZE_Y )); do ((lines_max_whip_z--)) # lines_max_whip_z < 1 indicates WHIP_SIZE_Y is too small to fit all message text and any lines_required_whip_z. # So we must force lines_max_whip_z=1 resulting in some missing message text if (( $lines_max_whip_z < 1 )); then lines_max_whip_z=2 G_DIETPI-NOTIFY 2 "Lines required for Whiptail box and its contents (y=$lines_required_whip_y, z=$lines_required_whip_z), exceeds screen dimensions (y=$WHIP_SIZE_Y)." break fi done WHIP_SIZE_Y=$(( $lines_required_whip_y + $lines_max_whip_z )) WHIP_SIZE_Z=$lines_max_whip_z else WHIP_SIZE_Y=$lines_required_whip_y fi fi } #G_WHIP_MSG "message" # - Display a whip message G_WHIP_MSG(){ WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 0 whiptail --title "$G_PROGRAM_NAME" --msgbox "$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X else G_DIETPI-NOTIFY 2 "$WHIP_MESSAGE" fi G_WHIP_DESTROY } #G_WHIP_SCROLLBOX "message" # - Display a whip message inside a scrollbox G_WHIP_SCROLLBOX(){ WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 0 whiptail --title "$G_PROGRAM_NAME" --scrolltext --msgbox "$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X else G_DIETPI-NOTIFY 2 "$WHIP_MESSAGE" fi G_WHIP_DESTROY } #G_WHIP_YESNO "message" # - Prompt user for a Yes/No, return result # - returns result 0=Ok, everything else considered a user canceled result G_WHIP_YESNO(){ local result=1 WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 0 whiptail --title "$G_PROGRAM_NAME" --yesno "$WHIP_MESSAGE" --backtitle "$WHIP_BACKTITLE" --yes-button "$G_WHIP_BUTTON_OK_TEXT" --no-button "$G_WHIP_BUTTON_CANCEL_TEXT" --defaultno $WHIP_SIZE_Y $WHIP_SIZE_X result=$? fi G_WHIP_DESTROY return $result } #G_WHIP_INPUTBOX "message" # - Prompt user to input text and export it to G_WHIP_RETURNED_VALUE # - returns result 0=Ok, everything else considered a user canceled result G_WHIP_INPUTBOX(){ local result=1 WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 0 export G_WHIP_RETURNED_VALUE=$(whiptail --title "$G_PROGRAM_NAME" --inputbox "$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --cancel-button "$G_WHIP_BUTTON_CANCEL_TEXT" --default-item "$G_WHIP_DEFAULT_ITEM" --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X "$G_WHIP_DEFAULT_ITEM" 3>&1 1>&2 2>&3; echo $? > /tmp/.G_WHIP_INPUTBOX_RESULT) result=$(cat /tmp/.G_WHIP_INPUTBOX_RESULT) if (( $result == 0 )) && [[ -z $G_WHIP_RETURNED_VALUE ]]; then result=1 whiptail --title "$G_PROGRAM_NAME" --msgbox '[FAILED] An input value was not entered.' --backtitle "$WHIP_BACKTITLE" 9 60 fi rm /tmp/.G_WHIP_INPUTBOX_RESULT fi G_WHIP_DESTROY return $result } #G_WHIP_PASSWORD "message" # - Prompt user to input password and safe it in variable "result". Do not export for security reasons! G_WHIP_PASSWORD(){ local return_value=1 result='' WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 0 while true do local password_0=$(whiptail --title "$G_PROGRAM_NAME" --passwordbox "$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --nocancel --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X 3>&1 1>&2 2>&3) local password_1=$(whiptail --title "$G_PROGRAM_NAME" --passwordbox 'Please enter the new password again:' --ok-button "$G_WHIP_BUTTON_OK_TEXT" --nocancel --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X 3>&1 1>&2 2>&3) if [[ $password_0 && $password_0 == $password_1 ]]; then result="$password_0" return_value=0 break else whiptail --title "$G_PROGRAM_NAME" --msgbox '[FAILED] No password entered, or invalid match.\n\nPlease try again...' --ok-button 'Retry' --backtitle "$WHIP_BACKTITLE" 9 60 fi done fi G_WHIP_DESTROY return $return_value } #G_WHIP_MENU "message" # - Prompt user to select option from G_WHIP_MENU_ARRAY and sets G_WHIP_RETURNED_VALUE # - returns result 0=Ok, everything else considered a user canceled result G_WHIP_MENU(){ local result=1 WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 1 export G_WHIP_RETURNED_VALUE=$(whiptail --title "$G_PROGRAM_NAME" --menu "$WHIP_MESSAGE" --default-item "$G_WHIP_DEFAULT_ITEM" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --cancel-button "$G_WHIP_BUTTON_CANCEL_TEXT" --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X $WHIP_SIZE_Z "${G_WHIP_MENU_ARRAY[@]}" 3>&1 1>&2 2>&3; echo $? > /tmp/.WHIP_MENU_RESULT) result=$(cat /tmp/.WHIP_MENU_RESULT) rm /tmp/.WHIP_MENU_RESULT fi G_WHIP_DESTROY return $result } #G_WHIP_CHECKLIST "message" # - Prompt user to select multiple option from G_WHIP_CHECKLIST_ARRAY and sets G_WHIP_RETURNED_VALUE # - returns result 0=Ok, everything else considered a user canceled result G_WHIP_CHECKLIST(){ local result=1 WHIP_MESSAGE=("$@") if (( $G_USER_INPUTS )); then G_WHIP_INIT 2 G_WHIP_RETURNED_VALUE=$(whiptail --title "$G_PROGRAM_NAME" --checklist "$WHIP_MESSAGE" --separate-output --default-item "$G_WHIP_DEFAULT_ITEM" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --cancel-button "$G_WHIP_BUTTON_CANCEL_TEXT" --backtitle "$WHIP_BACKTITLE" $WHIP_SIZE_Y $WHIP_SIZE_X $WHIP_SIZE_Z "${G_WHIP_CHECKLIST_ARRAY[@]}" 3>&1 1>&2 2>&3; echo $? > /tmp/.WHIP_CHECKLIST_RESULT) export G_WHIP_RETURNED_VALUE=$(echo -e "$G_WHIP_RETURNED_VALUE" | tr '\n' ' ') result=$(cat /tmp/.WHIP_CHECKLIST_RESULT) rm /tmp/.WHIP_CHECKLIST_RESULT fi G_WHIP_DESTROY return $result } #G_WHIP_VIEWLOG /var/log/file.log # - Prompt user to view logfile, then display it as needed. G_WHIP_VIEWLOG(){ local result=1 local fp_log="$1" if (( $G_USER_INPUTS )); then G_WHIP_YESNO "Would you like to view the contents of the following logfile?\n - $fp_log\n\nThis will contain further additional information, that may be required by the user." result=$? if (( ! $result )); then #??? #G_FILE_EXISTS "$fp_log" #??? G_WHIP_INIT 3 whiptail --title "$G_PROGRAM_NAME | Log viewer" --textbox "$fp_log" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --backtitle "$WHIP_BACKTITLE" --scrolltext $WHIP_SIZE_Y $WHIP_SIZE_X fi fi G_WHIP_DESTROY return $result } #----------------------------------------------------------------------------------- # DietPi Error Handler # https://github.com/Fourdee/DietPi/issues/1311#issuecomment-353716344 #----------------------------------------------------------------------------------- G_ERROR_HANDLER_EXITCODE=0 #input value to use with G_ERROR_HANDLER G_ERROR_HANDLER_EXITCODE_RETURN=0 #Same as above, but not destroyed during G_ERROR_HANDLER_RESET, allowing us to return it in funcs G_ERROR_HANDLER_COMMAND='' #eg: G_AGI: moooooooo # For export: On error, following entries will be used G_RUN_CMD_INFO_ONLY=0 #Only print info and retry options, no exit or bug report, sets G_ERROR_HANDLER_ONERROR_EXIT=1 automatically G_ERROR_HANDLER_ONERROR_EXIT=1 #Do we exit the program when the error occurs? 0=no 1=yes G_ERROR_HANDLER_ONERROR_FPLOGFILE='' #FP to logfile, if available G_ERROR_HANDLER_RETRY=0 #Used in func with while loop to re-run func as needed #Runs automatically after G_ERROR_HANDLER to reset vars to default G_ERROR_HANDLER_RESET(){ G_ERROR_HANDLER_EXITCODE_RETURN=$G_ERROR_HANDLER_EXITCODE #unset originating program unset G_ERROR_HANDLER_EXITCODE unset G_ERROR_HANDLER_COMMAND unset G_ERROR_HANDLER_ONERROR_EXIT unset G_ERROR_HANDLER_ONERROR_FPLOGFILE unset G_ERROR_HANDLER_RETRY unset G_RUN_CMD_INFO_ONLY G_ERROR_HANDLER_EXITCODE=0 G_ERROR_HANDLER_COMMAND='' G_ERROR_HANDLER_ONERROR_EXIT=1 G_ERROR_HANDLER_ONERROR_FPLOGFILE='' G_ERROR_HANDLER_RETRY=0 G_RUN_CMD_INFO_ONLY=0 } #Handles exit code errors, as defined by G_ERROR_HANDLER_xxxx settings # Usage: G_RUN_CMD dothis G_ERROR_HANDLER(){ [[ $l_message ]] || local l_message="$G_ERROR_HANDLER_COMMAND" local send_bugreport=0 local bugreport_id='N/A' #Ok if (( ! $G_ERROR_HANDLER_EXITCODE )); then G_DIETPI-NOTIFY 0 "$l_message" G_ERROR_HANDLER_RESET #Error else if (( $G_RUN_CMD_INFO_ONLY )); then G_ERROR_HANDLER_ONERROR_EXIT=0 fi local version_info='n/a' if [[ -f '/DietPi/dietpi/.version' ]]; then version_info="v$(sed -n 1p /DietPi/dietpi/.version).$(sed -n 2p /DietPi/dietpi/.version)" fi local print_hw_info="DietPi version: $version_info | HW_MODEL:$G_HW_MODEL | HW_ARCH:$G_HW_ARCH | DISTRO:$G_DISTRO" local image_creator='n/a' local preimage_name='n/a' if [[ -f '/DietPi/dietpi/.prep_info' ]]; then image_creator="$(sed -n 1p /DietPi/dietpi/.prep_info)" [[ $image_creator == 0 ]] && image_creator='DietPi Core Team' preimage_name="$(sed -n 2p /DietPi/dietpi/.prep_info)" fi local print_logfile_info="Log file contents:\n$(tail -50 $G_ERROR_HANDLER_ONERROR_FPLOGFILE)" local print_report_to_dietpi_info='If problems persist, please report this to DietPi for investigation' local print_unable_to_continue="Unable to continue, $G_PROGRAM_NAME will now terminate." G_DIETPI-NOTIFY 1 "$G_ERROR_HANDLER_COMMAND" # Display "please report to dietpi", if its one of our programs if [[ $G_PROGRAM_NAME && ! $G_RUN_CMD_INFO_ONLY ]]; then G_DIETPI-NOTIFY 2 "$print_report_to_dietpi_info" fi # - On Error: Display whip version? if (( $G_USER_INPUTS )); then local whip_msg=() if [[ $G_PROGRAM_NAME ]]; then whip_msg+="$G_PROGRAM_NAME: $G_ERROR_HANDLER_COMMAND" else whip_msg+="$G_ERROR_HANDLER_COMMAND" fi whip_msg+='\n' whip_msg+=" - Exit code: $G_ERROR_HANDLER_EXITCODE" whip_msg+='\n' whip_msg+=" - $print_hw_info" whip_msg+='\n' whip_msg+=" - Image creator: $image_creator" whip_msg+='\n' whip_msg+=" - Pre-image: $preimage_name" whip_msg+='\n\n' # Display optional logfile? if [[ $G_ERROR_HANDLER_ONERROR_FPLOGFILE ]]; then whip_msg+="$print_logfile_info" whip_msg+='\n\n' fi # Display "please report to dietpi", if its one of our programs if [[ $G_PROGRAM_NAME && ! $G_RUN_CMD_INFO_ONLY ]]; then whip_msg+="$print_report_to_dietpi_info" fi if (( $G_ERROR_HANDLER_ONERROR_EXIT )); then whip_msg+='\n\n' whip_msg+="$print_unable_to_continue" fi if (( $G_ERROR_HANDLER_RETRY )); then G_ERROR_HANDLER_RETRY=0 local aretry_menu_options=() local retry_menu_options_count=0 aretry_menu_options+=('Retry' 'Re-run the last command that failed'); ((retry_menu_options_count++)) if ! ps aux | grep -qi '[d]ietpi-config' && (( ! $G_RUN_CMD_INFO_ONLY )); then aretry_menu_options+=('DietPi-Config' 'Edit network, APT/NTP mirror settings etc'); ((retry_menu_options_count++)) fi if [[ -n $G_PROGRAM_NAME && ! $G_RUN_CMD_INFO_ONLY ]]; then aretry_menu_options+=('Send report' 'Uploads bugreport containing system info to DietPi'); ((retry_menu_options_count++)) fi local no_button_text='Ignore' if (( $G_ERROR_HANDLER_ONERROR_EXIT )); then no_button_text='Exit' fi local choice=$(whiptail --title 'DietPi Error Handler:' --menu "$whip_msg" --cancel-button "$no_button_text" --scrolltext 24 90 $retry_menu_options_count "${aretry_menu_options[@]}" 3>&1 1>&2 2>&3) if (( $? == 0 )); then if [[ $choice == 'Retry' ]]; then G_ERROR_HANDLER_RETRY=1 elif [[ $choice == 'DietPi-Config' ]]; then /DietPi/dietpi/dietpi-config G_ERROR_HANDLER_RETRY=1 elif [[ $choice == 'Send report' ]]; then send_bugreport=1 bugreport_id="$(sed -n 5p /DietPi/dietpi/.hw_model)" fi fi unset aretry_menu_options else whiptail --title 'DietPi Error Handler:' --msgbox "$whip_msg" --scrolltext 22 85 fi # - No user inputs, disable retry else G_ERROR_HANDLER_RETRY=0 fi #Github printout if (( ! $G_RUN_CMD_INFO_ONLY )); then echo -e " \e[41m -------------------------------------------------------------------- - DietPi has encounted an error, and, is unable to continue - - Please create a ticket: https://github.com/Fourdee/DietPi/issues - - Copy and paste the BLUE lines below, into the ticket - -------------------------------------------------------------------- \e[0m \e[44m #### Details: - Date | $(date) - Bug report | $bugreport_id - DietPi version | $version_info - Img creator | $image_creator - Pre-image | $preimage_name - SBC device | $G_HW_MODEL_DESCRIPTION (index=$G_HW_MODEL) - Kernel version | $(uname -v) - Distro | $G_DISTRO_NAME (index=$G_DISTRO) - Command | $G_ERROR_HANDLER_COMMAND - Exit code | $G_ERROR_HANDLER_EXITCODE - Software title | $G_PROGRAM_NAME #### Steps to reproduce: 1. ... 2. ... #### Expected behaviour: - ... #### Actual behaviour: - ... #### Extra details: - ... #### Additional logs: \`\`\` $print_logfile_info \`\`\` \e[0m \e[41m--------------------------------------------------------------------\e[0m " > /tmp/.G_ERROR_HANDLER_GITREPORT fi if (( $send_bugreport )); then # - Send automated bug report killall -w dietpi-bugreport &> /dev/null /DietPi/dietpi/dietpi-bugreport 1 fi # - End if (( ! $G_ERROR_HANDLER_RETRY )); then if [[ -f '/tmp/.G_ERROR_HANDLER_GITREPORT' ]]; then cat /tmp/.G_ERROR_HANDLER_GITREPORT rm /tmp/.G_ERROR_HANDLER_GITREPORT fi if (( $G_ERROR_HANDLER_ONERROR_EXIT )); then G_DIETPI-NOTIFY 1 "$print_unable_to_continue" G_ERROR_HANDLER_RESET # - Kill current script, excluding shell kill -INT $$ else G_ERROR_HANDLER_RESET fi fi fi return $G_ERROR_HANDLER_EXITCODE_RETURN } #Run a command and send the output through the error handler. Allows for command used info, and log output/view when an error occurs # NB: This command does not support inputs with redirects. For file creation, use G_FILE_EXISTS afterwards to check it exists: https://github.com/Fourdee/DietPi/issues/1311#issuecomment-354541417 # NB: automatically error handled (G_ERROR_HANDLER) # $@ = input command # eg: G_RUN_CMD mkdir /never/gonna/work G_RUN_CMD(){ G_CHECK_ROOT_USER 1 G_ERROR_HANDLER_COMMAND="$@" G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do [[ $l_message ]] || local l_message="$G_ERROR_HANDLER_COMMAND" G_DIETPI-NOTIFY -2 "$l_message" $G_ERROR_HANDLER_COMMAND &> /tmp/G_ERROR_HANDLER_COMMAND G_ERROR_HANDLER_EXITCODE=$? G_ERROR_HANDLER_ONERROR_FPLOGFILE='/tmp/G_ERROR_HANDLER_COMMAND' G_ERROR_HANDLER rm /tmp/G_ERROR_HANDLER_COMMAND &> /dev/null done } #URL Connection test # - $@ = URL # Prompts user to configure network if $G_USER_INPUTS=1 G_CHECK_URL(){ G_CHECK_ROOT_USER 1 local string="$@" local timeout=10 local retry_max=5 local error_connection_failed=0 G_ERROR_HANDLER_COMMAND="Connection test: $string" G_ERROR_HANDLER_ONERROR_FPLOGFILE='/tmp/G_CHECK_URL' while true do #Allow user choice to configure network on failure if (( $G_USER_INPUTS )); then G_ERROR_HANDLER_ONERROR_EXIT=0 if (( $error_connection_failed )); then #Ask to check settings, whiptail --title "URL Connection Test Failed" --yesno "DietPi was unable to establish a connection:\n - $string\n\nIf problems persist, the URL may be offline/unreachable.\n\nWould you like to configure network settings and try again?" --yes-button "Ok" --no-button "Exit" --defaultno --backtitle "$WHIP_BACKTITLE" 15 80 if (( ! $? )); then whiptail --title "Launching DietPi-Config" --msgbox "DietPi-Config will now be started.\nUse the Network Options menu to change and test your network settings.\n\nWhen completed, exit DietPi-Config to resume." --backtitle "Launching DietPi-Config" 14 60 /DietPi/dietpi/dietpi-config 8 1 #User aborted, exit now with current error information else G_ERROR_HANDLER_ONERROR_EXIT=1 G_ERROR_HANDLER_EXITCODE=1 G_USER_INPUTS=0 #disable whiptail info G_ERROR_HANDLER #break #handled by G_ERROR_HANDLER script kill fi fi #Force exit on failure to end loop (overrides any export G_ERROR_HANDLER_ONERROR_EXIT in this session) else G_ERROR_HANDLER_ONERROR_EXIT=1 fi local i=0 for ((i=1; i<=$retry_max; i++)); do G_DIETPI-NOTIFY -2 "($i/$retry_max) Testing connection to $string, please wait..." wget --spider --timeout=$timeout --tries=1 "$string" &> /tmp/G_CHECK_URL G_ERROR_HANDLER_EXITCODE=$? # Valid if (( ! $G_ERROR_HANDLER_EXITCODE )); then break # Retry else sleep 2 error_connection_failed=1 G_DIETPI-NOTIFY -2 "Failed connection attempt ($i/$retry_max), retrying..." fi done #--no-check-certificate #https://github.com/Fourdee/DietPi/issues/352#issuecomment-221013166 G_ERROR_HANDLER if (( ! $G_ERROR_HANDLER_EXITCODE )); then break fi done rm /tmp/G_CHECK_URL return $G_ERROR_HANDLER_EXITCODE_RETURN } #$1 = directory to test permissions support #Returns 0=ok >=1=failed G_CHECK_FS_PERMISSION_SUPPORT(){ local input=$1 local result=1 while true do mkdir -p $input if (( $? != 0 )); then G_WHIP_MSG "Error creating directory $input, unable to check filesystem permissions" break fi local fp_target="$input/.test" > $fp_target if (( $? != 0 )); then G_WHIP_MSG "Error creating test file $fp_target, unable to check filesystem permissions" break fi chown 775 $fp_target if (( $? != 0 )); then G_WHIP_MSG "ERROR: Filesystem does not support permissions (eg: FAT16/32):\n\n$fp_target\n\nPlease select a different drive and/or format it with ext4, ensuring support for filesystem permissions.\n\nUnable to continue, aborting..." break fi # - else ok result=0 break done rm $fp_target &> /dev/null return $result } #Checks if a file/folder exists G_FILE_EXISTS(){ G_ERROR_HANDLER_COMMAND="$@" G_DIETPI-NOTIFY -2 "Checking for existance: $G_ERROR_HANDLER_COMMAND" local string='' if [[ -f $G_ERROR_HANDLER_COMMAND ]]; then string='File exists' elif [[ -d $G_ERROR_HANDLER_COMMAND ]]; then string='Folder exists' else string='File/folder does not exist' G_ERROR_HANDLER_EXITCODE=1 fi string+=" | $G_ERROR_HANDLER_COMMAND" echo -e "$string" > /tmp/G_ERROR_HANDLER_COMMAND G_ERROR_HANDLER_ONERROR_FPLOGFILE='/tmp/G_ERROR_HANDLER_COMMAND' G_ERROR_HANDLER_COMMAND="$string" G_ERROR_HANDLER rm /tmp/G_ERROR_HANDLER_COMMAND &> /dev/null } #----------------------------------------------------------------------------------- # APT #----------------------------------------------------------------------------------- G_FP_LOG_APT='/var/tmp/dietpi/logs/dietpi-software_apt.log' #apt-get install # NB: automatically error handled (G_ERROR_HANDLER) G_AGI(){ G_CHECK_ROOT_USER 1 local string="$@" local force_options='' if (( $G_DISTRO >= 4 )); then force_options='--allow-downgrades --allow-remove-essential --allow-change-held-packages --allow-unauthenticated' else force_options='--force-yes' fi G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGI: $string" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" #-qq can add a slight period of appearing nothing is happening, lets inform user G_DIETPI-NOTIFY 0 "APT installation for: \e[33m$string\e[0m, please wait..." echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $force_options $string 2>&1 | tee "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #apt-get purge # NB: automatically error handled (G_ERROR_HANDLER) G_AGP(){ G_CHECK_ROOT_USER 1 local string=( "$@" ) local options='' if (( $G_DISTRO >= 4 )); then options+=' --allow-change-held-packages' fi G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGP: ${string[@]}" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" # - Remove non-matching packages from string, which are not installed, to prevent failures. # - Following checks are made: # match[[:space:]] # match: (eg: match:armhf) # match* (if wildcard is contained within input string) local fp_temp='/tmp/.G_AGP_CHECK_PKG_INSTALLED' local packages_to_remove='' dpkg --get-selections > "$fp_temp" local i=0 for i in "${string[@]}"; do if (( $(grep -ci -m1 "^$i[[:space:]|:]" "$fp_temp") || ( $(echo -e "$i" | grep -ci -m1 '*') && $(grep -ci -m1 "^$i" "$fp_temp") ) )); then #wildcard check packages_to_remove+="$i " G_DIETPI-NOTIFY 2 "Install verified: $i" else G_DIETPI-NOTIFY 2 "Not installed, ignoring: $i" fi done rm "$fp_temp" unset string #habbit :) if [[ $packages_to_remove ]]; then G_DIETPI-NOTIFY 0 "APT removal for: \e[33m$packages_to_remove\e[0m, please wait..." echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get purge -y $packages_to_remove $options 2>&1 | tee "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" else G_DIETPI-NOTIFY 2 "None of the requested packages are currently installed, aborting." fi G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #apt-get autoremove # NB: automatically error handled (G_ERROR_HANDLER) G_AGA(){ G_CHECK_ROOT_USER 1 G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGA" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" G_DIETPI-NOTIFY 0 'APT autoremove + purge, please wait...' echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y 2>&1 | tee "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #apt-get install -f # NB: automatically error handled (G_ERROR_HANDLER) G_AGF(){ G_CHECK_ROOT_USER 1 G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGF" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" G_DIETPI-NOTIFY 0 'APT fix, please wait...' echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get install -f -y 2>&1 | tee "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #apt-get clean + update # NB: automatically error handled (G_ERROR_HANDLER) G_AGUP(){ G_CHECK_ROOT_USER 1 G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGUP" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" G_DIETPI-NOTIFY 0 'APT update, please wait...' echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get clean 2>&1 | tee "$G_FP_LOG_APT" DEBIAN_FRONTEND=noninteractive apt-get update 2>&1 | tee -a "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #apt-get upgrade # NB: automatically error handled (G_ERROR_HANDLER) G_AGUG(){ G_CHECK_ROOT_USER 1 G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGUG" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" G_DIETPI-NOTIFY 0 'APT upgrade, please wait...' local options='' if (( $G_DISTRO >= 4 )); then options='--allow-unauthenticated' fi echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get upgrade -y $options 2>&1 | tee "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #apt-get dist-upgrade # NB: automatically error handled (G_ERROR_HANDLER) G_AGDUG(){ G_CHECK_ROOT_USER 1 G_ERROR_HANDLER_RETRY=1 while (( $G_ERROR_HANDLER_RETRY )) do G_ERROR_HANDLER_COMMAND="G_AGDUG" G_ERROR_HANDLER_ONERROR_FPLOGFILE="$G_FP_LOG_APT" if (( $G_DISTRO >= 4 )); then force_options='--allow-downgrades --allow-remove-essential --allow-change-held-packages --allow-unauthenticated' else force_options='--force-yes' fi G_DIETPI-NOTIFY 0 'APT dist-upgrade, please wait...' echo -ne "\e[90m" DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y $force_options 2>&1 | tee "$G_FP_LOG_APT" G_ERROR_HANDLER_EXITCODE=${PIPESTATUS[0]} echo -e "\e[0m" G_ERROR_HANDLER done return $G_ERROR_HANDLER_EXITCODE_RETURN } #Checks for required APT packages, installs if needed. # $@ = list of required packages # NB: automatically error handled (G_ERROR_HANDLER) G_AG_CHECK_INSTALL_PREREQ(){ G_CHECK_ROOT_USER 1 local fp_temp='/tmp/.G_AG_CHECK_INSTALL_PREREQ' local exit_code=0 local string=( "$@" ) local packages_to_install='' G_DIETPI-NOTIFY 0 "Checking for pre-req APT packages: \e[33m${string[*]}" dpkg --get-selections > "$fp_temp" local i=0 for i in "${string[@]}"; do if ! grep -qi "^$i[[:space:]]" "$fp_temp"; then G_DIETPI-NOTIFY 2 "Flagged for installation: $i" packages_to_install+=" $i" fi done if [[ $packages_to_install ]]; then G_DIETPI-NOTIFY -2 "Installing pre-req APT packages" G_AGI $packages_to_install exit_code=$? else G_DIETPI-NOTIFY 2 "Pre-req APT packages are installed" fi #G_AGI now handles the error rm "$fp_temp" return $exit_code } #----------------------------------------------------------------------------------- # MISC: Commands #----------------------------------------------------------------------------------- #Treesize # - $1 = Optional input directory (eg: G_TREESIZE /etc/apt) G_TREESIZE(){ G_CHECK_ROOT_USER 1 du -k --max-depth=1 $1 | sort -nr | awk ' BEGIN { split("KB,MB,GB,TB", Units, ","); } { u = 1; while ($1 >= 1024) { $1 = $1 / 1024; u += 1; } $1 = sprintf("%.1f %s", $1, Units[u]); print $0; } ' } #rpi-update # - Holds APT packages # - Skips backup # - Creates file (/var/lib/dietpi/.G_RPI_UPDATE) on succesful update G_RPI_UPDATE(){ G_CHECK_ROOT_USER 1 if (( $G_HW_MODEL < 10 )); then G_DIETPI-NOTIFY 2 'Installing latest RPi dev kernel. Please wait, this make take some time...' G_AG_CHECK_INSTALL_PREREQ rpi-update SKIP_BACKUP=1 SKIP_WARNING=1 G_RUN_CMD rpi-update #PRUNE_MODULES=1 would remove old/unused /lib/modules/* directories, too dangerous if update fails/firmware is unstable? apt-mark hold raspberrypi-bootloader raspberrypi-kernel libraspberrypi-bin libraspberrypi0 echo -e "$(date)" > /var/lib/dietpi/.G_RPI_UPDATE fi } #Returns current CPU temp 'C G_OBTAIN_CPU_TEMP(){ # - Array to store possible locations for temp read. afp_temperature=( #'/sys/class/thermal/thermal_zone1/temp' #sparky/Asus, will break other SBC temp readouts as most have 2 zones, needs a special case '/sys/class/thermal/thermal_zone0/temp' '/sys/devices/platform/sunxi-i2c.0/i2c-0/0-0034/temp1_input' '/sys/class/hwmon/hwmon0/device/temp_label' ) local i=0 for ((i=0; i<${#afp_temperature[@]}; i++)); do if [[ -f ${afp_temperature[$i]} ]]; then # Sparky/asus, special case: if (( $G_HW_MODEL == 70 || $G_HW_MODEL == 52 )); then cpu_temp_current=$(cat /sys/class/thermal/thermal_zone1/temp) else cpu_temp_current=$(cat ${afp_temperature[$i]}) fi # - Boards that provide 2 digit output # Pine # NanoPi M2 # NanoPi M3 # H3 3.x # H2+ 3.x if (( $G_HW_MODEL == 40 || $G_HW_MODEL == 61 || $G_HW_MODEL == 62 || ( $G_HW_CPUID == 1 && $(uname -r | grep -ci -m1 '^3.') ) || ( $G_HW_MODEL == 32 && $(uname -r | grep -ci -m1 '^3.') ) )); then echo -e "Do nothing" &> /dev/null else cpu_temp_current=$( echo -e "$cpu_temp_current" | awk '{print $1/1000}' | xargs printf "%0.0f" ) fi break fi done unset afp_temperature echo $cpu_temp_current } #Returns current CPU usage % G_OBTAIN_CPU_USAGE(){ # - PS (inaccurate, but fast??) local cpu_usage=0 local fp_temp='/tmp/.cpu_usage_cpuinfo' ps -axo %cpu | sed '1d' | sed 's/ //' > "$fp_temp" while read line; do cpu_usage=$( echo "scale=1;$cpu_usage + $line" | bc -l ) done < "$fp_temp" # - ps returns usage of each core, so we devide the total by #n cores cpu_usage=$(echo "scale=1;$cpu_usage / $(nproc --all)" | bc -l ) echo $cpu_usage } #Check available free space on path, against input value (MB) # - Returns 1=Ok, 0=insufficient space available # If $2 is not used, returns available space in MB # - $1 = path # - $2 = Optional, free space (MB) # EG: if (( $(G_CHECK_FREESPACE /path 100) )); then G_CHECK_FREESPACE(){ local return_value=0 local input_path="$1" local input_required_space=$2 local available_space=$(df -mP $input_path | sed -n 2p | awk '{print $4}') local string_output="Free space check: path=$input_path | available=$available_space MB | required=$input_required_space MB" if [[ -z $input_required_space ]]; then echo $available_space elif ! G_CHECK_VALIDINT $available_space; then G_WHIP_MSG 'G_CHECK_FREESPACE: invalid integer from df result' elif (( $available_space > $input_required_space )); then G_DIETPI-NOTIFY 0 "$string_output" return_value=1 else G_DIETPI-NOTIFY 1 "$string_output" fi return $return_value } #G_CHECK_VALIDINT | Simple test to verify if a variable is a valid integer. # $1=input # 1=no | scripts killed automatically # 0=yes # Usage = if G_CHECK_VALIDINT input; then G_CHECK_VALIDINT(){ local return_value=0 local input=$1 if [[ ! $input =~ ^-?[0-9]+$ ]]; then return_value=1 fi return $return_value } #Verifies the integrity of the DietPi userdata folder/symlink, based on where it should be psyhically. Basically, checks if user removed the USB drive with userdata on it. # NB: As this is considered a critical (if failed), current scripts will be exited automatically # 1=fail # 0=ok G_CHECK_USERDATA(){ return_value=0 local fp_actual=$G_FP_DIETPI_USERDATA #Symlinked? if [[ -L $G_FP_DIETPI_USERDATA ]]; then # - Check psyhical location exists and is mounted fp_actual="$(readlink $G_FP_DIETPI_USERDATA)" if ! df -P $fp_actual &> /dev/null; then return_value=1 fi fi G_DIETPI-NOTIFY $return_value "DietPi-Userdata validation: $fp_actual" if (( $return_value >= 1 )); then G_WHIP_MSG "Error: DietPi-Userdata validation\n\nDietPi was unable to verify the existance of the userdata directory ($fp_actual).\n\nPlease ensure all previous external drives are connected and functional, before trying again.\n\nUnable to continue, exiting." kill -INT $$ #kill all current scripts, excluding shell. fi return $return_value } #Prompt user to create a backup before system changes. Exit existing scripts if failed. G_BACKUP_DISABLED=${G_BACKUP_DISABLED:-0} G_BACKUP(){ if (( ! $G_BACKUP_DISABLED )); then G_WHIP_YESNO "Would you like to create (or update) a 'DietPi-Backup' of the system, before proceeding?\n\n'DietPi-Backup' creates a system restore point, which can be recovered if unexpected issues occur.\n\nFor more information on 'DietPi-Backup', please use the link below:\n - https://dietpi.com/phpbb/viewtopic.php?f=8&t=5&start=30#p255" if (( $? == 0 )); then /DietPi/dietpi/dietpi-backup 1 local exit_code=$? G_DIETPI-NOTIFY -1 $exit_code 'DietPi-Backup' if (( $exit_code != 0 )); then # - Kill current scripts, excluding shell G_WHIP_MSG 'DietPi-Backup was unable to complete sucessfully. To avoid issues, the current program will now be terminated.\n - logfile = /var/log/dietpi-backup.log' kill -INT $$ fi fi fi } #----------------------------------------------------------------------------------- # Multithreading handler #----------------------------------------------------------------------------------- #Not yet compatible with dietpi global commands. single bash commands only with no error handling. G_THREADING_ENABLED=${G_THREADING_ENABLED:-1} G_THREAD_COUNT=${G_THREAD_COUNT:--1} if [[ -z $G_THREAD_COMMAND ]]; then G_THREAD_COMMAND=() fi if [[ -z $G_THREAD_PID ]]; then G_THREAD_PID=() fi G_THREAD_START(){ # - Launch as BG process if (( $G_THREADING_ENABLED )); then ((G_THREAD_COUNT++)) G_THREAD_COMMAND[$G_THREAD_COUNT]=$@ ( G_USER_INPUTS=0 ${G_THREAD_COMMAND[$G_THREAD_COUNT]} &> /tmp/.G_THREAD_COMMAND_$G_THREAD_COUNT & echo $! > /tmp/.G_THREAD_PID_$G_THREAD_COUNT ) > /dev/null G_THREAD_PID[$G_THREAD_COUNT]=$(cat /tmp/.G_THREAD_PID_$G_THREAD_COUNT) rm /tmp/.G_THREAD_PID_$G_THREAD_COUNT G_DIETPI-NOTIFY 2 "G_THREAD_START_$G_THREAD_COUNT (PID:${G_THREAD_PID[$G_THREAD_COUNT]}) | ${G_THREAD_COMMAND[$G_THREAD_COUNT]}" return ${G_THREAD_PID[$G_THREAD_COUNT]} #returns a lower value, 255 char limit? # - Run in blocking mode else local command=$@ G_DIETPI-NOTIFY 2 "G_THREADING disabled, running command in blocking mode | $command" $command fi } G_THREAD_WAIT(){ # local wait_for_specific_thread_pid=0 # if [[ -n $1 ]]; then # wait_for_specific_thread_pid=$1 # fi while true do local thread_active=0 for (( i=0; i<${#G_THREAD_COMMAND[@]}; i++ )) do if [[ -z ${G_THREAD_PID[$i]} ]]; then G_DIETPI-NOTIFY 2 "G_THREAD_WAIT_$i (PID:${G_THREAD_PID[$i]}) | No PID for thread. Command may of finished, before PID could be pulled." elif [[ -d /proc/${G_THREAD_PID[$i]} ]]; then G_DIETPI-NOTIFY 2 "G_THREAD_WAIT_$i (PID:${G_THREAD_PID[$i]}) | ${G_THREAD_COMMAND[$i]}" thread_active=1 fi done if (( ! $thread_active )); then break fi sleep 1 done G_DIETPI-NOTIFY 0 'G_THREAD: All threads finished' unset G_THREAD_COMMAND unset G_THREAD_PID unset G_THREAD_COUNT } #----------------------------------------------------------------------------------- # DEV tools (Not for public use! All mine! :D) #----------------------------------------------------------------------------------- # Restore backup and update DietPi to latest testing branch code # - Restore backup # - Set testing branch, set version code -1 # - Update DietPi # - Update backup G_DEV_1(){ G_CHECK_ROOT_USER 1 export G_USER_INPUTS=0 /DietPi/dietpi/dietpi-backup -1 sed -i "/DEV_GITBRANCH=/c\DEV_GITBRANCH=testing" /DietPi/dietpi.txt /DietPi/dietpi/dietpi-update -1 /DietPi/dietpi/dietpi-backup 1 unset G_USER_INPUTS } # Inject setting into config file # - First tries to replace old setting, else commented setting and otherwise adds it to end of file. # - Optionally adds argument to new line after speficied pattern $4. # - PRESERVE=1 G_CONFIG_INJECT allows to keep current setting, if present. # - Example: G_CONFIG_INJECT 'prefer-family[[:blank:]=]' 'prefer-family = IPv4' /etc/wgetrc # - Extended regular expressions can be used for the identifying patterns $1 and $4. # - Escape any of: ".+*?&\|^$()[]{} # - Escape baskashes doubled within double quotes. G_CONFIG_INJECT(){ [[ $G_PROGRAM_NAME ]] || local G_PROGRAM_NAME='G_CONFIG_INJECT' local pattern="$1" local setting="$2" local file="$3" local after="$4" syntax_error(){ [[ $after ]] && after='\nafter line\n '.$after.' ($4)' G_WHIP_MSG "[FAILED] Syntax error\n Couldn't add setting (\$2)\n $setting\ninto file (\$3)\n $file$after\n\nNB: - Please escape any of the following characters with leading backslash (\):\n \".+*?\\|^\$()[]{} - Within double quotes (\"\"), please escape backslashes (\) doubled:\n \"\\\\\\\\\" results in \"\\\"" } if [[ ! -w $file ]]; then G_WHIP_MSG "[FAILED] '$file' does not exist or cannot be written to by current user.\n Please verify the existance of the file, retry with proper permissions or apply the setting manually:\n $(sed -E "c\\$setting" <<< '')" elif grep -qE "^[[:blank:]]*$pattern" $file; then if (( $PRESERVE )); then G_DIETPI-NOTIFY 0 "Current setting in \e[33m$file\e[0m will be preserved: \e[33m$(grep -Em1 "^[[:blank:]]*$pattern" $file | sed 's|\\|\\\\|g')\e[0m" elif grep -qE "^[[:blank:]]*$setting([[:space:]]|$)" $file; then G_DIETPI-NOTIFY 0 "Desired setting in \e[33m$file\e[0m was already set: \e[33m$(grep -Em1 "^[[:blank:]]*$pattern" $file | sed 's|\\|\\\\|g')\e[0m" else sed -Ei "0,\|^[[:blank:]]*$pattern.*$|s||$setting|" $file (( $? )) && syntax_error && return 1 G_DIETPI-NOTIFY 0 "Setting in \e[33m$file\e[0m adjusted: \e[33m$(sed -E "c\\$setting" <<< '' | sed 's|\\|\\\\|g')\e[0m" fi elif grep -qE "^[[:blank:]#;]*$pattern" $file; then sed -Ei "0,\|^[[:blank:]#;]*$pattern.*$|s||$setting|" $file (( $? )) && syntax_error && return 1 G_DIETPI-NOTIFY 0 "Comment in \e[33m$file\e[0m converted to setting: \e[33m$(sed -E "c\\$setting" <<< '' | sed 's|\\|\\\\|g')\e[0m" else if [[ $after ]]; then if grep -qE "^[[:blank:]]*$after" $file; then sed -Ei "0,\|^[[:blank:]]*$after.*$|s||&\n$setting|" $file (( $? )) && syntax_error && return 1 G_DIETPI-NOTIFY 0 "Added setting \e[33m$(sed -E "c\\$setting" <<< '' | sed 's|\\|\\\\|g')\e[0m to \e[33m$file\e[0m after line \e[33m$(grep -Em1 "^[[:blank:]]*$after" $file | sed 's|\\|\\\\|g')\e[0m" else G_WHIP_MSG "[FAILED] Setting could not be added after desired line.\n The pattern\n $(sed -E "c\\$after" <<< '')\ncould not be found in file\n $file\n Please retry with valid parameter (\$4) or apply the setting manually:\n $(sed -E "c\\$setting" <<< '')" fi else # The following sed does not work on empty files: [[ ! -s $file ]] && echo '# Added by DietPi:' >> $file sed -Ei "\$s|\$|\n$setting|" $file (( $? )) && syntax_error && return 1 G_DIETPI-NOTIFY 0 "Added setting \e[33m$(sed -E "c\\$setting" <<< '' | sed 's|\\|\\\\|g')\e[0m to end of file \e[33m$file\e[0m" fi fi unset syntax_error } #----------------------------------------------------------------------------------- #G_DIETPI-NOTIFY 2 'DietPi-Globals loaded\n' #----------------------------------------------------------------------------------- }