-
-
Notifications
You must be signed in to change notification settings - Fork 503
/
dietpi-globals
2213 lines (1805 loc) · 81.7 KB
/
dietpi-globals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# shellcheck disable=SC2034,SC2154
{
#////////////////////////////////////
# DietPi-Globals
#
#////////////////////////////////////
# Created by Daniel Knight / daniel.knight@dietpi.com / dietpi.com
#
#////////////////////////////////////
#
# Info:
# - Provides shared/global DietPi variables and functions for current bash session and DietPi scripts
# - CRITICAL: Use local index variables in for/while loops, or unset them afterwards, else havoc: https://github.com/MichaIng/DietPi/issues/1454
# - Sourced/Loaded in interactive bash sessions via /etc/bashrc.d/dietpi.bash
# - Sourced/Loaded at start of most DietPi script
#////////////////////////////////////
#-----------------------------------------------------------------------------------
# Core variables, functions and environment, used at start of most DietPi scripts
#-----------------------------------------------------------------------------------
# Script/Program name
# - Set this in originating script, after loading globals and before calling G_INIT()
# - Used in G_EXEC, G_WHIP and G_DIETPI-NOTIFY functions
unset -v G_PROGRAM_NAME
# Debug mode
# - Set G_DEBUG=1 to enable additional debug output for some DietPi scripts and functions
# - This variable is not pre-generated but checked via: [[ $G_DEBUG == 1 ]]
#[[ $G_DEBUG == [01] ] || G_DEBUG=0
# Non-interactive mode
# - Set G_INTERACTIVE=0 to skip interactive G_EXEC and G_WHIP dialogues
# - Set G_INTERACTIVE=1 to force interactive G_EXEC and G_WHIP dialogues
# - Default is based on whether STDIN is attached to a terminal: [[ -t 0 ]]
# cron jobs run non-interactively.
# systemd services run non-interactively unless StardardInput is explicitly set to a terminal.
# /etc/profile, /etc/profile.d/*, ~/.profile, /etc/bash.bashrc, /etc/bashrc.d/* and ~/.bashrc usually run interactively as those are sourced from the originating shell session. "profile" is sourced from login shells only, bashrc from all interactive bash shells.
[[ $G_INTERACTIVE == [01] ]] || { [[ -t 0 ]] && G_INTERACTIVE=1 || G_INTERACTIVE=0; }
# Disable DietPi-Services
# - Set G_DIETPI_SERVICES_DISABLE=1 to disable DietPi-Services
# - This variable is not pre-generated but checked via: [[ $G_DIETPI_SERVICES_DISABLE == 1 ]]
#[[ $G_DIETPI_SERVICES_DISABLE == [01] ]] || G_DIETPI_SERVICES_DISABLE=0
# DietPi first boot setup stage: -2 = DietPi-Installer/Unknown | -1 = 1st boot | 0 = 1st run dietpi-update | 1 = 1st run dietpi-software | 2 = completed | 10 = Pre-installed image, converts to 2 during 1st boot
[[ -f '/boot/dietpi/.install_stage' ]] && read -r G_DIETPI_INSTALL_STAGE < /boot/dietpi/.install_stage || G_DIETPI_INSTALL_STAGE=-2
# Hardware details
[[ -f '/boot/dietpi/.hw_model' ]] && . /boot/dietpi/.hw_model
# DietPi version and Git branch
# shellcheck disable=SC1091
[[ -f '/boot/dietpi/.version' ]] && . /boot/dietpi/.version
# - Assign defaults/code version as fallback
[[ $G_DIETPI_VERSION_CORE ]] || G_DIETPI_VERSION_CORE=9
[[ $G_DIETPI_VERSION_SUB ]] || G_DIETPI_VERSION_SUB=9
[[ $G_DIETPI_VERSION_RC ]] || G_DIETPI_VERSION_RC=0
[[ $G_GITBRANCH ]] || G_GITBRANCH='master'
[[ $G_GITOWNER ]] || G_GITOWNER='MichaIng'
# - Save current version and Git branch
G_VERSIONDB_SAVE(){
echo "G_DIETPI_VERSION_CORE=$G_DIETPI_VERSION_CORE
G_DIETPI_VERSION_SUB=$G_DIETPI_VERSION_SUB
G_DIETPI_VERSION_RC=$G_DIETPI_VERSION_RC
G_GITBRANCH='$G_GITBRANCH'
G_GITOWNER='$G_GITOWNER'" > /boot/dietpi/.version
}
# Init function for originating script
# - Stuff we can't init in main globals/funcs due to /etc/bashrc.d/dietpi.bash load into interactive bash sessions.
# - Optional environment variables:
# G_INIT_ALLOW_CONCURRENT=1 = Allow concurrent DietPi script execution (default: 0)
# G_INIT_WAIT_CONCURRENT=<int> = Max time to wait for concurrent execution to exit before user prompt (default: 5)
G_INIT(){
# Set locale to prevent incorrect scraping due to translated command outputs
# Set PATH to expected default to rule out issues due to broken environment, e.g. in combination with "su" or "sudo -E"
export LC_ALL='C.UTF-8' LANG='C.UTF-8' PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
# Enforce default umask: https://github.com/MichaIng/DietPi/issues/7022
umask 0022
# Set G_PROGRAM_NAME to originating script file (or shell executable) name if it was not set by originating script
[[ $G_PROGRAM_NAME ]] || readonly G_PROGRAM_NAME=${0##*/}
# HIERARCHY system for G_DIETPI-NOTIFY 3 to reduce highlight or sub script output
# shellcheck disable=SC2015
[[ $HIERARCHY =~ ^[0-9]+$ ]] && export HIERARCHY=$((HIERARCHY+1)) || export HIERARCHY=0
# Concurrent execution handling
local i=0
if [[ $G_INIT_ALLOW_CONCURRENT == 1 ]]
then
# Concurrency allowed: Use next free suffix for working directory, avoid race condition by checking via "mkdir" success
until mkdir "/tmp/${G_PROGRAM_NAME}_$i" &> /dev/null
do
# If the directory does not exist, its creation failed, probably due to R/O filesystem, hence break loop!
[[ -d /tmp/${G_PROGRAM_NAME}_$i ]] || break
((i++))
done
readonly G_WORKING_DIR="/tmp/${G_PROGRAM_NAME}_$i"
else
# Concurrency not allowed: Use existing working directory as flag
readonly G_WORKING_DIR="/tmp/$G_PROGRAM_NAME"
G_INIT_WAIT_CONCURRENT=${G_INIT_WAIT_CONCURRENT:=5}
while [[ -d $G_WORKING_DIR ]]
do
# SIGKILL prevents the exit trap from removing this dir. Remove it in this case and proceed.
[[ $(pidof -x "$0") == *' '* ]] || { rm -R "$G_WORKING_DIR"; break; }
if (( $i < $G_INIT_WAIT_CONCURRENT ))
then
((i++))
G_DIETPI-NOTIFY 2 "Concurrent execution of $G_PROGRAM_NAME detected, retrying... ($i/$G_INIT_WAIT_CONCURRENT)"
G_SLEEP 1
else
G_WHIP_BUTTON_OK_TEXT='Retry'
# shellcheck disable=SC2009
G_WHIP_YESNO "WARNING: Concurrent execution of $G_PROGRAM_NAME detected\n
Please check if one of the following applies:
- This script already runs on another terminal/SSH session.
- Currently a cron or systemd background job executes the script.
- You started this script from within another DietPi program, causing a loop.\n
Please assure that the concurrent execution has finished, before retrying, otherwise cancel this instance.\n
The following info might help:
$(ps f -eo pid,user,tty,cmd | grep -i 'dietpi')" && continue
G_DIETPI-NOTIFY 1 "Cancelled $G_PROGRAM_NAME due to concurrent execution"
exit 1
fi
done
fi
# Declare exit trap which runs on EXIT signals, including SIGINT and SIGTERM but not SIGKILL!
G_EXIT(){
# Execute custom exit function if declared
declare -F G_EXIT_CUSTOM &> /dev/null && G_EXIT_CUSTOM
# Navigate to /tmp before removing working directory
cd /tmp || G_DIETPI-NOTIFY 1 'Failed to navigate to /tmp'
# Purge working directory if existent
[[ ! -d $G_WORKING_DIR ]] || rm -R "$G_WORKING_DIR" || G_DIETPI-NOTIFY 1 "Failed to remove scripts working directory: $G_WORKING_DIR"
}
trap 'G_EXIT' EXIT
# Create and navigate to scripts working directory or users home if available: https://github.com/MichaIng/DietPi/issues/905#issuecomment-298223705
mkdir -p "$G_WORKING_DIR" && cd "$G_WORKING_DIR" && return
G_DIETPI-NOTIFY 1 "Failed to create or enter scripts working directory: $G_WORKING_DIR. Aborting ..."
exit 1
}
# Clear terminal by moving content into scrollback buffer: https://github.com/MichaIng/DietPi/issues/1615
G_TERM_CLEAR(){
# Without an input terminal, there is no point in doing this.
[[ -t 0 ]] || return
# Printing terminal height - 1 newlines seems to be the fastest method that is compatible with all terminal types.
local lines=$(tput lines) i newlines
for ((i=1;i<$lines;i++)); do newlines+='\n'; done
echo -ne "\e[0m$newlines\e[H"
}
# DietPi-Notify
# $1:
# -2 = Processing
# $2+ = message
# -1 = Autodetect ok or failed
# $2 = exit code
# $3+ = message
# 0 = Ok
# $2+ = message
# 1 = Failed
# $2+ = message
# 2 = Info
# $2+ = message
# 3 = Header
# $2 = program name
# $3+ = message, prefixed with "${G_NOTIFY_3_MODE}: ", defaults to "Mode: "
G_DIETPI-NOTIFY(){
local i ainput_string=("$@") output_string grey green red reset yellow dietpi_green
# If this is a terminal, it understands ANSI escape sequences, so use colour, always start left-aligned with colour reset and clear screen from cursor to end.
# - Assume if STDIN is a terminal that STDOUT is one as well, e.g. masked by pipe to "tee"
if [[ -t 0 || -t 1 ]]
then
output_string='\e[0m\r\e[J' grey='\e[90m' green='\e[32m' red='\e[31m' reset='\e[0m' yellow='\e[33m' dietpi_green='\e[38;5;154m'
# Kill existing process animation if this is not a processing message
if [[ $1 != '-2' && -w '/tmp/dietpi-process.pid' ]]
then
kill -9 "$(</tmp/dietpi-process.pid)" &> /dev/null
rm -f /tmp/dietpi-process.pid &> /dev/null
fi
# Else remove all colour codes from input string
else
shopt -s extglob
for i in "${!ainput_string[@]}"
do
ainput_string[$i]=${ainput_string[$i]//\\e[[0-9]*([;0-9])m}
done
shopt -u extglob
fi
local bracket_l="${grey}[$reset" bracket_r="$grey]$reset"
local ok="$bracket_l$green OK $bracket_r " failed="$bracket_l${red}FAILED$bracket_r "
# Print input array from index $1
Print(){
[[ $1 == 1 && $G_PROGRAM_NAME ]] && output_string+="$grey$G_PROGRAM_NAME |$reset "
for ((i=$1; i<${#ainput_string[@]}; i++))
do
output_string+=${ainput_string[$i]}
done
echo -ne "$output_string$reset"
}
#--------------------------------------------------------------------------------------
# Main Loop
#--------------------------------------------------------------------------------------
# Autodetect ok or failed
# $2 = exit code
# $3+ = message
# - Use this at end of DietPi scripts, e.g. G_DIETPI-NOTIFY -1 ${EXIT_CODE:=0}
if (( $1 == -1 )); then
if (( $2 )); then
output_string+=$failed
ainput_string+=(' | Exited with error\n')
else
output_string+=$ok
ainput_string+=(' | Completed\n')
fi
Print 2
#--------------------------------------------------------------------------------------
# Processing
# $3+ = message
# NB: Do not use this with newlines, literally or "\n", as this would cause parts of the processing message not being overwritten as intended.
elif (( $1 == -2 )); then
# If this is a terminal and not "dumb", it understands the carriage return control code, so make any next output overwrite the processing message.
if [[ ( -t 0 || -t 1 ) && $TERM != 'dumb' ]]
then
# Calculate the amount of output lines and in case move cursor up for correct animation position and to allow overwriting the whole output.
local input_string="${G_PROGRAM_NAME:+$G_PROGRAM_NAME | }$*"
# - Remove colour codes: Use extended globbing
shopt -s extglob
input_string=${input_string//\\e[[0-9]*([;0-9])m}
shopt -u extglob
local screen_width=$(tput cols)
local output_lines=$(( ( ${#input_string} + 5 ) / $screen_width )) # +5 = [ .... ] - $1
(( $output_lines )) && ainput_string+=("\e[${output_lines}A")
# If we do not print the animation, move the cursor left as well to allow overwriting the whole first line.
[[ -t 0 ]] || ainput_string+=('\r')
# Else, we add a newline to leave processing message complete.
else
ainput_string+=('\n')
fi
# Print animation only if this is the terminal control process as otherwise foreign output might cause a mess and we might be not able to kill the animation.
if [[ -t 0 && $TERM != 'dumb' ]]
then
output_string+="$bracket_l $bracket_r "
Print 1
# If redirect to existent PID file fails due to noclobber, don't start processing animation.
# - This method prevents a tiny condition race from checking file existence until creating it, when doing: [[ ! -e file ]] && > file
set -C
if { > /tmp/dietpi-process.pid; } &> /dev/null
then
set +C
Start_Process_Animation(){
local bright_dot='\e[1;33m.' 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"
" $bright_dot$dimmed_dot"
" $bright_dot$dimmed_dot "
" $bright_dot$dimmed_dot "
" $bright_dot$dimmed_dot "
"$bright_dot$dimmed_dot "
)
for (( i=0; i<=${#aprocess_string[@]}; i++ ))
do
(( i == ${#aprocess_string[@]} )) && i=0
[[ -w '/tmp/dietpi-process.pid' ]] && echo -ne "\e[2G${aprocess_string[$i]}$reset\e[C" || return
G_SLEEP 0.15
done
}
{ Start_Process_Animation & echo $! > /tmp/dietpi-process.pid; disown; } 2> /dev/null
unset -f Start_Process_Animation
else
set +C
fi
else
output_string+="$bracket_l $yellow.... $bracket_r "
Print 1
fi
#--------------------------------------------------------------------------------------
# Ok
# $2+ = message
elif (( $1 == 0 )); then
output_string+=$ok
ainput_string+=('\n')
Print 1
#--------------------------------------------------------------------------------------
# Failed
# $2+ = message
elif (( $1 == 1 )); then
output_string+=$failed
ainput_string+=('\n')
# Print error messages to STDERR
Print 1 >&2
#--------------------------------------------------------------------------------------
# Info
# $2+ = message
elif (( $1 == 2 )); then
output_string+="$bracket_l INFO $bracket_r "
# Keep info messages in grey, even if "$G_PROGRAM_NAME | \e[0m" is added:
ainput_string[1]="$grey${ainput_string[1]}"
ainput_string+=('\n')
Print 1
#--------------------------------------------------------------------------------------
# Header
# $2 = program name
# $3+ = message, prefixed with "${G_NOTIFY_3_MODE}: ", defaults to "Mode: "
elif (( $1 == 3 )); then
if disable_error=1 G_CHECK_VALIDINT "$HIERARCHY" 1; then
local status_subfunction="$HIERARCHY "
# > 9 should never occur, however, if it is, lets make it line up regardless
(( $HIERARCHY > 9 )) && status_subfunction=$HIERARCHY
output_string+="$bracket_l$yellow SUB$status_subfunction$bracket_r $2 > "
ainput_string+=('\n')
else
output_string+="
$dietpi_green$2$reset
$grey─────────────────────────────────────────────────────
${G_NOTIFY_3_MODE:-Mode}:$reset "
ainput_string+=('\n\n')
fi
Print 2
fi
#-----------------------------------------------------------------------------------
# Unset internal functions, otherwise they are accessible from terminal
unset -f Print
#-----------------------------------------------------------------------------------
}
# $1 = mode
# 2 = Silent check, only returning error code if non-root
# 1 = Kill current script only, excluding the shell.
# else = Exit all linked scripts (kill all)
G_CHECK_ROOT_USER(){
(( $UID )) || return 0
[[ $1 == 2 ]] && return 1
G_DIETPI-NOTIFY 1 'Root privileges required. Please run the command with "sudo" or "G_SUDO".'
if [[ $1 == 1 ]]
then
kill -INT $$
else
exit 1
fi
}
G_CHECK_ROOTFS_RW(){
[[ $G_CHECK_ROOTFS_RW_VERIFIED == 1 ]] && return 0
if grep -q '[[:blank:]]/[[:blank:]].*[[:blank:]]ro,' /proc/mounts
then
G_DIETPI-NOTIFY 1 'RootFS is currently Read Only (R/O) mounted. Aborting...'
G_DIETPI-NOTIFY 2 'DietPi requires RootFS to be Read/Write (R/W) mounted. Please run "dietpi-drive_manager" to re-enable.'
exit 1
else
export G_CHECK_ROOTFS_RW_VERIFIED=1
fi
}
#-----------------------------------------------------------------------------------
# Shortcut functions
#-----------------------------------------------------------------------------------
# sudo wrapper that ensures DietPi-Globals with G_* commands are loaded
G_SUDO(){ local input=$*; sudo bash -c ". /boot/dietpi/func/dietpi-globals && $input"; }
#-----------------------------------------------------------------------------------
# Whiptail (Whippy-da-whip-whip-whip tail!)
# - Automatically detects/processes for G_INTERACTIVE
#-----------------------------------------------------------------------------------
# Input:
# - G_WHIP_DEFAULT_ITEM | Optional, to set the default selected/menu item or inputbox entry
# - G_WHIP_SIZE_X_MAX=50 | Optional, limits width [in chars], if below available screen width
# - G_WHIP_BUTTON_OK_TEXT | Optional, change as needed, defaults to "Ok"
# - G_WHIP_BUTTON_CANCEL_TEXT | Optional, change as needed, defaults to "Cancel"
# - G_WHIP_NOCANCEL=1 | Optional, hide the cancel button on inputbox, menu and checkbox dialogues
# - G_WHIP_MENU_ARRAY | Required for G_WHIP_MENU to set available menu entries, 2 array indices per line: ('item' 'description')
# - G_WHIP_CHECKLIST_ARRAY | Required for G_WHIP_CHECKLIST set available checklist options, 3 array indices per line: ('item' 'description' 'on'/'off')
# Output:
# - G_WHIP_RETURNED_VALUE | Returned value from inputbox/menu/checklist based whiptail items
# G_WHIP_DESTROY | Clear vars after run of whiptail
G_WHIP_DESTROY(){ unset -v G_WHIP_DEFAULT_ITEM G_WHIP_SIZE_X_MAX G_WHIP_BUTTON_OK_TEXT G_WHIP_BUTTON_CANCEL_TEXT G_WHIP_NOCANCEL G_WHIP_MENU_ARRAY G_WHIP_CHECKLIST_ARRAY G_WHIP_INPUTBOX_REGEX G_WHIP_INPUTBOX_REGEX_TEXT; }
# Run once, to be failsafe in case any exported/environment variables are left from originating shell
G_WHIP_DESTROY
# G_WHIP_INIT
# - Update target whiptail size, based on current screen dimensions
# - $1 = input mode | 2: Z=G_WHIP_MENU_ARRAY 3: Z=G_WHIP_CHECKLIST_ARRAY
G_WHIP_INIT()
{
# Automagically set size of whiptail box and contents according to screen size and whiptail type
local input_mode=$1
# Update backtitle
WHIP_BACKTITLE=$G_HW_MODEL_NAME
local active_ip=$(G_GET_NET -q ip)
[[ $active_ip ]] && WHIP_BACKTITLE+=" | IP: $active_ip"
# 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}
# Get current screen dimensions
WHIP_SIZE_X=$(tput cols)
WHIP_SIZE_Y=$(tput lines)
# - Limit and reset non-valid integer values to 120 characters per line
(( $WHIP_SIZE_X <= 120 )) || WHIP_SIZE_X=120
# - If width is below 9 characters, the text field starts to cover the internal margin, regardless of content or button text, hence 9 is the absolute minimum.
(( $WHIP_SIZE_X >= 9 )) || WHIP_SIZE_X=9
# - G_WHIP_SIZE_X_MAX allows to further reduce width, e.g. to keep X/Y ratio in beautiful range.
disable_error=1 G_CHECK_VALIDINT "$G_WHIP_SIZE_X_MAX" 0 "$WHIP_SIZE_X" && WHIP_SIZE_X=$G_WHIP_SIZE_X_MAX
# - If height is below 7 lines, not a single line of text can be shown, hence 7 is the reasonable minimum.
(( $WHIP_SIZE_Y >= 7 )) || WHIP_SIZE_Y=7
# Calculate lines required to show all text content
local whip_lines_text=6 # Due to internal margins, the available height is 6 lines smaller
local whip_chars_text=$(( $WHIP_SIZE_X - 4 )) # Due to internal margins, the available width is 4 characters smaller
WHIP_SCROLLTEXT= # Add "--scrolltext" automatically if text height exceeds max available
Process_Line()
{
local split line=$1
# Split line by "\n" newline escape sequences, the only one which is interpreted by whiptail, in a strict way: "\\n" still creates a newline, hence the sequence cannot be escaped!
while [[ $line == *'\n'* ]]
do
# Grab first line
split=${line%%\\n*}
# Add required line + additional lines due to automated line breaks, if text exceeds internal box
(( whip_lines_text += 1 + ( ${#split} - 1 ) / $whip_chars_text ))
# Stop counting if required size exceeds screen already
(( $whip_lines_text > $WHIP_SIZE_Y )) && return 1
# Cut away handled line from string
line=${line#*\\n}
done
# Process remaining line
(( whip_lines_text += 1 + ( ${#line} - 1 ) / $whip_chars_text ))
# Stop counting if required size exceeds screen already
(( $whip_lines_text <= $WHIP_SIZE_Y )) || return 1
}
# - WHIP_MESSAGE
if [[ $WHIP_ERROR$WHIP_MESSAGE ]]
then
while read -r line; do Process_Line "$line" || break; done <<< "$WHIP_ERROR$WHIP_MESSAGE"
# - WHIP_TEXTFILE
elif [[ $WHIP_TEXTFILE ]]
then
while read -r line; do Process_Line "$line" || break; done < "$WHIP_TEXTFILE"
fi
unset -f Process_Line
# Process menu and checklist
# - G_WHIP_MENU
if [[ $input_mode == 2 ]]
then
# Requires 1 additional line for text
((whip_lines_text++))
# Lines required for menu: ( ${#array} + 1 ) to round up on uneven array entries
WHIP_SIZE_Z=$(( ( ${#G_WHIP_MENU_ARRAY[@]} + 1 ) / 2 ))
# Auto length for ─
# - Get max length of all lines in array indices 1 + 2n | '' 'this one'
local i character_count_max=0
for (( i=1; i<${#G_WHIP_MENU_ARRAY[@]}; i+=2 ))
do
(( ${#G_WHIP_MENU_ARRAY[$i]} > $character_count_max )) && character_count_max=${#G_WHIP_MENU_ARRAY[$i]}
done
((character_count_max--)) # -1 for additional ●
# - Now add the additional required lines
for (( i=1; i<${#G_WHIP_MENU_ARRAY[@]}; i+=2 ))
do
[[ ${G_WHIP_MENU_ARRAY[$i]} == '●'* ]] || continue
while (( ${#G_WHIP_MENU_ARRAY[$i]} < $character_count_max ))
do
G_WHIP_MENU_ARRAY[$i]+='─'
done
G_WHIP_MENU_ARRAY[$i]+='●'
done
# - G_WHIP_CHECKLIST
elif [[ $input_mode == 3 ]]
then
# Lines required for checklist: ( ${#array} + 2 ) to round up single+double array entries
WHIP_SIZE_Z=$(( ( ${#G_WHIP_CHECKLIST_ARRAY[@]} + 2 ) / 3 ))
# Auto length for ─
# - Get max length of all lines in array indices 1 + 3n 1st | '' 'this one' ''
local i character_count_max=0
for (( i=1; i<${#G_WHIP_CHECKLIST_ARRAY[@]}; i+=3 ))
do
(( ${#G_WHIP_CHECKLIST_ARRAY[$i]} > $character_count_max )) && character_count_max=${#G_WHIP_CHECKLIST_ARRAY[$i]}
done
((character_count_max--)) # -1 for additional ●
# - Now add the additional required lines
for (( i=1; i<${#G_WHIP_CHECKLIST_ARRAY[@]}; i+=3 ))
do
[[ ${G_WHIP_CHECKLIST_ARRAY[$i]} == '●'* ]] || continue
while (( ${#G_WHIP_CHECKLIST_ARRAY[$i]} < $character_count_max ))
do
G_WHIP_CHECKLIST_ARRAY[$i]+='─'
done
G_WHIP_CHECKLIST_ARRAY[$i]+='●'
done
fi
# Adjust sizes to fit content
# - G_WHIP_MENU/G_WHIP_CHECKLIST needs to hold text + selection field (WHIP_SIZE_Z)
if [[ $input_mode == [23] ]]
then
# If required lines would exceed screen, reduce WHIP_SIZE_Z
if (( $whip_lines_text + $WHIP_SIZE_Z > $WHIP_SIZE_Y ))
then
WHIP_SIZE_Z=$(( $WHIP_SIZE_Y - $whip_lines_text ))
# Assure at least 2 lines to have the selection field scroll bar identifiable
if (( $WHIP_SIZE_Z < 2 ))
then
WHIP_SIZE_Z=2
# Since text is partly hidden now, add text scroll ability and info to backtitle
WHIP_SCROLLTEXT='--scrolltext'
WHIP_BACKTITLE+=' | Use up/down buttons to scroll text'
fi
# else reduce WHIP_SIZE_Y to hold all content
else
WHIP_SIZE_Y=$(( $whip_lines_text + $WHIP_SIZE_Z ))
fi
# - Everything else needs to hold text only
elif (( $whip_lines_text > $WHIP_SIZE_Y ))
then
WHIP_SCROLLTEXT='--scrolltext'
WHIP_BACKTITLE+=' | Use up/down buttons to scroll text'
else
WHIP_SIZE_Y=$whip_lines_text
fi
}
# G_WHIP_MSG "message"
# - Display a message from input string
G_WHIP_MSG()
{
local WHIP_MESSAGE=$*
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y
G_WHIP_INIT
# shellcheck disable=SC2086
whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE" --msgbox "$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X"
else
G_DIETPI-NOTIFY 2 "$WHIP_MESSAGE"
fi
G_WHIP_DESTROY
}
# G_WHIP_VIEWFILE "/path/to/file"
# - Display content from input file
# - Exit code: 1=file not found, else=file shown or noninteractive
G_WHIP_VIEWFILE()
{
local result=0
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_MESSAGE WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y WHIP_TEXTFILE=$1 header='File viewer'
[[ $log == 1 ]] && header='Log viewer'
if [[ -f $WHIP_TEXTFILE ]]
then
G_WHIP_INIT
# shellcheck disable=SC2086
whiptail --title "${G_PROGRAM_NAME:+$G_PROGRAM_NAME | }$header" --backtitle "$WHIP_BACKTITLE" --textbox "$WHIP_TEXTFILE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X"
else
result=1
WHIP_ERROR="[FAILED] File does not exist: $WHIP_TEXTFILE"
G_WHIP_INIT
# shellcheck disable=SC2086
whiptail --title "${G_PROGRAM_NAME:+$G_PROGRAM_NAME | }$header" --backtitle "$WHIP_BACKTITLE" --msgbox "$WHIP_ERROR" --ok-button "$G_WHIP_BUTTON_OK_TEXT" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X"
fi
fi
G_WHIP_DESTROY
return "${result:-1}"
}
# G_WHIP_YESNO "message"
# - Prompt user for Yes/No | Ok/Cancel choice and return result
# - Exit code: 0=Yes/Ok, else=No/Cancel or noninteractive
G_WHIP_YESNO()
{
local result=1 default_no='--defaultno'
[[ ${G_WHIP_DEFAULT_ITEM,,} == 'yes' || ${G_WHIP_DEFAULT_ITEM,,} == 'ok' ]] && result=0 default_no=
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y WHIP_MESSAGE=$*
G_WHIP_INIT
# shellcheck disable=SC2086
whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE" --yesno "$WHIP_MESSAGE" --yes-button "$G_WHIP_BUTTON_OK_TEXT" --no-button "$G_WHIP_BUTTON_CANCEL_TEXT" "$default_no" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X"
result=$?
fi
G_WHIP_DESTROY
return "${result:-1}"
}
# G_WHIP_INPUTBOX "message"
# - Prompt user to input text and save it to G_WHIP_RETURNED_VALUE
# - G_WHIP_INPUTBOX_REGEX/G_WHIP_INPUTBOX_REGEX_TEXT: Regular expression and description about allowed input. Defaults to the requirement that the input is not empty.
# - Exit code: 0=input done, else=user cancelled or noninteractive
G_WHIP_INPUTBOX()
{
local result=1
unset -v G_WHIP_RETURNED_VALUE # in case left from last G_WHIP
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y WHIP_MESSAGE=$* NOCANCEL=()
G_WHIP_INPUTBOX_REGEX=${G_WHIP_INPUTBOX_REGEX:-'.'}
G_WHIP_INPUTBOX_REGEX_TEXT=${G_WHIP_INPUTBOX_REGEX_TEXT:-'not be empty'}
[[ $G_WHIP_NOCANCEL == 1 ]] && NOCANCEL=('--nocancel')
while :
do
G_WHIP_INIT
# shellcheck disable=SC2086
G_WHIP_RETURNED_VALUE=$(whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE" --inputbox "$WHIP_ERROR$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --cancel-button "$G_WHIP_BUTTON_CANCEL_TEXT" "${NOCANCEL[@]}" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X" "$G_WHIP_DEFAULT_ITEM" 3>&1 1>&2 2>&3-; echo $? > /tmp/.G_WHIP_INPUTBOX_RESULT)
read -r result < /tmp/.G_WHIP_INPUTBOX_RESULT; rm -f /tmp/.G_WHIP_INPUTBOX_RESULT
[[ $result == 0 && ! $G_WHIP_RETURNED_VALUE =~ $G_WHIP_INPUTBOX_REGEX ]] && { WHIP_ERROR="[FAILED] Input must $G_WHIP_INPUTBOX_REGEX_TEXT, please try again ...\n\n"; continue; }
break
done
else
G_WHIP_RETURNED_VALUE=$G_WHIP_DEFAULT_ITEM
fi
G_WHIP_DESTROY
return "${result:-1}"
}
# G_WHIP_PASSWORD "message"
# - Prompt user to input password and save it in variable "result"
# - Originating script must "unset result" after value has been handled for security reasons!
# - Exit code: 0=input done + passwords match, else=noninteractive (Cancelling is disabled since no password in originating script can cause havoc!)
G_WHIP_PASSWORD()
{
local return_value=1
unset -v result # in case left from last call
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y WHIP_MESSAGE=$*
while :
do
G_WHIP_INIT
# shellcheck disable=SC2086
local password_0=$(whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE" --passwordbox "$WHIP_ERROR$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --nocancel $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X" 3>&1 1>&2 2>&3-)
[[ $password_0 ]] || { WHIP_ERROR='[FAILED] No input made, please try again...\n\n'; continue; }
local password_1=$(whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE" --passwordbox 'Please retype and confirm your input:' --ok-button "$G_WHIP_BUTTON_OK_TEXT" --nocancel 7 "$WHIP_SIZE_X" 3>&1 1>&2 2>&3-)
[[ $password_0 == "$password_1" ]] || { WHIP_ERROR='[FAILED] Inputs do not match, please try again...\n\n'; continue; }
result=$password_0
return_value=0
break
done
fi
G_WHIP_DESTROY
return "${return_value:-1}"
}
# G_WHIP_MENU "message"
# - Prompt user to select option from G_WHIP_MENU_ARRAY and save choice to G_WHIP_RETURNED_VALUE
# - Exit code: 0=selection done, else=user cancelled or noninteractive
G_WHIP_MENU()
{
local result=1
unset -v G_WHIP_RETURNED_VALUE # in case left from last call
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y WHIP_SIZE_Z WHIP_MESSAGE=$* NOCANCEL=()
[[ $G_WHIP_NOCANCEL == 1 ]] && NOCANCEL=('--nocancel')
G_WHIP_BUTTON_OK_TEXT=${G_WHIP_BUTTON_OK_TEXT:-Select}
until [[ $G_WHIP_RETURNED_VALUE ]] # Stay in menu if empty option was selected (separator line)
do
G_WHIP_INIT 2
# shellcheck disable=SC2086
G_WHIP_RETURNED_VALUE=$(whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE" --menu "$WHIP_MESSAGE" --ok-button "$G_WHIP_BUTTON_OK_TEXT" --cancel-button "$G_WHIP_BUTTON_CANCEL_TEXT" "${NOCANCEL[@]}" --default-item "$G_WHIP_DEFAULT_ITEM" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X" "$WHIP_SIZE_Z" -- "${G_WHIP_MENU_ARRAY[@]}" 3>&1 1>&2 2>&3-; echo $? > /tmp/.WHIP_MENU_RESULT)
read -r result < /tmp/.WHIP_MENU_RESULT; rm -f /tmp/.WHIP_MENU_RESULT
[[ ${result:=1} == 0 ]] || break # Exit loop in case of cancel button selection or error or if .WHIP_MENU_RESULT could not be created
done
else
G_WHIP_RETURNED_VALUE=$G_WHIP_DEFAULT_ITEM
fi
G_WHIP_DESTROY
return "${result:-1}"
}
# G_WHIP_CHECKLIST "message"
# - Prompt user to select multiple options from G_WHIP_CHECKLIST_ARRAY and save choice to G_WHIP_RETURNED_VALUE
# - Exit code: 0=selection done, else=user cancelled or noninteractive
G_WHIP_CHECKLIST()
{
local result=1
unset -v G_WHIP_RETURNED_VALUE # in case left from last call
if [[ $G_INTERACTIVE == 1 ]]
then
local WHIP_ERROR WHIP_BACKTITLE WHIP_SCROLLTEXT WHIP_SIZE_X WHIP_SIZE_Y WHIP_SIZE_Z WHIP_MESSAGE=$* NOCANCEL=()
[[ $G_WHIP_NOCANCEL == 1 ]] && NOCANCEL=('--nocancel')
G_WHIP_BUTTON_OK_TEXT=${G_WHIP_BUTTON_OK_TEXT:-Confirm}
G_WHIP_INIT 3
# shellcheck disable=SC2086
G_WHIP_RETURNED_VALUE=$(whiptail ${G_PROGRAM_NAME:+--title "$G_PROGRAM_NAME"} --backtitle "$WHIP_BACKTITLE | Use spacebar to toggle selection" --checklist "$WHIP_MESSAGE" --separate-output --ok-button "$G_WHIP_BUTTON_OK_TEXT" --cancel-button "$G_WHIP_BUTTON_CANCEL_TEXT" "${NOCANCEL[@]}" --default-item "$G_WHIP_DEFAULT_ITEM" $WHIP_SCROLLTEXT "$WHIP_SIZE_Y" "$WHIP_SIZE_X" "$WHIP_SIZE_Z" -- "${G_WHIP_CHECKLIST_ARRAY[@]}" 3>&1 1>&2 2>&3-; echo $? > /tmp/.WHIP_CHECKLIST_RESULT)
G_WHIP_RETURNED_VALUE=$(echo -e "$G_WHIP_RETURNED_VALUE" | tr '\n' ' ')
read -r result < /tmp/.WHIP_CHECKLIST_RESULT; rm -f /tmp/.WHIP_CHECKLIST_RESULT
else
G_WHIP_RETURNED_VALUE=$G_WHIP_DEFAULT_ITEM
fi
G_WHIP_DESTROY
return "${result:-1}"
}
# G_BUG_REPORT [-c COMMAND] [-? EXIT_CODE]
# - Print pre-filled bug report template
G_BUG_REPORT()
{
local command exit_code
while (( $# ))
do
case $1 in
'-c') shift; command=$1;;
'-?') shift; exit_code=$1;;
*) G_DIETPI-NOTIFY 1 "Invalid argument \"$1\""; return 1;;
esac
shift
done
# Print bug report template
echo "#### Details:
- Date | $(date '+%F %T')"
# - Add program/script name, failed command and exit code if given
[[ $G_PROGRAM_NAME ]] && echo "- Program name | $G_PROGRAM_NAME"
[[ $command ]] && echo "- Command | \`$command\`"
[[ $exit_code ]] && echo "- Exit code | $exit_code"
echo "- DietPi version | v$G_DIETPI_VERSION_CORE.$G_DIETPI_VERSION_SUB.$G_DIETPI_VERSION_RC ($G_GITOWNER/$G_GITBRANCH)
- Distro version | $G_DISTRO_NAME (ID=$G_DISTRO${G_RASPBIAN:+,RASPBIAN=$G_RASPBIAN})
- Kernel version | \`$(uname -a)\`
- Architecture | \`$(dpkg --print-architecture)\`
- Hardware model | $G_HW_MODEL_NAME (ID=$G_HW_MODEL)
- Power supply | (EG: RAVPower 5V 1A)
- SD card | (EG: SanDisk Ultra 16 GB)"
# - Add image creator and pre-image info if given
if [[ -f '/boot/dietpi/.prep_info' ]]
then
image_creator=$(mawk 'NR==1' /boot/dietpi/.prep_info)
[[ ${image_creator:-0} == 0 ]] || echo "- Image creator | $image_creator"
preimage_name=$(mawk 'NR==2' /boot/dietpi/.prep_info)
[[ $preimage_name ]] && echo "- Pre-image | $preimage_name"
fi
echo '#### Steps to reproduce:
<!-- Explain how to reproduce the issue -->
1. ...
2. ...
#### Expected behaviour:
<!-- What SHOULD happen? -->
- ...
#### Actual behaviour:
<!-- What IS happening? -->
- ...
#### Extra details:
<!-- Please post any extra details that might help solve the issue -->
- ...
#### Additional logs:
```'
# - Add log if given
[[ -f '/tmp/G_EXEC_LOG' ]] && cat '/tmp/G_EXEC_LOG' || echo '<!-- Please paste logs or related console output here -->'
echo '```'
}
#-----------------------------------------------------------------------------------
# Error handled command execution wrapper
#-----------------------------------------------------------------------------------
# IMPORTANT:
# - Never pipe G_EXEC! "G_EXEC command | command" leads to G_EXEC not being able to unset G_EXEC_* variables and functions from originating shell or kill the originating script in case of error.
# Required input:
# - $@=<command> | Command to execute
# Optional input:
# - $G_EXEC_DESC=<text> | Command description to print instead of raw command string
# - $G_EXEC_RETRIES=<int> | Amount of non-interactive retries in case of error, before doing interactive error prompt
# - G_EXEC_PRE_FUNC(){} | Function to call before every input command attempt, e.g. to re-evaluate variables
# - G_EXEC_POST_FUNC(){} | Function to call after every input command attempt, e.g. to handle errors without error exit code
# - $G_EXEC_OUTPUT=1 | Print full command output instead of animated processing message
# - $G_EXEC_OUTPUT_COL='\e[90m' | Override colour of command output via console colour code, requires $G_EXEC_OUTPUT=1
# - $G_EXEC_NOFAIL=1 | On error, override as success, only useful to replace verbose output by animated processing message, inherits $G_EXEC_NOHALT=1 and $G_EXEC_NOEXIT=1
# - $G_EXEC_NOHALT=1 | On error, print short error message only, skip error handler menu and do not exit script, inherits $G_EXEC_NOEXIT=1
# - $G_EXEC_NOEXIT=1 | On error, do not exit script, inherited by $G_EXEC_NOHALT=1
# - $G_EXEC_ARRAY_TEXT[] | Add additional entries to error handler menu
# - $G_EXEC_ARRAY_ACTION[] | Associative array, containing uneven $G_EXEC_ARRAY_TEXT[] values as keys and related commands as values
G_EXEC(){
local exit_code fp_log='/tmp/G_EXEC_LOG' attempt=1 acommand=("$@")
local ecommand=${acommand[*]//\\/\\\\}
# Enter retry loop
while :
do
declare -F G_EXEC_PRE_FUNC &> /dev/null && G_EXEC_PRE_FUNC
# Exit immediately if exit_code=0 was set by G_EXEC_PRE_FUNC
[[ $exit_code == 0 ]] && break
# Execute command, store output to $fp_log file and store exit code to $exit_code variable
# - Print full command output if $G_EXEC_OUTPUT=1 is given
if [[ $G_EXEC_OUTPUT == 1 ]]; then
# Print $G_EXEC_DESC if given, else raw input command string and show current non-interactive attempt count if $G_EXEC_RETRIES is given
G_DIETPI-NOTIFY 2 "${G_EXEC_DESC:-$ecommand}, please wait...${G_EXEC_RETRIES:+ ($attempt/$((G_EXEC_RETRIES+1)))}"
[[ $G_EXEC_OUTPUT_COL ]] && echo -ne "$G_EXEC_OUTPUT_COL"
"${acommand[@]}" 2>&1 | tee "$fp_log"
exit_code=${PIPESTATUS[0]}
[[ $G_EXEC_OUTPUT_COL ]] && echo -ne '\e[0m'
# - Else print animated processing message only
else
G_DIETPI-NOTIFY -2 "${G_EXEC_DESC:-$ecommand}${G_EXEC_RETRIES:+ ($attempt/$((G_EXEC_RETRIES+1)))}"
"${acommand[@]}" &> "$fp_log"
exit_code=$?
fi
declare -F G_EXEC_POST_FUNC &> /dev/null && G_EXEC_POST_FUNC
# Override exit code if $G_EXEC_NOFAIL=1 is given
[[ $G_EXEC_NOFAIL == 1 ]] && exit_code=0
### Success: Print OK and exit retry loop
[[ $exit_code == 0 ]] && { G_DIETPI-NOTIFY 0 "${G_EXEC_DESC:-$ecommand}"; break; }
### Error
# Retry non-interactively if current $attempt is <= $G_EXEC_RETRIES
[[ $attempt -le $G_EXEC_RETRIES ]] && { ((attempt++)) && continue; }
# Print command output if not done already
[[ $G_EXEC_OUTPUT != 1 ]] && cat "$fp_log"
# Print FAILED, append raw command string if $G_EXEC_DESC is given
G_DIETPI-NOTIFY 1 "${G_EXEC_DESC:+$G_EXEC_DESC\n - Command: }$ecommand"
# Exit retry loop if $G_EXEC_NOHALT=1 is given
[[ $G_EXEC_NOHALT == 1 ]] && break
# Enter error handler menu loop in interactive mode
local last_whip_menu_item sent_bug_report fp_error_report='/tmp/G_EXEC_ERROR_REPORT' dietpi_version="v$G_DIETPI_VERSION_CORE.$G_DIETPI_VERSION_SUB.$G_DIETPI_VERSION_RC ($G_GITOWNER/$G_GITBRANCH)"
[[ $G_INTERACTIVE == 1 ]] && while :
do
G_WHIP_MENU_ARRAY=('Retry' ': Re-run the last command that failed')
# Add targeted solution suggestions, passed via $G_EXEC_ARRAY_TEXT[] and $G_EXEC_ARRAY_ACTION[${G_EXEC_ARRAY_TEXT[]}]
[[ $G_EXEC_ARRAY_TEXT ]] && G_WHIP_MENU_ARRAY+=("${G_EXEC_ARRAY_TEXT[@]}")
# Allow to open DietPi-Config if this error was not produced within DietPi-Config
pgrep -cf 'dietpi-config' &> /dev/null || G_WHIP_MENU_ARRAY+=('DietPi-Config' ': Edit network, APT/NTP mirror settings etc')
G_WHIP_MENU_ARRAY+=('Open subshell' ': Open a subshell to investigate or solve the issue')
# Allow to send bug report if it was produced by one of our scripts, excluding DietPi-Installer
[[ ${G_PROGRAM_NAME,,} == 'dietpi-'* && $G_PROGRAM_NAME != 'DietPi-Installer' ]] && G_WHIP_MENU_ARRAY+=('Send report' ': Upload bug report including system info to DietPi')
# Allow to print bug report template if it was produced by one of our scripts
[[ ${G_PROGRAM_NAME,,} == 'dietpi-'* ]] && G_WHIP_MENU_ARRAY+=('Print report' ': Print bug report template for GitHub or forum')
G_WHIP_MENU_ARRAY+=('' '●─ Devs only ')
G_WHIP_MENU_ARRAY+=('Change command' ': Adjust and rerun the command')
# Show "Ignore" on cancel button if $G_EXEC_NOEXIT=1 is given, else "Exit"
[[ $G_EXEC_NOEXIT == 1 ]] && G_WHIP_BUTTON_CANCEL_TEXT='Ignore' || G_WHIP_BUTTON_CANCEL_TEXT='Exit'
G_WHIP_DEFAULT_ITEM=${last_whip_menu_item:-Retry}
G_WHIP_MENU "${G_EXEC_DESC:+$(mawk '{gsub("\\\e[[0-9][;0-9]*m","");print}' <<< "$G_EXEC_DESC")\n} - Command: ${acommand[*]}
- Exit code: $exit_code
- DietPi version: $dietpi_version | HW_MODEL: $G_HW_MODEL | HW_ARCH: $G_HW_ARCH | DISTRO: $G_DISTRO
- Error log:
$(<"$fp_log")" || break # Exit error handler menu loop on cancel
last_whip_menu_item=$G_WHIP_RETURNED_VALUE
if [[ $G_WHIP_RETURNED_VALUE == 'Retry' ]]; then
# Reset current $attempt and continue retry loop
attempt=1
continue 2
elif [[ $G_WHIP_RETURNED_VALUE == 'DietPi-Config' ]]; then
/boot/dietpi/dietpi-config
elif [[ $G_WHIP_RETURNED_VALUE == 'Open subshell' ]]; then
G_WHIP_MSG 'A bash subshell will now open which allows you to investigate and/or fix the issue.
\nPlease use the "exit" command when you are finished, to return to this error handler menu.'
# Prevent dietpi-login call in subshell
local reallow_dietpi_login=1
[[ $G_DIETPI_LOGIN ]] && reallow_dietpi_login=0
export G_DIETPI_LOGIN=1
bash &> /dev/tty < /dev/tty
(( $reallow_dietpi_login )) && unset -v G_DIETPI_LOGIN
elif [[ $G_WHIP_RETURNED_VALUE == 'Send report' ]]; then
# Store error details to append to bug report upload
G_BUG_REPORT -c "${acommand[*]}" -? "$exit_code" > "$fp_error_report"
# Send report
/boot/dietpi/dietpi-bugreport 1 && sent_bug_report=1
read -rp '
Press any key to continue...'
elif [[ $G_WHIP_RETURNED_VALUE == 'Print report' ]]; then
echo -e '\e[41m---------------------------------------------------------------------
- DietPi bug report template for GitHub or forum -
- Please report at: https://github.com/MichaIng/DietPi/issues -
or: https://dietpi.com/forum/c/troubleshooting/10 -
- Copy and paste ONLY the BLUE lines below, replacing the template! -
---------------------------------------------------------------------\e[44m'