1213 lines
45 KiB
Bash
1213 lines
45 KiB
Bash
#!/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="v3.11.0"
|
||
|
||
# LastChecks
|
||
LastCheckVersionInformation=$(date +%s)
|
||
LastCheckNetworkInformation=$(date +%s)
|
||
LastCheckSummaryInformation=$(date +%s)
|
||
LastCheckPiholeInformation=$(date +%s)
|
||
LastCheckSystemInformation=$(date +%s)
|
||
LastCheckPADDInformation=$(date +%s)
|
||
|
||
# CORES
|
||
core_count=$(nproc --all 2> /dev/null)
|
||
|
||
# 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_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} FTL Down"
|
||
pico_status_dns_down="${check_box_bad} DNS Down"
|
||
pico_status_unknown="${check_box_question} Stat. Unk."
|
||
|
||
# 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} FTL down!"
|
||
mini_status_dns_down="${check_box_bad} DNS off!"
|
||
mini_status_unknown="${check_box_question} Status unknown"
|
||
|
||
# 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} FTL is down!"
|
||
full_status_dns_down="${check_box_bad} DNS is off!"
|
||
full_status_unknown="${check_box_question} Status unknown!"
|
||
|
||
# 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} FTLDNS service is not running!"
|
||
mega_status_dns_down="${check_box_bad} Pi-hole's DNS server is off!"
|
||
mega_status_unknown="${check_box_question} Unable to determine Pi-hole status!"
|
||
|
||
# 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} FTL is down!"
|
||
tiny_status_dns_down="${check_box_bad} DNS is off!"
|
||
tiny_status_unknown="${check_box_question} Status unknown!"
|
||
|
||
# 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}"
|
||
|
||
|
||
############################################# GETTERS ##############################################
|
||
|
||
GetFTLData() {
|
||
local ftl_port data
|
||
ftl_port=$(getFTLAPIPort)
|
||
if [ -n "$ftl_port" ]; then
|
||
# Send command to FTL and ask to quit when finished
|
||
data="$(echo ">$1 >quit" | nc 127.0.0.1 "${ftl_port}")"
|
||
echo "${data}"
|
||
fi
|
||
}
|
||
|
||
GetSummaryInformation() {
|
||
summary=$(GetFTLData "stats")
|
||
cache_info=$(GetFTLData "cacheinfo")
|
||
|
||
clients=$(echo "${summary}" | grep "unique_clients" | grep -Eo "[0-9]+$")
|
||
|
||
blocking_status=$(echo "${summary}" | grep "status" | grep -Eo "enabled|disabled|unknown" )
|
||
|
||
domains_being_blocked_raw=$(echo "${summary}" | grep "domains_being_blocked" | grep -Eo "[0-9]+$")
|
||
domains_being_blocked=$(printf "%.f" "${domains_being_blocked_raw}")
|
||
|
||
dns_queries_today_raw=$(echo "$summary" | grep "dns_queries_today" | grep -Eo "[0-9]+$")
|
||
dns_queries_today=$(printf "%.f" "${dns_queries_today_raw}")
|
||
|
||
ads_blocked_today_raw=$(echo "$summary" | grep "ads_blocked_today" | grep -Eo "[0-9]+$")
|
||
ads_blocked_today=$(printf "%.f" "${ads_blocked_today_raw}")
|
||
|
||
ads_percentage_today_raw=$(echo "$summary" | grep "ads_percentage_today" | grep -Eo "[0-9.]+$")
|
||
ads_percentage_today=$(printf "%.1f" "${ads_percentage_today_raw}")
|
||
|
||
cache_size=$(echo "$cache_info" | grep "cache-size" | grep -Eo "[0-9.]+$")
|
||
cache_deletes=$(echo "$cache_info" | grep "cache-live-freed" | grep -Eo "[0-9.]+$")
|
||
cache_inserts=$(echo "$cache_info"| grep "cache-inserted" | grep -Eo "[0-9.]+$")
|
||
|
||
latest_blocked_raw=$(GetFTLData recentBlocked)
|
||
|
||
top_blocked_raw=$(GetFTLData "top-ads (1)" | awk '{print $3}')
|
||
|
||
top_domain_raw=$(GetFTLData "top-domains (1)" | awk '{print $3}')
|
||
|
||
top_client_raw=$(GetFTLData "top-clients (1)" | awk '{print $4}')
|
||
if [ -z "${top_client_raw}" ]; then
|
||
# if no hostname was supplied, use IP
|
||
top_client_raw=$(GetFTLData "top-clients (1)" | awk '{print $3}')
|
||
fi
|
||
}
|
||
|
||
GetSystemInformation() {
|
||
# System uptime
|
||
system_uptime_raw=$(uptime)
|
||
|
||
# CPU temperature
|
||
if [ -d "/sys/devices/platform/coretemp.0/hwmon/" ]; then
|
||
cpu=$(cat "$(find /sys/devices/platform/coretemp.0/hwmon/ -maxdepth 2 -name "temp1_input" 2>/dev/null | head -1)" 2>/dev/null)
|
||
fi
|
||
if [ -z "${cpu}" ] && [ -f /sys/class/thermal/thermal_zone0/temp ]; then
|
||
cpu=$(cat /sys/class/thermal/thermal_zone0/temp)
|
||
fi
|
||
if [ -z "${cpu}" ] && [ -f /sys/class/hwmon/hwmon0/temp1_input ]; then
|
||
cpu=$(cat /sys/class/hwmon/hwmon0/temp1_input)
|
||
fi
|
||
if [ -z "${cpu}" ]; then
|
||
cpu=0
|
||
fi
|
||
|
||
# Convert CPU temperature to correct unit
|
||
if [ "${TEMPERATUREUNIT}" = "F" ]; then
|
||
temperature="$(printf %.1f "$(echo "${cpu}" | awk '{print $1 * 9 / 5000 + 32}')")°F"
|
||
elif [ "${TEMPERATUREUNIT}" = "K" ]; then
|
||
temperature="$(printf %.1f "$(echo "${cpu}" | awk '{print $1 / 1000 + 273.15}')")°K"
|
||
else
|
||
temperature="$(printf %.1f "$(echo "${cpu}" | awk '{print $1 / 1000}')")°C"
|
||
fi
|
||
|
||
# CPU load, heatmap
|
||
cpu_load_1=$(awk '{print $1}' < /proc/loadavg)
|
||
cpu_load_5=$(awk '{print $2}' < /proc/loadavg)
|
||
cpu_load_15=$(awk '{print $3}' < /proc/loadavg)
|
||
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 "$(echo "${cpu_load_1} ${core_count}" | awk '{print ($1 / $2) * 100}')")
|
||
|
||
# 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} -gt 80000 ]; then
|
||
temp_heatmap=${blinking_text}${red_text}
|
||
# set flag to change the status message in SetStatusMessage()
|
||
hot_flag=true
|
||
elif [ ${cpu} -gt 70000 ]; then
|
||
temp_heatmap=${magenta_text}
|
||
elif [ ${cpu} -gt 60000 ]; then
|
||
temp_heatmap=${blue_text}
|
||
else
|
||
temp_heatmap=${cyan_text}
|
||
fi
|
||
|
||
# Memory use, heatmap and bar
|
||
memory_percent=$(awk '/MemTotal:/{total=$2} /MemFree:/{free=$2} /Buffers:/{buffers=$2} /^Cached:/{cached=$2} END {printf "%.1f", (total-free-buffers-cached)*100/total}' '/proc/meminfo')
|
||
memory_heatmap=$(HeatmapGenerator "${memory_percent}")
|
||
|
||
# Get product name and family
|
||
product_name=
|
||
product_family=
|
||
if [ -f /sys/devices/virtual/dmi/id/product_name ]; then
|
||
# Get product name, remove possible null byte
|
||
product_name=$(tr -d '\0' < /sys/devices/virtual/dmi/id/product_name)
|
||
fi
|
||
if [ -f /sys/devices/virtual/dmi/id/product_family ]; then
|
||
# Get product family, remove possible null byte
|
||
product_family=$(tr -d '\0' < /sys/devices/virtual/dmi/id/product_family)
|
||
fi
|
||
|
||
board_vendor=
|
||
board_name=
|
||
if [ -f /sys/devices/virtual/dmi/id/board_vendor ]; then
|
||
board_vendor=$(tr -d '\0' < /sys/devices/virtual/dmi/id/board_vendor)
|
||
fi
|
||
if [ -f /sys/devices/virtual/dmi/id/board_name ]; then
|
||
board_name="$(tr -d '\0' < /sys/devices/virtual/dmi/id/board_name)"
|
||
fi
|
||
|
||
|
||
if [ -n "$product_name" ] || [ -n "$product_family" ]; then
|
||
if echo "$product_family" | grep -q "$product_name"; then
|
||
# If product_name is contained in product_family, only show product_family
|
||
sys_model="${product_family}"
|
||
else
|
||
# If product_name is not contained in product_family, both are shown
|
||
sys_model="${product_family} ${product_name}"
|
||
fi
|
||
elif [ -f /sys/firmware/devicetree/base/model ]; then
|
||
sys_model=$(tr -d '\0' < /sys/firmware/devicetree/base/model)
|
||
elif [ -n "$board_vendor" ] || [ -n "$board_name" ]; then
|
||
sys_model="${board_vendor} ${board_name}"
|
||
elif [ -f /tmp/sysinfo/model ]; then
|
||
sys_model=$(tr -d '\0' < /tmp/sysinfo/model)
|
||
elif [ -n "${DOCKER_VERSION}" ]; then
|
||
# Docker image. DOCKER_VERSION is read from /etc/pihole/versions
|
||
sys_model="Container"
|
||
fi
|
||
|
||
# Cleaning device model from useless OEM information
|
||
sys_model=$(filterModel "${sys_model}")
|
||
|
||
if [ -z "$sys_model" ]; then
|
||
sys_model="Unknown"
|
||
fi
|
||
}
|
||
|
||
GetNetworkInformation() {
|
||
# Get pi IPv4 address
|
||
pi_ip4_addrs="$(ip addr | grep 'inet ' | grep -v '127.0.0.1/8' | awk '{print $2}' | cut -f1 -d'/' |wc -l)"
|
||
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
|
||
pi_ip4_addr="$(ip addr | grep 'inet ' | grep -v '127.0.0.1/8' | awk '{print $2}' | cut -f1 -d'/' | head -n 1)"
|
||
else
|
||
# More than one IPv4 address available
|
||
pi_ip4_addr="$(ip addr | grep 'inet ' | grep -v '127.0.0.1/8' | awk '{print $2}' | cut -f1 -d'/' | head -n 1)+"
|
||
fi
|
||
|
||
# Get pi IPv6 address
|
||
pi_ip6_addrs="$(ip addr | grep 'inet6 ' | grep -v '::1/128' | awk '{print $2}' | cut -f1 -d'/' | wc -l)"
|
||
if [ "${pi_ip6_addrs}" -eq 0 ]; then
|
||
# No IPv6 address available
|
||
pi_ip6_addr="N/A"
|
||
ipv6_check_box=${check_box_bad}
|
||
elif [ "${pi_ip6_addrs}" -eq 1 ]; then
|
||
# One IPv6 address available
|
||
pi_ip6_addr="$(ip addr | grep 'inet6 ' | grep -v '::1/128' | awk '{print $2}' | cut -f1 -d'/' | head -n 1)"
|
||
ipv6_check_box=${check_box_good}
|
||
else
|
||
# More than one IPv6 address available
|
||
pi_ip6_addr="$(ip addr | grep 'inet6 ' | grep -v '::1/128' | awk '{print $2}' | cut -f1 -d'/' | head -n 1)+"
|
||
ipv6_check_box=${check_box_good}
|
||
fi
|
||
|
||
# Get hostname and gateway
|
||
pi_hostname=$(hostname)
|
||
|
||
full_hostname=${pi_hostname}
|
||
# does the Pi-hole have a domain set?
|
||
if [ -n "${PIHOLE_DOMAIN+x}" ]; then
|
||
# is Pi-hole acting as DHCP server?
|
||
if [ "${DHCP_ACTIVE}" = "true" ]; then
|
||
count=${pi_hostname}"."${PIHOLE_DOMAIN}
|
||
count=${#count}
|
||
if [ "${count}" -lt "18" ]; then
|
||
full_hostname=${pi_hostname}"."${PIHOLE_DOMAIN}
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Get the DNS count (from pihole -c)
|
||
dns_count="0"
|
||
[ -n "${PIHOLE_DNS_1}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_2}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_3}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_4}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_5}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_6}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_7}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_8}" ] && dns_count=$((dns_count+1))
|
||
[ -n "${PIHOLE_DNS_9}" ] && dns_count=$((dns_count+1))
|
||
|
||
# if there's only one DNS server
|
||
if [ ${dns_count} -eq 1 ]; then
|
||
dns_information="1 server"
|
||
else
|
||
dns_information="${dns_count} servers"
|
||
fi
|
||
|
||
# Is Pi-Hole acting as the DHCP server?
|
||
if [ "${DHCP_ACTIVE}" = "true" ]; then
|
||
dhcp_status="Enabled"
|
||
dhcp_info=" Range: ${DHCP_START} - ${DHCP_END}"
|
||
dhcp_heatmap=${green_text}
|
||
dhcp_check_box=${check_box_good}
|
||
|
||
# Is DHCP handling IPv6?
|
||
# DHCP_IPv6 is set in setupVars.conf
|
||
# shellcheck disable=SC2154
|
||
if [ "${DHCP_IPv6}" = "true" ]; then
|
||
dhcp_ipv6_status="Enabled"
|
||
dhcp_ipv6_heatmap=${green_text}
|
||
dhcp_ipv6_check_box=${check_box_good}
|
||
else
|
||
dhcp_ipv6_status="Disabled"
|
||
dhcp_ipv6_heatmap=${red_text}
|
||
dhcp_ipv6_check_box=${check_box_bad}
|
||
fi
|
||
else
|
||
dhcp_status="Disabled"
|
||
dhcp_heatmap=${red_text}
|
||
dhcp_check_box=${check_box_bad}
|
||
|
||
# if the DHCP Router variable isn't set
|
||
if [ -z ${DHCP_ROUTER+x} ]; then
|
||
DHCP_ROUTER=$(GetFTLData "gateway" | awk '{ printf $1 }')
|
||
fi
|
||
|
||
dhcp_info=" Router: ${DHCP_ROUTER}"
|
||
dhcp_heatmap=${red_text}
|
||
dhcp_check_box=${check_box_bad}
|
||
|
||
dhcp_ipv6_status="N/A"
|
||
dhcp_ipv6_heatmap=${yellow_text}
|
||
dhcp_ipv6_check_box=${check_box_question}
|
||
fi
|
||
|
||
# DNSSEC
|
||
if [ "${DNSSEC}" = "true" ]; then
|
||
dnssec_status="Enabled"
|
||
dnssec_heatmap=${green_text}
|
||
else
|
||
dnssec_status="Disabled"
|
||
dnssec_heatmap=${red_text}
|
||
fi
|
||
|
||
# Conditional forwarding
|
||
if [ "${CONDITIONAL_FORWARDING}" = "true" ] || [ "${REV_SERVER}" = "true" ]; then
|
||
conditional_forwarding_status="Enabled"
|
||
conditional_forwarding_heatmap=${green_text}
|
||
else
|
||
conditional_forwarding_status="Disabled"
|
||
conditional_forwarding_heatmap=${red_text}
|
||
fi
|
||
|
||
#Default interface data
|
||
def_iface_data=$(GetFTLData "interfaces" | head -n1)
|
||
iface_name="$(echo "$def_iface_data" | awk '{print $1}')"
|
||
tx_bytes="$(echo "$def_iface_data" | awk '{print $4}')"
|
||
rx_bytes="$(echo "$def_iface_data" | awk '{print $5}')"
|
||
}
|
||
|
||
GetPiholeInformation() {
|
||
# Get FTL status
|
||
|
||
# Get FTL's current PID
|
||
ftlPID="$(getFTLPID)"
|
||
|
||
# If FTL is not running (getFTLPID returns -1), set all variables to "not running"
|
||
ftl_down_flag=false
|
||
if [ "${ftlPID}" = "-1" ]; then
|
||
ftl_status="Not running"
|
||
ftl_heatmap=${red_text}
|
||
ftl_check_box=${check_box_bad}
|
||
# set flag to change the status message in SetStatusMessage()
|
||
ftl_down_flag=true
|
||
ftl_cpu="N/A"
|
||
ftl_mem_percentage="N/A"
|
||
else
|
||
ftl_status="Running"
|
||
ftl_heatmap=${green_text}
|
||
ftl_check_box=${check_box_good}
|
||
# Get FTL CPU and memory usage
|
||
ftl_cpu="$(ps h -p "${ftlPID}" -o %cpu | tr -d '[:space:]')%"
|
||
ftl_mem_percentage="$(ps h -p "${ftlPID}" -o %mem | tr -d '[:space:]')%"
|
||
# Get Pi-hole (blocking) status
|
||
ftl_dns_port=$(GetFTLData "dns-port")
|
||
fi
|
||
|
||
# ${ftl_dns_port} == 0 DNS server part of dnsmasq disabled, ${ftl_status} == "Not running" no ftlPID found
|
||
dns_down_flag=false
|
||
if [ "${ftl_dns_port}" = 0 ] || [ "${ftl_status}" = "Not running" ]; 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() {
|
||
# Check if version status has been saved
|
||
# all info is sourced from /etc/pihole/versions
|
||
|
||
out_of_date_flag=false
|
||
|
||
# If PADD is running inside docker, immediately return without checking for updated component versions
|
||
if [ -n "${DOCKER_VERSION}" ]; then
|
||
docker_version_converted="$(VersionConverter "${DOCKER_VERSION}")"
|
||
docker_version_latest_converted="$(VersionConverter "${GITHUB_DOCKER_VERSION}")"
|
||
|
||
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...
|
||
# Extract vx.xx or vx.xx.xxx version
|
||
CORE_VERSION="$(echo "${CORE_VERSION}" | grep -oE '^v[0-9]+([.][0-9]+){1,2}')"
|
||
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...
|
||
# Extract vx.xx or vx.xx.xxx version
|
||
if [ "$INSTALL_WEB_INTERFACE" = true ]; then
|
||
WEB_VERSION="$(echo "${WEB_VERSION}" | grep -oE '^v[0-9]+([.][0-9]+){1,2}')"
|
||
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...
|
||
# Extract vx.xx or vx.xx.xxx version
|
||
FTL_VERSION="$(echo "${FTL_VERSION}" | grep -oE '^v[0-9]+([.][0-9]+){1,2}')"
|
||
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 [ -n "${DOCKER_VERSION}" ]; then
|
||
return
|
||
fi
|
||
|
||
# PADD version information...
|
||
padd_version_latest="$(curl --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=$(echo "${system_uptime_raw}" | awk -F'( |,|:)+' '{if ($7=="min") m=$6; else {if ($7~/^day/){if ($9=="min") {d=$6;m=$8} else {d=$6;h=$8;m=$9}} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours"}')
|
||
else
|
||
system_uptime=$(echo "${system_uptime_raw}" | awk -F'( |,|:)+' '{if ($7=="min") m=$6; else {if ($7~/^day/){if ($9=="min") {d=$6;m=$8} else {d=$6;h=$8;m=$9}} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes"}')
|
||
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 [ "${ftl_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_status}" = "unknown" ]; then
|
||
# Check if blocking status is unknown
|
||
pico_status=${pico_status_unknown}
|
||
mini_status=${mini_status_unknown}
|
||
tiny_status=${tiny_status_unknown}
|
||
full_status=${full_status_unknown}
|
||
mega_status=${mega_status_unknown}
|
||
|
||
elif [ "${blocking_status}" = "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}
|
||
full_status=${full_status_update}
|
||
mega_status=${mega_status_update}
|
||
|
||
elif [ "${blocking_status}" = "enabled" ]; then
|
||
# if we reach this point and blocking is enabled, everything is fine
|
||
pico_status=${pico_status_ok}
|
||
mini_status=${mini_status_ok}
|
||
tiny_status=${tiny_status_ok}
|
||
full_status=${full_status_ok}
|
||
mega_status=${mega_status_ok}
|
||
fi
|
||
}
|
||
|
||
############################################# PRINTERS #############################################
|
||
|
||
PrintLogo() {
|
||
if [ -n "${DOCKER_VERSION}" ]; then
|
||
version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}"
|
||
else
|
||
version_info="Pi-hole® ${core_version_heatmap}${CORE_VERSION}${reset_text}, Web ${web_version_heatmap}${WEB_VERSION}${reset_text}, FTL ${ftl_version_heatmap}${FTL_VERSION}${reset_text}"
|
||
fi
|
||
|
||
# Screen size checks
|
||
if [ "$1" = "pico" ]; then
|
||
printf "%s${clear_line}\n" "p${padd_text} ${pico_status}"
|
||
elif [ "$1" = "nano" ]; then
|
||
printf "%s${clear_line}\n" "n${padd_text} ${mini_status}"
|
||
elif [ "$1" = "micro" ]; then
|
||
printf "%s${clear_line}\n${clear_line}\n" "µ${padd_text} ${mini_status}"
|
||
elif [ "$1" = "mini" ]; then
|
||
printf "%s${clear_line}\n${clear_line}\n" "${padd_text}${dim_text}mini${reset_text} ${mini_status}"
|
||
elif [ "$1" = "tiny" ]; then
|
||
printf "%s${clear_line}\n" "${padd_text}${dim_text}tiny${reset_text} ${version_info}${reset_text}"
|
||
printf "%s${clear_line}\n" " PADD ${padd_version_heatmap}${padd_version}${reset_text} ${tiny_status}${reset_text}"
|
||
elif [ "$1" = "slim" ]; then
|
||
printf "%s${clear_line}\n${clear_line}\n" "${padd_text}${dim_text}slim${reset_text} ${full_status}"
|
||
elif [ "$1" = "regular" ] || [ "$1" = "slim" ]; then
|
||
printf "%s${clear_line}\n" "${padd_logo_1}"
|
||
printf "%s${clear_line}\n" "${padd_logo_2}${version_info}${reset_text}"
|
||
printf "%s${clear_line}\n${clear_line}\n" "${padd_logo_3}PADD ${padd_version_heatmap}${padd_version}${reset_text} ${full_status}${reset_text}"
|
||
# normal or not defined
|
||
else
|
||
printf "%s${clear_line}\n" "${padd_logo_1}"
|
||
printf "%s${clear_line}\n" "${padd_logo_2} ${version_info}, PADD ${padd_version_heatmap}${padd_version}${reset_text}"
|
||
printf "%s${clear_line}\n${clear_line}\n" "${padd_logo_3} ${dns_check_box} DNS ${ftl_check_box} FTL ${mega_status}${reset_text}"
|
||
fi
|
||
}
|
||
|
||
PrintDashboard() {
|
||
if [ -n "${DOCKER_VERSION}" ]; then
|
||
version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}"
|
||
else
|
||
version_info="Pi-hole® ${core_version_heatmap}${CORE_VERSION}${reset_text}, Web ${web_version_heatmap}${WEB_VERSION}${reset_text}, FTL ${ftl_version_heatmap}${FTL_VERSION}${reset_text}"
|
||
fi
|
||
# Move cursor to (0,0).
|
||
printf '\e[H'
|
||
|
||
# adds the y-offset
|
||
moveYOffset
|
||
|
||
# mega is a screen with at least 80 columns and 26 lines
|
||
moveXOffset; printf "%s${clear_line}\n" "${padd_logo_1}"
|
||
moveXOffset; printf "%s${clear_line}\n" "${padd_logo_2} ${version_info}, PADD ${padd_version_heatmap}${padd_version}${reset_text}"
|
||
moveXOffset; printf "%s${clear_line}\n" "${padd_logo_3} ${dns_check_box} DNS ${ftl_check_box} FTL ${mega_status}${reset_text}"
|
||
moveXOffset; printf "%s${clear_line}\n" ""
|
||
moveXOffset; printf "%s${clear_line}\n" "${bold_text}STATS ==========================================================================${reset_text}"
|
||
moveXOffset; printf " %-10s%-19s %-10s[%-40s] %-5s${clear_line}\n" "Blocking:" "${domains_being_blocked} domains" "Piholed:" "${ads_blocked_bar}" "${ads_percentage_today}%"
|
||
moveXOffset; printf " %-10s%-30s%-29s${clear_line}\n" "Clients:" "${clients}" " ${ads_blocked_today} out of ${dns_queries_today} queries"
|
||
moveXOffset; printf " %-10s%-39s${clear_line}\n" "Top Ad:" "${top_blocked}"
|
||
moveXOffset; printf "%s${clear_line}\n" "${bold_text}FTL ============================================================================${reset_text}"
|
||
moveXOffset; printf " %-10s%-9s %-10s%-9s %-10s%-9s${clear_line}\n" "PID:" "${ftlPID}" "CPU Use:" "${ftl_cpu}" "Mem. Use:" "${ftl_mem_percentage}"
|
||
moveXOffset; printf " %-10s%-69s${clear_line}\n" "DNSCache:" "${cache_inserts} insertions, ${cache_deletes} deletions, ${cache_size} total entries"
|
||
moveXOffset; printf "%s${clear_line}\n" "${bold_text}NETWORK ========================================================================${reset_text}"
|
||
moveXOffset; printf " %-10s%-15s %-4s%-9s %-4s%-9s${clear_line}\n" "Interfce:" "${iface_name}" "TX:" "${tx_bytes}" "RX:" "${rx_bytes}"
|
||
moveXOffset; printf "%s${clear_line}\n" "${bold_text}SYSTEM =========================================================================${reset_text}"
|
||
moveXOffset; printf " %-10s%-39s${clear_line}\n" "Device:" "${sys_model}"
|
||
moveXOffset; printf " %-10s%-39s %-10s[${memory_heatmap}%-10s${reset_text}] %-6s${clear_line}\n" "Uptime:" "${system_uptime}" "Memory:" "${memory_bar}" "${memory_percent}%"
|
||
moveXOffset; printf " %-10s${temp_heatmap}%-10s${reset_text} %-10s${cpu_load_1_heatmap}%-4s${reset_text}, ${cpu_load_5_heatmap}%-4s${reset_text}, ${cpu_load_15_heatmap}%-7s${reset_text} %-10s[${memory_heatmap}%-10s${reset_text}] %-6s${clear_line}" "CPU Temp:" "${temperature}" "CPU Load:" "${cpu_load_1}" "${cpu_load_5}" "${cpu_load_15}" "CPU Load:" "${cpu_bar}" "${cpu_percent}%"
|
||
|
||
# Clear to end of screen (below the drawn dashboard)
|
||
# https://vt100.net/docs/vt510-rm/ED.html
|
||
printf '\e[0J'
|
||
}
|
||
|
||
############################################# HELPERS ##############################################
|
||
|
||
# Provides a color based on a provided percentage
|
||
# takes in one or two parameters
|
||
HeatmapGenerator () {
|
||
# if one number is provided, just use that percentage to figure out the colors
|
||
if [ -z "$2" ]; then
|
||
load=$(printf "%.0f" "$1")
|
||
# if two numbers are provided, do some math to make a percentage to figure out the colors
|
||
else
|
||
load=$(printf "%.0f" "$(echo "$1 $2" | awk '{print ($1 / $2) * 100}')")
|
||
fi
|
||
|
||
# Color logic
|
||
# |<- green ->| yellow | red ->
|
||
# 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
|
||
if [ "${load}" -lt 75 ]; then
|
||
out=${green_text}
|
||
elif [ "${load}" -lt 90 ]; then
|
||
out=${yellow_text}
|
||
else
|
||
out=${red_text}
|
||
fi
|
||
|
||
echo "$out"
|
||
}
|
||
|
||
# Provides a "bar graph"
|
||
# takes in two or three parameters
|
||
# $1: percentage filled
|
||
# $2: max length of the bar
|
||
# $3: colored flag, if "color" backfill with color
|
||
BarGenerator() {
|
||
# number of filled in cells in the bar
|
||
barNumber=$(printf %.f "$(echo "$1 $2" | awk '{print ($1 / 100) * $2}')")
|
||
frontFill=$(for i in $(seq "$barNumber"); do printf "%b" "■"; done)
|
||
|
||
# remaining "unfilled" cells in the bar
|
||
backfillNumber=$(($2-barNumber))
|
||
|
||
# if the filled in cells is less than the max length of the bar, fill it
|
||
if [ "$barNumber" -lt "$2" ]; then
|
||
# if the bar should be colored
|
||
if [ "$3" = "color" ]; then
|
||
# fill the rest in color
|
||
backFill=$(for i in $(seq $backfillNumber); do printf "%b" "■"; done)
|
||
out="${red_text}${frontFill}${green_text}${backFill}${reset_text}"
|
||
# else, it shouldn't be colored in
|
||
else
|
||
# fill the rest with "space"
|
||
backFill=$(for i in $(seq $backfillNumber); do printf "%b" "·"; done)
|
||
out="${frontFill}${reset_text}${backFill}"
|
||
fi
|
||
# else, fill it all the way
|
||
else
|
||
out=$(for i in $(seq "$2"); do printf "%b" "■"; done)
|
||
fi
|
||
|
||
echo "$out"
|
||
}
|
||
|
||
# Checks the size of the screen and sets the value of $padd_size
|
||
SizeChecker(){
|
||
# adding a tiny delay here to to give the kernel a bit time to
|
||
# report new sizes correctly after a terminal resize
|
||
# this reduces "flickering" of GenerateSizeDependendOutput() items
|
||
# after a terminal re-size
|
||
sleep 0.1
|
||
console_width=$(tput cols)
|
||
console_height=$(tput lines)
|
||
|
||
# Mega
|
||
padd_size="mega"
|
||
width=80
|
||
height=26
|
||
|
||
# Center the output (default position)
|
||
xOffset="$(( (console_width - width) / 2 ))"
|
||
yOffset="$(( (console_height - height) / 2 ))"
|
||
|
||
# If the user sets an offset option, use it.
|
||
if [ -n "$xOffOrig" ]; then
|
||
xOffset=$xOffOrig
|
||
|
||
# Limit the offset to avoid breaks
|
||
xMaxOffset=$((console_width - width))
|
||
if [ "$xOffset" -gt "$xMaxOffset" ]; then
|
||
xOffset="$xMaxOffset"
|
||
fi
|
||
fi
|
||
if [ -n "$yOffOrig" ]; then
|
||
yOffset=$yOffOrig
|
||
|
||
# Limit the offset to avoid breaks
|
||
yMaxOffset=$((console_height - height))
|
||
if [ "$yOffset" -gt "$yMaxOffset" ]; then
|
||
yOffset="$yMaxOffset"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# converts a given version string e.g. v3.7.1 to 3007001000 to allow for easier comparison of multi digit version numbers
|
||
# credits https://apple.stackexchange.com/a/123408
|
||
VersionConverter() {
|
||
echo "$@" | tr -d '[:alpha:]' | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }';
|
||
}
|
||
|
||
# get the Telnet API Port FTL is using by parsing `pihole-FTL.conf`
|
||
# same implementation as https://github.com/pi-hole/pi-hole/pull/4945
|
||
getFTLAPIPort(){
|
||
local FTLCONFFILE="/etc/pihole/pihole-FTL.conf"
|
||
local DEFAULT_FTL_PORT=4711
|
||
local ftl_api_port
|
||
|
||
if [ -s "$FTLCONFFILE" ]; then
|
||
# if FTLPORT is not set in pihole-FTL.conf, use the default port
|
||
ftl_api_port="$({ grep '^FTLPORT=' "${FTLCONFFILE}" || echo "${DEFAULT_FTL_PORT}"; } | cut -d'=' -f2-)"
|
||
# Exploit prevention: set the port to the default port if there is malicious (non-numeric)
|
||
# content set in pihole-FTL.conf
|
||
expr "${ftl_api_port}" : "[^[:digit:]]" > /dev/null && ftl_api_port="${DEFAULT_FTL_PORT}"
|
||
else
|
||
# if there is no pihole-FTL.conf, use the default port
|
||
ftl_api_port="${DEFAULT_FTL_PORT}"
|
||
fi
|
||
|
||
echo "${ftl_api_port}"
|
||
|
||
}
|
||
|
||
# returns FTL's PID based on the content of the pihole-FTL.pid file
|
||
# honor PIDFILE setting in `pihole-FTL.conf`
|
||
getFTLPID() {
|
||
local FTLCONFFILE="/etc/pihole/pihole-FTL.conf"
|
||
local DEFAULT_PID_FILE="/run/pihole-FTL.pid"
|
||
local FTL_PID_FILE
|
||
local FTL_PID
|
||
|
||
if [ -s "${FTLCONFFILE}" ]; then
|
||
# if PIDFILE is not set in pihole-FTL.conf, use the default path
|
||
FTL_PID_FILE="$({ grep '^PIDFILE=' "${FTLCONFFILE}" || echo "${DEFAULT_PID_FILE}"; } | cut -d'=' -f2-)"
|
||
else
|
||
# if there is no pihole-FTL.conf, use the default path
|
||
FTL_PID_FILE="${DEFAULT_PID_FILE}"
|
||
fi
|
||
|
||
if [ -s "${FTL_PID_FILE}" ]; then
|
||
# -s: FILE exists and has a size greater than zero
|
||
FTL_PID="$(cat "${FTL_PID_FILE}")"
|
||
# Exploit prevention: unset the variable if there is malicious content
|
||
# Verify that the value read from the file is numeric
|
||
expr "${FTL_PID}" : "[^[:digit:]]" > /dev/null && unset FTL_PID
|
||
fi
|
||
|
||
# If FTL is not running, or the PID file contains malicious stuff, substitute
|
||
# negative PID to signal this
|
||
FTL_PID=${FTL_PID:=-1}
|
||
echo "${FTL_PID}"
|
||
}
|
||
|
||
|
||
moveYOffset(){
|
||
# moves the cursor yOffset-times down
|
||
# https://vt100.net/docs/vt510-rm/CUD.html
|
||
# this needs to be guarded, because if the amount is 0, it is adjusted to 1
|
||
# https://terminalguide.namepad.de/seq/csi_cb/
|
||
|
||
if [ "${yOffset}" -gt 0 ]; then
|
||
printf '\e[%sB' "${yOffset}"
|
||
fi
|
||
}
|
||
|
||
moveXOffset(){
|
||
# moves the cursor xOffset-times to the right
|
||
# https://vt100.net/docs/vt510-rm/CUF.html
|
||
# this needs to be guarded, because if the amount is 0, it is adjusted to 1
|
||
# https://terminalguide.namepad.de/seq/csi_cb/
|
||
|
||
if [ "${xOffset}" -gt 0 ]; then
|
||
printf '\e[%sC' "${xOffset}"
|
||
fi
|
||
}
|
||
|
||
# Remove undesired strings from sys_model variable - used in GetSystemInformation() function
|
||
filterModel() {
|
||
FILTERLIST="To be filled by O.E.M.|Not Applicable|System Product Name|System Version|Undefined|Default string|Not Specified|Type1ProductConfigId|INVALID|All Series|<7C>"
|
||
|
||
# Description:
|
||
# `-v` : set $FILTERLIST into a variable called `list`
|
||
# `gsub()` : replace all list items (ignoring case) with an empty string, deleting them
|
||
# `{$1=$1}1`: remove all extra spaces. The last "1" evaluates as true, printing the result
|
||
echo "$1" | awk -v list="$FILTERLIST" '{IGNORECASE=1; gsub(list,"")}; {$1=$1}1'
|
||
}
|
||
|
||
# Truncates a given string and appends three '...'
|
||
# takes two parameters
|
||
# $1: string to truncate
|
||
# $2: max length of the string
|
||
truncateString() {
|
||
local truncatedString length shorted
|
||
|
||
length=${#1}
|
||
shorted=$(($2-3)) # shorten max allowed length by 3 to make room for the dots
|
||
if [ "${length}" -gt "$2" ]; then
|
||
# if length of the string is larger then the specified max length
|
||
# cut every char from the string exceeding length $shorted and add three dots
|
||
truncatedString=$(echo "$1" | cut -c1-$shorted)"..."
|
||
echo "${truncatedString}"
|
||
else
|
||
echo "$1"
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
########################################## MAIN FUNCTIONS ##########################################
|
||
|
||
OutputJSON() {
|
||
GetSummaryInformation
|
||
echo "{\"domains_being_blocked\":${domains_being_blocked_raw},\"dns_queries_today\":${dns_queries_today_raw},\"ads_blocked_today\":${ads_blocked_today_raw},\"ads_percentage_today\":${ads_percentage_today_raw},\"clients\": ${clients}}"
|
||
}
|
||
|
||
ShowVersion() {
|
||
# source version file to check if $DOCKER_VERSION is set
|
||
. /etc/pihole/versions
|
||
GetPADDInformation
|
||
if [ -z "${padd_version_latest}" ]; then
|
||
padd_version_latest="N/A"
|
||
fi
|
||
if [ -n "${DOCKER_VERSION}" ]; then
|
||
# Check for latest Docker version
|
||
GetVersionInformation
|
||
printf "%s${clear_line}\n" " PADD version is ${padd_version} as part of Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text} (Latest Docker: ${GITHUB_DOCKER_VERSION})"
|
||
version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}"
|
||
else
|
||
printf "%s${clear_line}\n" " PADD version is ${padd_version_heatmap}${padd_version}${reset_text} (Latest: ${padd_version_latest})"
|
||
fi
|
||
}
|
||
|
||
StartupRoutine(){
|
||
# Get config variables
|
||
. /etc/pihole/setupVars.conf
|
||
|
||
# Clear the screen and move cursor to (0,0).
|
||
# This mimics the 'clear' command.
|
||
# https://vt100.net/docs/vt510-rm/ED.html
|
||
# https://vt100.net/docs/vt510-rm/CUP.html
|
||
# E3 extension `\e[3J` to clear the scrollback buffer see 'man clear'
|
||
printf '\e[H\e[2J\e[3J'
|
||
|
||
# adds the y-offset
|
||
moveYOffset
|
||
|
||
# Get versions information
|
||
. /etc/pihole/versions
|
||
|
||
GetSystemInformation
|
||
GetSummaryInformation
|
||
GetPiholeInformation
|
||
GetNetworkInformation
|
||
GetVersionInformation
|
||
GetPADDInformation
|
||
for i in 3 2 1
|
||
do
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
NormalPADD() {
|
||
|
||
# Trap the window resize signal (handle window resize events)
|
||
trap 'TerminalResize' WINCH
|
||
|
||
# Generate output that depends on the terminal size
|
||
# e.g. Heatmap and barchart
|
||
GenerateSizeDependendOutput ${padd_size}
|
||
|
||
# Sets the message displayed in the "status field" depending on the set flags
|
||
SetStatusMessage
|
||
|
||
# Output everything to the screen
|
||
PrintDashboard ${padd_size}
|
||
|
||
}
|
||
|
||
Update() {
|
||
# source version file to check if $DOCKER_VERSION is set
|
||
. /etc/pihole/versions
|
||
|
||
if [ -n "${DOCKER_VERSION}" ]; then
|
||
echo "${check_box_info} Update is not supported for Docker"
|
||
exit 1
|
||
fi
|
||
|
||
GetPADDInformation
|
||
|
||
if [ "${padd_out_of_date_flag}" = "true" ]; then
|
||
echo "${check_box_info} Updating PADD from ${padd_version} to ${padd_version_latest}"
|
||
|
||
padd_script_path=$(realpath "$0")
|
||
|
||
if which wget > /dev/null 2>&1; then
|
||
echo "${check_box_info} Downloading PADD update via wget ..."
|
||
if wget -qO "${padd_script_path}" https://install.padd.sh > /dev/null 2>&1; then
|
||
echo "${check_box_good} ... done. Restart PADD for the update to take effect"
|
||
else
|
||
echo "${check_box_bad} Cannot download PADD update via wget"
|
||
echo "${check_box_info} Go to https://install.padd.sh to download the update manually"
|
||
exit 1
|
||
fi
|
||
elif which curl > /dev/null 2>&1; then
|
||
echo "${check_box_info} Downloading PADD update via curl ..."
|
||
if curl -sSL https://install.padd.sh -o "${padd_script_path}" > /dev/null 2>&1; then
|
||
echo "${check_box_good} ... done. Restart PADD for the update to take effect"
|
||
else
|
||
echo "${check_box_bad} Cannot download PADD update via curl"
|
||
echo "${check_box_info} Go to https://install.padd.sh to download the update manually"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "${check_box_bad} Cannot download, neither wget nor curl are available"
|
||
echo "${check_box_info} Go to https://install.padd.sh to download the update manually"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "${check_box_good} You are already using the latest PADD version ${padd_version}"
|
||
fi
|
||
|
||
exit 0
|
||
}
|
||
|
||
DisplayHelp() {
|
||
cat << EOM
|
||
|
||
::: PADD displays stats about your Pi-hole!
|
||
:::
|
||
::: Note: If no option is passed, then stats are displayed on screen, updated every 5 seconds
|
||
:::
|
||
::: Options:
|
||
::: -xoff [num] set the x-offset, reference is the upper left corner, disables auto-centering
|
||
::: -yoff [num] set the y-offset, reference is the upper left corner, disables auto-centering
|
||
::: -j, --json output stats as JSON formatted string and exit
|
||
::: -u, --update update to the latest version
|
||
::: -v, --version show PADD version info
|
||
::: -h, --help display this help text
|
||
|
||
EOM
|
||
}
|
||
|
||
CleanExit(){
|
||
# save the return code of the script
|
||
err=$?
|
||
#clear the line
|
||
printf '\e[0K\n'
|
||
|
||
# Show the cursor
|
||
# https://vt100.net/docs/vt510-rm/DECTCEM.html
|
||
printf '\e[?25h'
|
||
|
||
# if background sleep is running, kill it
|
||
# http://mywiki.wooledge.org/SignalTrap#When_is_the_signal_handled.3F
|
||
kill $sleepPID > /dev/null 2>&1
|
||
|
||
exit $err # exit the script with saved $?
|
||
}
|
||
|
||
TerminalResize(){
|
||
# if a terminal resize is trapped, check the new terminal size and
|
||
# kill the sleep function within NormalPADD() to trigger redrawing
|
||
# of the Dashboard
|
||
SizeChecker
|
||
|
||
# Clear the screen and move cursor to (0,0).
|
||
# This mimics the 'clear' command.
|
||
# https://vt100.net/docs/vt510-rm/ED.html
|
||
# https://vt100.net/docs/vt510-rm/CUP.html
|
||
# E3 extension `\e[3J` to clear the scrollback buffer (see 'man clear')
|
||
|
||
printf '\e[H\e[2J\e[3J'
|
||
|
||
kill $sleepPID > /dev/null 2>&1
|
||
}
|
||
|
||
main(){
|
||
# Hiding the cursor.
|
||
# https://vt100.net/docs/vt510-rm/DECTCEM.html
|
||
printf '\e[?25l'
|
||
|
||
# Trap on exit
|
||
trap 'CleanExit' INT TERM EXIT
|
||
|
||
# If setupVars.conf is not present, then PADD is not running on a Pi-hole
|
||
# and we are not able to start as StartupRoutine() will fail below
|
||
if [ ! -f /etc/pihole/setupVars.conf ]; then
|
||
printf "%b" "${check_box_bad} Error!\n PADD only works in conjunction with Pi-hole!\n"
|
||
exit 1
|
||
fi
|
||
|
||
SizeChecker
|
||
|
||
StartupRoutine ${padd_size}
|
||
|
||
# Run PADD
|
||
NormalPADD
|
||
}
|
||
|
||
# Process all options (if present)
|
||
while [ "$#" -gt 0 ]; do
|
||
case "$1" in
|
||
"-j" | "--json" ) OutputJSON; exit 0;;
|
||
"-u" | "--update" ) Update;;
|
||
"-h" | "--help" ) DisplayHelp; exit 0;;
|
||
"-v" | "--version" ) ShowVersion; exit 0;;
|
||
"-xoff" ) xOffset="$2"; xOffOrig="$2"; shift;;
|
||
"-yoff" ) yOffset="$2"; yOffOrig="$2"; shift;;
|
||
* ) DisplayHelp; exit 1;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
main
|