-
-
Notifications
You must be signed in to change notification settings - Fork 126
/
Copy pathpadd.sh
executable file
·1911 lines (1627 loc) · 77.4 KB
/
padd.sh
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
#!/usr/bin/env sh
# shellcheck disable=SC1091
# Ignore warning about `local` being undefinded in POSIX
# shellcheck disable=SC3043
# https://github.com/koalaman/shellcheck/wiki/SC3043#exceptions
# PADD
# A more advanced version of the chronometer provided with Pihole
# SETS LOCALE
export LC_ALL=C
export LC_NUMERIC=C
############################################ VARIABLES #############################################
# VERSION
padd_version="v4.0.0"
# LastChecks
LastCheckPADDInformation=$(date +%s)
LastCheckFullInformation=$(date +%s)
LastCheckNetworkInformation=$(date +%s)
# padd_data holds the data returned by FTL's /padd endpoint globally
padd_data=""
# COLORS
CSI="$(printf '\033')[" # Control Sequence Introducer
red_text="${CSI}91m" # Red
green_text="${CSI}92m" # Green
yellow_text="${CSI}93m" # Yellow
blue_text="${CSI}94m" # Blue
magenta_text="${CSI}95m" # Magenta
cyan_text="${CSI}96m" # Cyan
reset_text="${CSI}0m" # Reset to default
clear_line="${CSI}0K" # Clear the current line to the right to wipe any artifacts remaining from last print
# STYLES
bold_text="${CSI}1m"
blinking_text="${CSI}5m"
dim_text="${CSI}2m"
# CHECK BOXES
check_box_good="[${green_text}✓${reset_text}]" # Good
check_box_bad="[${red_text}✗${reset_text}]" # Bad
check_box_disabled="[${blue_text}-${reset_text}]" # Disabled, but not an error
check_box_question="[${yellow_text}?${reset_text}]" # Question / ?
check_box_info="[${yellow_text}i${reset_text}]" # Info / i
# PICO STATUSES
pico_status_ok="${check_box_good} Sys. OK"
pico_status_update="${check_box_info} Update"
pico_status_hot="${check_box_bad} Sys. Hot!"
pico_status_off="${check_box_info} No blck"
pico_status_ftl_down="${check_box_bad} No CXN"
pico_status_dns_down="${check_box_bad} DNS Down"
# MINI STATUS
mini_status_ok="${check_box_good} System OK"
mini_status_update="${check_box_info} Update avail."
mini_status_hot="${check_box_bad} System is hot!"
mini_status_off="${check_box_info} No blocking!"
mini_status_ftl_down="${check_box_bad} No connection!"
mini_status_dns_down="${check_box_bad} DNS off!"
# REGULAR STATUS
full_status_ok="${check_box_good} System is healthy"
full_status_update="${check_box_info} Updates are available"
full_status_hot="${check_box_bad} System is hot!"
full_status_off="${check_box_info} Blocking is disabled"
full_status_ftl_down="${check_box_bad} No connection!"
full_status_dns_down="${check_box_bad} DNS is off!"
# MEGA STATUS
mega_status_ok="${check_box_good} Your system is healthy"
mega_status_update="${check_box_info} Updates are available"
mega_status_hot="${check_box_bad} Your system is hot!"
mega_status_off="${check_box_info} Blocking is disabled!"
mega_status_ftl_down="${check_box_bad} No connection to FTL!"
mega_status_dns_down="${check_box_bad} Pi-hole's DNS server is off!"
# TINY STATUS
tiny_status_ok="${check_box_good} System is healthy"
tiny_status_update="${check_box_info} Updates are available"
tiny_status_hot="${check_box_bad} System is hot!"
tiny_status_off="${check_box_info} Blocking is disabled"
tiny_status_ftl_down="${check_box_bad} No connection to FTL!"
tiny_status_dns_down="${check_box_bad} DNS is off!"
# Text only "logos"
padd_text="${green_text}${bold_text}PADD${reset_text}"
# PADD logos - regular and retro
padd_logo_1="${bold_text}${green_text} __ __ __ ${reset_text}"
padd_logo_2="${bold_text}${green_text}|__) /\\ | \\| \\ ${reset_text}"
padd_logo_3="${bold_text}${green_text}| /--\\|__/|__/ ${reset_text}"
padd_logo_retro_1="${bold_text} ${yellow_text}_${green_text}_ ${blue_text}_${magenta_text}_ ${yellow_text}_${green_text}_ ${reset_text}"
padd_logo_retro_2="${bold_text}${yellow_text}|${green_text}_${blue_text}_${cyan_text}) ${red_text}/${yellow_text}\\ ${blue_text}| ${red_text}\\${yellow_text}| ${cyan_text}\\ ${reset_text}"
padd_logo_retro_3="${bold_text}${green_text}| ${red_text}/${yellow_text}-${green_text}-${blue_text}\\${cyan_text}|${magenta_text}_${red_text}_${yellow_text}/${green_text}|${blue_text}_${cyan_text}_${magenta_text}/ ${reset_text}"
############################################# FTL ##################################################
TestAPIAvailability() {
local chaos_api_list authResponse cmdResult digReturnCode authStatus authData
# Query the API URLs from FTL using CHAOS TXT
# The result is a space-separated enumeration of full URLs
# e.g., "http://localhost:80/api" or "https://domain.com:443/api"
if [ -z "${SERVER}" ] || [ "${SERVER}" = "localhost" ] || [ "${SERVER}" = "127.0.0.1" ]; then
# --server was not set or set to local, assuming we're running locally
cmdResult="$(dig +short chaos txt local.api.ftl @localhost 2>&1; echo $?)"
else
# --server was set, try to get response from there
cmdResult="$(dig +short chaos txt domain.api.ftl @"${SERVER}" 2>&1; echo $?)"
fi
# Gets the return code of the dig command (last line)
# We can't use${cmdResult##*$'\n'*} here as $'..' is not POSIX
digReturnCode="$(echo "${cmdResult}" | tail -n 1)"
if [ ! "${digReturnCode}" = "0" ]; then
# If the query was not successful
moveXOffset; echo "API not available. Please check server address and connectivity"
exit 1
else
# Dig returned 0 (success), so get the actual response (first line)
chaos_api_list="$(echo "${cmdResult}" | head -n 1)"
fi
# Iterate over space-separated list of URLs
while [ -n "${chaos_api_list}" ]; do
# Get the first URL
API_URL="${chaos_api_list%% *}"
# Strip leading and trailing quotes
API_URL="${API_URL%\"}"
API_URL="${API_URL#\"}"
# Test if the API is available at this URL
authResponse=$(curl --connect-timeout 2 -skS -w "%{http_code}" "${API_URL}auth")
# authStatus are the last 3 characters
# not using ${authResponse#"${authResponse%???}"}" here because it's extremely slow on big responses
authStatus=$(printf "%s" "${authResponse}" | tail -c 3)
# data is everything from response without the last 3 characters
authData=$(printf %s "${authResponse%???}")
# Test if http status code was 200 (OK) or 401 (authentication required)
if [ ! "${authStatus}" = 200 ] && [ ! "${authStatus}" = 401 ]; then
# API is not available at this port/protocol combination
API_PORT=""
else
# API is available at this URL combination
if [ "${authStatus}" = 200 ]; then
# API is available without authentication
needAuth=false
fi
# Check if 2FA is required
needTOTP=$(echo "${authData}"| jq --raw-output .session.totp 2>/dev/null)
break
fi
# Remove the first URL from the list
local last_api_list
last_api_list="${chaos_api_list}"
chaos_api_list="${chaos_api_list#* }"
# If the list did not change, we are at the last element
if [ "${last_api_list}" = "${chaos_api_list}" ]; then
# Remove the last element
chaos_api_list=""
fi
done
# if API_PORT is empty, no working API port was found
if [ -n "${API_PORT}" ]; then
moveXOffset; echo "API not available at: ${API_URL}"
moveXOffset; echo "Exiting."
exit 1
fi
}
LoginAPI() {
# Exit early if no authentication is required
if [ "${needAuth}" = false ]; then
moveXOffset; echo "No authentication required."
return
fi
# Try to read the CLI password (if enabled and readable by the current user)
if [ -r /etc/pihole/cli_pw ]; then
password=$(cat /etc/pihole/cli_pw)
# If we can read the CLI password, we can skip 2FA even when it's required otherwise
needTOTP=false
fi
if [ -z "${password}" ]; then
# no password was supplied as argument or read from CLI file
moveXOffset; echo "No password supplied. Please enter your password:"
# secretly read the password
moveXOffset; secretRead; printf '\n'
fi
if [ "${needTOTP}" = true ] && [ -z "${totp}" ]; then
# 2FA required, but no TOTP was supplied as argument
moveXOffset; echo "Please enter the correct second factor."
moveXOffset; echo "(Can be any number if you used the app password)"
moveXOffset; read -r totp
fi
# Try to authenticate using the supplied password (CLI file, argument or user input) and TOTP
Authenticate
# Try to login again until the session is valid
while [ ! "${validSession}" = true ] ; do
moveXOffset; echo "Authentication failed."
# Print the error message if there is one
if [ ! "${sessionError}" = "null" ]; then
moveXOffset; echo "Error: ${sessionError}"
fi
# Print the session message if there is one
if [ ! "${sessionMessage}" = "null" ]; then
moveXOffset; echo "Error: ${sessionMessage}"
fi
moveXOffset; echo "Please enter the correct password:"
# secretly read the password
moveXOffset; secretRead; printf '\n'
if [ "${needTOTP}" = true ]; then
moveXOffset; echo "Please enter the correct second factor:"
moveXOffset; echo "(Can be any number if you used the app password)"
moveXOffset; read -r totp
fi
# Try to authenticate again
Authenticate
done
# Loop exited, authentication was successful
moveXOffset; echo "Authentication successful."
}
DeleteSession() {
# if a valid Session exists (no password required or successful authenthication) and
# SID is not null (successful authenthication only), delete the session
if [ "${validSession}" = true ] && [ ! "${SID}" = null ]; then
# Try to delete the session. Omit the output, but get the http status code
deleteResponse=$(curl --connect-timeout 2 -skS -o /dev/null -w "%{http_code}" -X DELETE "${API_URL}auth" -H "Accept: application/json" -H "sid: ${SID}")
printf "\n\n"
case "${deleteResponse}" in
"204") moveXOffset; printf "%b" "Session successfully deleted.\n";;
"401") moveXOffset; printf "%b" "Logout attempt without a valid session. Unauthorized!\n";;
esac;
else
# no session to delete, just print a newline for nicer output
echo
fi
}
Authenticate() {
sessionResponse="$(curl --connect-timeout 2 -skS -X POST "${API_URL}auth" --user-agent "PADD ${padd_version}" --data "{\"password\":\"${password}\", \"totp\":${totp:-null}}" )"
if [ -z "${sessionResponse}" ]; then
moveXOffset; echo "No response from FTL server. Please check connectivity and use the options to set the API URL"
moveXOffset; echo "Usage: $0 [--server <domain|IP>]"
exit 1
fi
# obtain validity, session ID and sessionMessage from session response
validSession=$(echo "${sessionResponse}"| jq .session.valid 2>/dev/null)
SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null)
sessionMessage=$(echo "${sessionResponse}"| jq --raw-output .session.message 2>/dev/null)
# obtain the error message from the session response
sessionError=$(echo "${sessionResponse}"| jq --raw-output .error.message 2>/dev/null)
}
GetFTLData() {
local response
local data
local status
# get the data from querying the API as well as the http status code
response=$(curl --connect-timeout 2 -sk -w "%{http_code}" -X GET "${API_URL}$1$2" -H "Accept: application/json" -H "sid: ${SID}" )
# status are the last 3 characters
# not using ${response#"${response%???}"}" here because it's extremely slow on big responses
status=$(printf "%s" "${response}" | tail -c 3)
# data is everything from response without the last 3 characters
data=$(printf %s "${response%???}")
if [ "${status}" = 200 ]; then
echo "${data}"
elif [ "${status}" = 000 ]; then
# connection lost
echo "000"
elif [ "${status}" = 401 ]; then
# unauthorized
echo "401"
fi
}
############################################# GETTERS ##############################################
GetPADDData() {
local response
response=$(GetFTLData "padd" "$1")
if [ "${response}" = 000 ]; then
# connection lost
padd_data="000"
elif [ "${response}" = 401 ]; then
# unauthorized
padd_data="401"
else
# Iterate over all the leaf paths in the JSON object and creates key-value
# pairs in the format "key=value". Nested objects are flattened using the dot
# notation, e.g., { "a": { "b": 1 } } becomes "a.b=1".
# We cannot use leaf_paths here as it was deprecated in jq 1.6 and removed in
# current master
# Using "paths(scalars | true)" will return null and false values.
# We also check if the value is exactly `null` and, in this case, return the
# string "null", as jq would return an empty string for nulls.
padd_data=$(echo "$response" | jq -r 'paths(scalars | true) as $p | [$p | join(".")] + [if getpath($p)!=null then getpath($p) else "null" end] | join("=")' 2>/dev/null)
fi
}
GetPADDValue() {
echo "$padd_data" | sed -n "s/^$1=//p" 2>/dev/null
}
GetSummaryInformation() {
if [ "${connection_down_flag}" = true ]; then
clients="N/A"
blocking_enabled="N/A"
domains_being_blocked="N/A"
dns_queries_today="N/A"
ads_blocked_today="N/A"
ads_percentage_today="N/A"
cache_size="N/A"
cache_evictions="N/A"
cache_inserts="N/A"
latest_blocked_raw="N/A"
top_blocked_raw="N/A"
top_domain_raw="N/A"
top_client_raw="N/A"
return
fi
clients=$(GetPADDValue active_clients)
blocking_enabled=$(GetPADDValue blocking)
domains_being_blocked_raw=$(GetPADDValue gravity_size)
domains_being_blocked=$(printf "%.f" "${domains_being_blocked_raw}")
dns_queries_today_raw=$(GetPADDValue queries.total)
dns_queries_today=$(printf "%.f" "${dns_queries_today_raw}")
ads_blocked_today_raw=$(GetPADDValue queries.blocked)
ads_blocked_today=$(printf "%.f" "${ads_blocked_today_raw}")
ads_percentage_today_raw=$(GetPADDValue queries.percent_blocked)
ads_percentage_today=$(printf "%.1f" "${ads_percentage_today_raw}")
cache_size=$(GetPADDValue cache.size)
cache_evictions=$(GetPADDValue cache.evicted)
cache_inserts=$(echo "${padd_data}"| GetPADDValue cache.inserted)
latest_blocked_raw=$(GetPADDValue recent_blocked)
top_blocked_raw=$(GetPADDValue top_blocked)
top_domain_raw=$(GetPADDValue top_domain)
top_client_raw=$(GetPADDValue top_client)
}
GetSystemInformation() {
if [ "${connection_down_flag}" = true ]; then
system_uptime_raw=0
temperature="N/A"
temp_heatmap=${reset_text}
cpu_load_1="N/A"
cpu_load_5="N/A"
cpu_load_15="N/A"
cpu_load_1_heatmap=${reset_text}
cpu_load_5_heatmap=${reset_text}
cpu_load_15_heatmap=${reset_text}
cpu_percent=0
memory_percent=0
memory_heatmap=${reset_text}
sys_model="N/A"
return
fi
# System uptime
system_uptime_raw=$(GetPADDValue system.uptime)
# CPU temperature and unit
cpu_temp_raw=$(GetPADDValue sensors.cpu_temp)
cpu_temp=$(printf "%.1f" "${cpu_temp_raw}")
temp_unit=$(echo "${padd_data}" | GetPADDValue sensors.unit)
# Temp + Unit
if [ "${temp_unit}" = "C" ]; then
temperature="${cpu_temp}°${temp_unit}"
# no conversion needed
cpu_temp_celsius="$(echo "${cpu_temp}" | awk -F '.' '{print $1}')"
elif [ "${temp_unit}" = "F" ]; then
temperature="${cpu_temp}°${temp_unit}"
# convert to Celsius for limit checking
cpu_temp_celsius="$(echo "${cpu_temp}" | awk '{print ($1-32) * 5 / 9}' | awk -F '.' '{print $1}')"
elif [ "${temp_unit}" = "K" ]; then
# no ° for Kelvin
temperature="${cpu_temp}${temp_unit}"
# convert to Celsius for limit checking
cpu_temp_celsius="$(echo "${cpu_temp}" | awk '{print $1 - 273.15}' | awk -F '.' '{print $1}')"
else # unknown unit
temperature="${cpu_temp}°?"
# no conversion needed
cpu_temp_celsius=0
fi
# CPU temperature heatmap
hot_flag=false
# If we're getting close to 85°C... (https://www.raspberrypi.org/blog/introducing-turbo-mode-up-to-50-more-performance-for-free/)
if [ "${cpu_temp_celsius}" -gt 80 ]; then
temp_heatmap=${blinking_text}${red_text}
# set flag to change the status message in SetStatusMessage()
hot_flag=true
elif [ "${cpu_temp_celsius}" -gt 70 ]; then
temp_heatmap=${magenta_text}
elif [ "${cpu_temp_celsius}" -gt 60 ]; then
temp_heatmap=${blue_text}
else
temp_heatmap=${cyan_text}
fi
# CPU, load, heatmap
core_count=$(GetPADDValue system.cpu.nprocs)
cpu_load_1=$(printf %.2f "$(GetPADDValue system.cpu.load.raw.[0])")
cpu_load_5=$(printf %.2f "$(GetPADDValue system.cpu.load.raw.[1])")
cpu_load_15=$(printf %.2f "$(GetPADDValue system.cpu.load.raw.[2])")
cpu_load_1_heatmap=$(HeatmapGenerator "${cpu_load_1}" "${core_count}")
cpu_load_5_heatmap=$(HeatmapGenerator "${cpu_load_5}" "${core_count}")
cpu_load_15_heatmap=$(HeatmapGenerator "${cpu_load_15}" "${core_count}")
cpu_percent=$(printf %.1f "$(GetPADDValue system.cpu.load.percent.0)")
# Memory use, heatmap and bar
memory_percent_raw="$(GetPADDValue system.memory.ram.%used)"
memory_percent=$(printf %.1f "${memory_percent_raw}")
memory_heatmap="$(HeatmapGenerator "${memory_percent}")"
# Get device model
sys_model="$(GetPADDValue host_model)"
# DOCKER_VERSION is set during GetVersionInformation, so this needs to run first during startup
if [ ! "${DOCKER_VERSION}" = "null" ]; then
# Docker image
sys_model="Container"
fi
# Cleaning device model from useless OEM information
sys_model=$(filterModel "${sys_model}")
# FTL returns null if device information is not available
if [ -z "$sys_model" ] || [ "$sys_model" = "null" ]; then
sys_model="N/A"
fi
}
GetNetworkInformation() {
if [ "${connection_down_flag}" = true ]; then
iface_name="N/A"
pi_ip4_addr="N/A"
pi_ip6_addr="N/A"
ipv6_status="N/A"
ipv6_heatmap=${reset_text}
ipv6_check_box=${check_box_question}
dhcp_status="N/A"
dhcp_heatmap=${reset_text}
dhcp_range="N/A"
dhcp_range_heatmap=${reset_text}
dhcp_ipv6_status="N/A"
dhcp_ipv6_heatmap=${reset_text}
pi_hostname="N/A"
full_hostname="N/A"
dns_count="N/A"
dns_information="N/A"
dnssec_status="N/A"
dnssec_heatmap=${reset_text}
conditional_forwarding_status="N/A"
conditional_forwarding_heatmap=${reset_text}
tx_bytes="N/A"
rx_bytes="N/A"
return
fi
gateway_v4_iface=$(GetPADDValue iface.v4.name)
gateway_v6_iface=$(GetPADDValue iface.v4.name)
# Get IPv4 address of the default interface
pi_ip4_addrs="$(GetPADDValue iface.v4.num_addrs)"
pi_ip4_addr="$(GetPADDValue iface.v4.addr)"
if [ "${pi_ip4_addrs}" -eq 0 ]; then
# No IPv4 address available
pi_ip4_addr="N/A"
elif [ "${pi_ip4_addrs}" -eq 1 ]; then
# One IPv4 address available
: # Do nothing as the address is already set
else
# More than one IPv4 address available
pi_ip4_addr="${pi_ip4_addr}+"
fi
# Get IPv6 address of the default interface
pi_ip6_addrs="$(GetPADDValue iface.v6.num_addrs)"
pi_ip6_addr="$(GetPADDValue iface.v6.addr)"
if [ "${pi_ip6_addrs}" -eq 0 ]; then
# No IPv6 address available
pi_ip6_addr="N/A"
ipv6_check_box=${check_box_disabled}
ipv6_status="Disabled"
ipv6_heatmap=${blue_text}
elif [ "${pi_ip6_addrs}" -eq 1 ]; then
# One IPv6 address available
ipv6_check_box=${check_box_good}
ipv6_status="Enabled"
ipv6_heatmap=${green_text}
else
# More than one IPv6 address available
pi_ip6_addr="${pi_ip6_addr}+"
ipv6_check_box=${check_box_good}
ipv6_status="Enabled"
ipv6_heatmap=${green_text}
fi
# Is Pi-Hole acting as the DHCP server?
DHCP_ACTIVE="$(GetPADDValue config.dhcp_active )"
if [ "${DHCP_ACTIVE}" = "true" ]; then
DHCP_START="$(GetPADDValue config.dhcp_start)"
DHCP_END="$(GetPADDValue config.dhcp_end)"
dhcp_status="Enabled"
dhcp_range="${DHCP_START} - ${DHCP_END}"
dhcp_range_heatmap=${reset_text}
dhcp_heatmap=${green_text}
dhcp_check_box=${check_box_good}
# Is DHCP handling IPv6?
DHCP_IPv6="$(GetPADDValue config.dhcp_ipv6)"
if [ "${DHCP_IPv6}" = "true" ]; then
dhcp_ipv6_status="Enabled"
dhcp_ipv6_heatmap=${green_text}
else
dhcp_ipv6_status="Disabled"
dhcp_ipv6_heatmap=${blue_text}
fi
else
dhcp_status="Disabled"
dhcp_heatmap=${blue_text}
dhcp_check_box=${check_box_disabled}
dhcp_range="N/A"
dhcp_ipv6_status="N/A"
dhcp_range_heatmap=${yellow_text}
dhcp_ipv6_heatmap=${yellow_text}
fi
# Get hostname
pi_hostname="$(GetPADDValue node_name)"
full_hostname=${pi_hostname}
# when PI-hole is the DHCP server, append the domain to the hostname
if [ "${DHCP_ACTIVE}" = "true" ]; then
PIHOLE_DOMAIN="$(GetPADDValue config.dns_domain)"
if [ -n "${PIHOLE_DOMAIN}" ]; then
count=${pi_hostname}"."${PIHOLE_DOMAIN}
count=${#count}
if [ "${count}" -lt "18" ]; then
full_hostname=${pi_hostname}"."${PIHOLE_DOMAIN}
fi
fi
fi
# Get the number of configured upstream DNS servers
dns_count="$(GetPADDValue config.dns_num_upstreams)"
# if there's only one DNS server
if [ "${dns_count}" -eq 1 ]; then
dns_information="1 server"
else
dns_information="${dns_count} servers"
fi
# DNSSEC
DNSSEC="$(GetPADDValue config.dns_dnssec)"
if [ "${DNSSEC}" = "true" ]; then
dnssec_status="Enabled"
dnssec_heatmap=${green_text}
else
dnssec_status="Disabled"
dnssec_heatmap=${blue_text}
fi
# Conditional forwarding
CONDITIONAL_FORWARDING="$(GetPADDValue config.dns_revServer_active)"
if [ "${CONDITIONAL_FORWARDING}" = "true" ]; then
conditional_forwarding_status="Enabled"
conditional_forwarding_heatmap=${green_text}
else
conditional_forwarding_status="Disabled"
conditional_forwarding_heatmap=${blue_text}
fi
# Default interface data (use IPv4 interface - we cannot show both and assume they are the same)
iface_name="${gateway_v4_iface}"
tx_bytes="$(GetPADDValue iface.v4.tx_bytes.value)"
tx_bytes_unit="$(GetPADDValue iface.v4.tx_bytes.unit)"
tx_bytes=$(printf "%.1f %b" "${tx_bytes}" "${tx_bytes_unit}")
rx_bytes="$(GetPADDValue iface.v4.rx_bytes.value)"
rx_bytes_unit="$(GetPADDValue iface.v4.rx_bytes.unit)"
rx_bytes=$(printf "%.1f %b" "${rx_bytes}" "${rx_bytes_unit}")
# If IPv4 and IPv6 interfaces are not the same, add a "*" to the interface
# name to highlight that there are two different interfaces and the
# displayed statistics are only for the IPv4 interface, while the IPv6
# address correctly corresponds to the default IPv6 interface
if [ ! "${gateway_v4_iface}" = "${gateway_v6_iface}" ]; then
iface_name="${iface_name}*"
fi
}
GetPiholeInformation() {
if [ "${connection_down_flag}" = true ]; then
ftl_status="No connection"
ftl_heatmap=${red_text}
ftl_check_box=${check_box_bad}
ftl_cpu="N/A"
ftl_mem_percentage="N/A"
dns_status="DNS offline"
dns_heatmap=${red_text}
dns_check_box=${check_box_bad}
ftlPID="N/A"
dns_down_flag=true
return
fi
ftl_status="Running"
ftl_heatmap=${green_text}
ftl_check_box=${check_box_good}
# Get FTL CPU and memory usage
ftl_cpu_raw="$(GetPADDValue "%cpu")"
ftl_mem_percentage_raw="$(GetPADDValue "%mem")"
ftl_cpu="$(printf "%.1f" "${ftl_cpu_raw}")%"
ftl_mem_percentage="$(printf "%.1f" "${ftl_mem_percentage_raw}")%"
# Get Pi-hole (blocking) status
ftl_dns_port=$(GetPADDValue config.dns_port)
# Get FTL's current PID
ftlPID="$(GetPADDValue pid)"
# ${ftl_dns_port} == 0 DNS server part of dnsmasq disabled
dns_down_flag=false
if [ "${ftl_dns_port}" = 0 ]; then
dns_status="DNS offline"
dns_heatmap=${red_text}
dns_check_box=${check_box_bad}
# set flag to change the status message in SetStatusMessage()
dns_down_flag=true
else
dns_check_box=${check_box_good}
dns_status="Active"
dns_heatmap=${green_text}
fi
}
GetVersionInformation() {
if [ "${connection_down_flag}" = true ]; then
DOCKER_VERSION=null
CORE_VERSION="N/A"
WEB_VERSION="N/A"
FTL_VERSION="N/A"
core_version_heatmap=${reset_text}
web_version_heatmap=${reset_text}
ftl_version_heatmap=${reset_text}
return
fi
out_of_date_flag=false
# Gather DOCKER version information...
# returns "null" if not running Pi-hole in Docker container
DOCKER_VERSION="$(GetPADDValue version.docker.local)"
# If PADD is running inside docker, immediately return without checking for updated component versions
if [ ! "${DOCKER_VERSION}" = "null" ] ; then
GITHUB_DOCKER_VERSION="$(GetPADDValue version.docker.remote)"
docker_version_converted="$(VersionConverter "${DOCKER_VERSION}")"
docker_version_latest_converted="$(VersionConverter "${GITHUB_DOCKER_VERSION}")"
# Note: the version comparison will fail for any Docker tag not following a 'YYYY.MM.VV' scheme
# e.g. 'nightly', 'beta', 'v6-pre-alpha' and might set a false out_of_date_flag
# As those versions are not meant to be used in production, we ignore this small bug
if [ "${docker_version_converted}" -lt "${docker_version_latest_converted}" ]; then
out_of_date_flag="true"
docker_version_heatmap=${red_text}
else
docker_version_heatmap=${green_text}
fi
return
fi
# Gather core version information...
CORE_BRANCH="$(GetPADDValue version.core.local.branch)"
CORE_VERSION="$(GetPADDValue version.core.local.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')"
GITHUB_CORE_VERSION="$(GetPADDValue version.core.remmote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')"
CORE_HASH="$(GetPADDValue version.core.local.hash)"
GITHUB_CORE_HASH="$(GetPADDValue version.core.remote.hash)"
if [ "${CORE_BRANCH}" = "master" ]; then
core_version_converted="$(VersionConverter "${CORE_VERSION}")"
core_version_latest_converted=$(VersionConverter "${GITHUB_CORE_VERSION}")
if [ "${core_version_converted}" -lt "${core_version_latest_converted}" ]; then
out_of_date_flag="true"
core_version_heatmap=${red_text}
else
core_version_heatmap=${green_text}
fi
else
# Custom branch
if [ -z "${CORE_BRANCH}" ]; then
# Branch name is empty, something went wrong
core_version_heatmap=${red_text}
CORE_VERSION="?"
else
if [ "${CORE_HASH}" = "${GITHUB_CORE_HASH}" ]; then
# up-to-date
core_version_heatmap=${green_text}
else
# out-of-date
out_of_date_flag="true"
core_version_heatmap=${red_text}
fi
# shorten common branch names (fix/, tweak/, new/)
# use the first 7 characters of the branch name as version
CORE_VERSION="$(printf '%s' "$CORE_BRANCH" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)"
fi
fi
# Gather web version information...
WEB_VERSION="$(GetPADDValue version.web.local.version)"
if [ ! "$WEB_VERSION" = "null" ]; then
WEB_BRANCH="$(GetPADDValue version.web.local.branch)"
WEB_VERSION="$(GetPADDValue version.web.local.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')"
GITHUB_WEB_VERSION="$(GetPADDValue version.web.remmote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')"
WEB_HASH="$(GetPADDValue version.web.local.hash)"
GITHUB_WEB_HASH="$(GetPADDValue version.web.remote.hash)"
if [ "${WEB_BRANCH}" = "master" ]; then
web_version_converted="$(VersionConverter "${WEB_VERSION}")"
web_version_latest_converted=$(VersionConverter "${GITHUB_WEB_VERSION}")
if [ "${web_version_converted}" -lt "${web_version_latest_converted}" ]; then
out_of_date_flag="true"
web_version_heatmap=${red_text}
else
web_version_heatmap=${green_text}
fi
else
# Custom branch
if [ -z "${WEB_BRANCH}" ]; then
# Branch name is empty, something went wrong
web_version_heatmap=${red_text}
WEB_VERSION="?"
else
if [ "${WEB_HASH}" = "${GITHUB_WEB_HASH}" ]; then
# up-to-date
web_version_heatmap=${green_text}
else
# out-of-date
out_of_date_flag="true"
web_version_heatmap=${red_text}
fi
# shorten common branch names (fix/, tweak/, new/)
# use the first 7 characters of the branch name as version
WEB_VERSION="$(printf '%s' "$WEB_BRANCH" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)"
fi
fi
else
# Web interface not installed
WEB_VERSION="N/A"
web_version_heatmap=${yellow_text}
fi
# Gather FTL version information...
FTL_BRANCH="$(GetPADDValue version.ftl.local.branch)"
FTL_VERSION="$(GetPADDValue version.ftl.local.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')"
GITHUB_FTL_VERSION="$(GetPADDValue version.ftl.remmote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')"
FTL_HASH="$(GetPADDValue version.ftl.local.hash)"
GITHUB_FTL_HASH="$(GetPADDValue version.ftl.remote.hash)"
if [ "${FTL_BRANCH}" = "master" ]; then
ftl_version_converted="$(VersionConverter "${FTL_VERSION}")"
ftl_version_latest_converted=$(VersionConverter "${GITHUB_FTL_VERSION}")
if [ "${ftl_version_converted}" -lt "${ftl_version_latest_converted}" ]; then
out_of_date_flag="true"
ftl_version_heatmap=${red_text}
else
ftl_version_heatmap=${green_text}
fi
else
# Custom branch
if [ -z "${FTL_BRANCH}" ]; then
# Branch name is empty, something went wrong
ftl_version_heatmap=${red_text}
FTL_VERSION="?"
else
if [ "${FTL_HASH}" = "${GITHUB_FTL_HASH}" ]; then
# up-to-date
ftl_version_heatmap=${green_text}
else
# out-of-date
out_of_date_flag="true"
ftl_version_heatmap=${red_text}
fi
# shorten common branch names (fix/, tweak/, new/)
# use the first 7 characters of the branch name as version
FTL_VERSION="$(printf '%s' "$FTL_BRANCH" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)"
fi
fi
}
GetPADDInformation() {
# If PADD is running inside docker, immediately return without checking for an update
if [ ! "${DOCKER_VERSION}" = "null" ]; then
return
fi
# PADD version information...
padd_version_latest="$(curl --connect-timeout 5 --silent https://api.github.com/repos/pi-hole/PADD/releases/latest | grep '"tag_name":' | awk -F \" '{print $4}')"
# is PADD up-to-date?
padd_out_of_date_flag=false
if [ -z "${padd_version_latest}" ]; then
padd_version_heatmap=${yellow_text}
else
padd_version_latest_converted="$(VersionConverter "${padd_version_latest}")"
padd_version_converted=$(VersionConverter "${padd_version}")
if [ "${padd_version_converted}" -lt "${padd_version_latest_converted}" ]; then
padd_out_of_date_flag="true"
padd_version_heatmap=${red_text}
else
# local and remote PADD version match or local is newer
padd_version_heatmap=${green_text}
fi
fi
}
GenerateSizeDependendOutput() {
if [ "$1" = "pico" ] || [ "$1" = "nano" ]; then
ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 9 "color")
elif [ "$1" = "micro" ]; then
ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 10 "color")
elif [ "$1" = "mini" ]; then
ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 20 "color")
latest_blocked=$(truncateString "$latest_blocked_raw" 29)
top_blocked=$(truncateString "$top_blocked_raw" 29)
elif [ "$1" = "tiny" ]; then
ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 30 "color")
latest_blocked=$(truncateString "$latest_blocked_raw" 41)
top_blocked=$(truncateString "$top_blocked_raw" 41)
top_domain=$(truncateString "$top_domain_raw" 41)
top_client=$(truncateString "$top_client_raw" 41)
elif [ "$1" = "regular" ] || [ "$1" = "slim" ]; then
ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 40 "color")
latest_blocked=$(truncateString "$latest_blocked_raw" 48)
top_blocked=$(truncateString "$top_blocked_raw" 48)
top_domain=$(truncateString "$top_domain_raw" 48)
top_client=$(truncateString "$top_client_raw" 48)
elif [ "$1" = "mega" ]; then
ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 30 "color")
latest_blocked=$(truncateString "$latest_blocked_raw" 68)
top_blocked=$(truncateString "$top_blocked_raw" 68)
top_domain=$(truncateString "$top_domain_raw" 68)
top_client=$(truncateString "$top_client_raw" 68)
fi
# System uptime
if [ "$1" = "pico" ] || [ "$1" = "nano" ] || [ "$1" = "micro" ]; then
system_uptime="$(convertUptime "${system_uptime_raw}" | awk -F ',' '{print $1 "," $2}')"
else
system_uptime="$(convertUptime "${system_uptime_raw}")"
fi
# Bar generations
if [ "$1" = "mini" ]; then
cpu_bar=$(BarGenerator "${cpu_percent}" 20)
memory_bar=$(BarGenerator "${memory_percent}" 20)
elif [ "$1" = "tiny" ]; then
cpu_bar=$(BarGenerator "${cpu_percent}" 7)
memory_bar=$(BarGenerator "${memory_percent}" 7)
else
cpu_bar=$(BarGenerator "${cpu_percent}" 10)
memory_bar=$(BarGenerator "${memory_percent}" 10)
fi
}
SetStatusMessage() {
# depending on which flags are set, the "message field" shows a different output
# 7 messages are possible (from highest to lowest priority):
# - System is hot
# - FTLDNS service is not running
# - Pi-hole's DNS server is off (FTL running, but not providing DNS)
# - Unable to determine Pi-hole blocking status
# - Pi-hole blocking disabled
# - Updates are available
# - Everything is fine
if [ "${hot_flag}" = true ]; then
# Check if CPU temperature is high
pico_status="${pico_status_hot}"
mini_status="${mini_status_hot} ${blinking_text}${red_text}${temperature}${reset_text}"
tiny_status="${tiny_status_hot} ${blinking_text}${red_text}${temperature}${reset_text}"
full_status="${full_status_hot} ${blinking_text}${red_text}${temperature}${reset_text}"
mega_status="${mega_status_hot} ${blinking_text}${red_text}${temperature}${reset_text}"
elif [ "${connection_down_flag}" = true ]; then
# Check if FTL is down
pico_status=${pico_status_ftl_down}
mini_status=${mini_status_ftl_down}
tiny_status=${tiny_status_ftl_down}
full_status=${full_status_ftl_down}
mega_status=${mega_status_ftl_down}
elif [ "${dns_down_flag}" = true ]; then
# Check if DNS is down
pico_status=${pico_status_dns_down}
mini_status=${mini_status_dns_down}
tiny_status=${tiny_status_dns_down}
full_status=${full_status_dns_down}
mega_status=${mega_status_dns_down}
elif [ "${blocking_enabled}" = "disabled" ]; then
# Check if blocking status is disabled
pico_status=${pico_status_off}
mini_status=${mini_status_off}
tiny_status=${tiny_status_off}
full_status=${full_status_off}
mega_status=${mega_status_off}
elif [ "${out_of_date_flag}" = "true" ] || [ "${padd_out_of_date_flag}" = "true" ]; then
# Check if one of the components of Pi-hole (or PADD itself) is out of date
pico_status=${pico_status_update}
mini_status=${mini_status_update}
tiny_status=${tiny_status_update}