cf-node-bootstrap/lib/skel/.bashrc.d/11-quickinfo.bashrc
pyr0ball c7b7934e68 fix: locale/unicode compatibility in quickinfo, bump PRbL submodule
- Pin LC_ALL=C on free/uptime/sensors calls parsed by grep/awk on
  English words and fixed column positions; these broke under
  non-English locales. Uptime's token-position parsing is still
  format-fragile across procps versions/distros; noted as a follow-up
  for a /proc/uptime-based rewrite.
- Replace hardcoded '°C' with a deg_symbol variable that collapses to
  plain "C" when PRbL/functions reports unicode_supported=false, so
  temperature readouts stay pure ASCII on terminals/locales that can't
  render the degree sign.
- Bump PRbL submodule for the ANSI-code and box-border fallback fixes.
2026-07-17 16:01:31 -07:00

1108 lines
No EOL
35 KiB
Bash
Executable file

#!/bin/bash
###############################################################
# MOTD/Login Quick Information script #
# This script pulls information from a variety of sources #
# in a Linux system, and aggregates it into a small table #
# that is displayed upon terminal login to the system. #
# #
# Written by Alan "pyr0ball" Weinstock #
# Enhanced for multi-distro compatibility #
###############################################################
quickinfo_version=4.1.2
prbl_functons_req_ver=2.1.0
# Source PRbL Functions locally or retrieve from online
if [ -f $prbl_functions ] ; then
source $prbl_functions
else
if [ -f ${rundir}/functions ] ; then
source ${rundir}/functions
else
# Iterate through get commands and fall back on next if unavailable
if command -v curl >/dev/null 2>&1; then
source <(curl -ks 'https://raw.githubusercontent.com/pyr0ball/PRbL/main/functions')
elif command -v wget >/dev/null 2>&1; then
source <(wget -qO- 'https://raw.githubusercontent.com/pyr0ball/PRbL/main/functions')
elif command -v fetch >/dev/null 2>&1; then
source <(fetch -qo- 'https://raw.githubusercontent.com/pyr0ball/PRbL/main/functions')
else
echo "Error: curl, wget, and fetch commands are not available. Please install one to retrieve PRbL functions."
exit 1
fi
fi
fi
# Locate script information
scriptname="${BASH_SOURCE[0]##*/}"
rundir="${BASH_SOURCE[0]%/*}"
# Degree symbol used in temperature readouts. PRbL/functions sets
# unicode_supported when sourced; fall back to a bare "C" (no degree sign)
# on terminals/locales without Unicode support.
deg_symbol='°'
[[ "$unicode_supported" == true ]] || deg_symbol=''
########################################
# Default quickinfo bashrc Preferences #
########################################
# Disable run on non-interactive sessions (set true to disable)
interactive_only=false
# Network Adapter Preferences
# set false to hide network adapters without valid IP's
show_disconnected=true
# Ignored network adapter names (regex match)
# separate adapter names with '\|' ex. "lo\|tun0"
filtered_adapters="lo"
# Disks
allowed_disk_prefixes=(
"sd"
"md"
"mapper"
"nvme"
"mmcblk"
"root"
"vd"
"xvd"
)
disallowed_disk_prefixes=(
"boot"
"snap"
"loop"
)
# Settings for additional information display
show_gpu_info=true
show_container_info=true
show_service_status=true
show_security_info=true
show_smart_status=false # Requires root privileges or sudo config
show_top_processes=true
show_power_info=true # Enable power monitoring (requires appropriate hardware/sensors)
show_power_details=false
# Services to check (space separated list)
critical_services="sshd nginx apache2 mysqld mariadb docker containerd kubelet cron"
#######################################################
# Do not change these unless you know what you're doing
allowed_disk_prefixes_string=""
disallowed_disk_prefixes_string=""
# Build the allowed disk prefixes pattern
for prefix in "${allowed_disk_prefixes[@]}"; do
if [ -z "$allowed_disk_prefixes_string" ]; then
allowed_disk_prefixes_string="$prefix"
else
allowed_disk_prefixes_string+="\\|$prefix"
fi
done
# Build the disallowed disk prefixes pattern
for prefix in "${disallowed_disk_prefixes[@]}"; do
if [ -z "$disallowed_disk_prefixes_string" ]; then
disallowed_disk_prefixes_string="$prefix"
else
disallowed_disk_prefixes_string+="\\|$prefix"
fi
done
# check PRbL functions version
if command -v vercomp >/dev/null 2>&1; then
if [[ $(vercomp $functionsrev $prbl_functons_req_ver) == 2 ]] ; then
warn "PRbL functions installed are lower than recommended ($prbl_functons_req_ver)"
warn "Some features may not work as expected"
fi
fi
################################
# Enhanced OS Detection Function
################################
detect_os() {
# Initialize OS info variables
OS="Unknown"
VER="Unknown"
ID="Unknown"
# Method 1: lsb_release
if command -v lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si)
VER=$(lsb_release -sr)
ID=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
return
fi
# Method 2: /etc/os-release
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$NAME
VER=$VERSION_ID
ID=${ID:-$NAME}
ID=$(echo $ID | tr '[:upper:]' '[:lower:]')
return
fi
# Method 3: /etc/lsb-release
if [ -f /etc/lsb-release ]; then
. /etc/lsb-release
OS=$DISTRIB_ID
VER=$DISTRIB_RELEASE
ID=$(echo $DISTRIB_ID | tr '[:upper:]' '[:lower:]')
return
fi
# Method 4: /etc/{system}-release files
for release_file in /etc/*-release; do
if [ -f "$release_file" ] && [ "$release_file" != "/etc/os-release" ] && [ "$release_file" != "/etc/lsb-release" ]; then
OS=$(head -n1 "$release_file" | cut -d' ' -f1)
VER=$(head -n1 "$release_file" | grep -o -E '[0-9]+\.[0-9]+' | head -n1)
ID=$(echo $OS | tr '[:upper:]' '[:lower:]')
return
fi
done
# Method 5: Fallback to uname
OS=$(uname -s)
VER=$(uname -r)
ID=$(echo $OS | tr '[:upper:]' '[:lower:]')
}
################################
# Package Manager Detection
################################
detect_package_manager() {
package_manager="unknown"
update_cmd=""
update_check_cmd=""
if command -v apt >/dev/null 2>&1 || command -v apt-get >/dev/null 2>&1; then
package_manager="apt"
update_cmd="apt update && apt upgrade"
if [ -f /usr/lib/update-notifier/apt-check ]; then
update_check_cmd="/usr/lib/update-notifier/apt-check --human-readable"
else
update_check_cmd="apt list --upgradable 2>/dev/null | grep -c ^"
fi
elif command -v dnf >/dev/null 2>&1; then
package_manager="dnf"
update_cmd="dnf upgrade"
update_check_cmd="dnf check-update --quiet | grep -v \"^$\" | wc -l"
elif command -v yum >/dev/null 2>&1; then
package_manager="yum"
update_cmd="yum update"
update_check_cmd="yum check-update --quiet | grep -v \"^$\" | wc -l"
elif command -v pacman >/dev/null 2>&1; then
package_manager="pacman"
update_cmd="pacman -Syu"
update_check_cmd="pacman -Qu | wc -l"
elif command -v zypper >/dev/null 2>&1; then
package_manager="zypper"
update_cmd="zypper update"
update_check_cmd="zypper list-updates | grep -c \"^v \""
elif command -v apk >/dev/null 2>&1; then
package_manager="apk"
update_cmd="apk update && apk upgrade"
update_check_cmd="apk version -v | grep -c upgradable"
fi
}
################################
# Check for Updates
################################
check_for_updates() {
packages_available=0
security_updates=0
need_updates=false
case $package_manager in
apt)
if [ -f /usr/lib/update-notifier/apt-check ]; then
# Capture both stdout and stderr, and handle malformed output
apt_check_output=$(/usr/lib/update-notifier/apt-check 2>&1)
# Validate that we have a proper semicolon-separated output
if [[ "$apt_check_output" == *";"* ]]; then
packages_available=$(echo "$apt_check_output" | cut -d';' -f1)
security_updates=$(echo "$apt_check_output" | cut -d';' -f2)
# Validate that packages_available is a valid number
if [[ "$packages_available" =~ ^[0-9]+$ ]]; then
if [ $packages_available -gt 0 ]; then
packages="${packages_available} updates can be installed"
need_updates=true
else
packages="0 updates available"
fi
else
packages="Error parsing update count"
fi
# Validate that security_updates is a valid number
if [[ "$security_updates" =~ ^[0-9]+$ ]]; then
if [ $security_updates -gt 0 ]; then
supdates="${security_updates} security updates"
need_updates=true
else
supdates="0 security updates"
fi
else
supdates="Error parsing security updates"
fi
else
packages="Invalid update-notifier output"
fi
else
# If apt-check isn't available, try an alternative method
if command -v apt >/dev/null 2>&1; then
# This method requires root/sudo, will be empty if not available
# FIX: Properly capture and process the apt list output
apt_output=$(apt list --upgradable 2>/dev/null)
if [ $? -eq 0 ]; then
# Only count lines that actually contain "upgradable" to avoid parsing errors
packages_count=$(echo "$apt_output" | grep -c "upgradable" || echo "0")
# Ensure we have a clean numeric value
if [[ "$packages_count" =~ ^[0-9]+$ ]]; then
packages_available=$packages_count
if [ $packages_available -gt 0 ]; then
packages="${packages_available} updates can be installed"
need_updates=true
else
packages="0 updates available"
fi
else
packages="Error checking updates"
fi
else
packages="Need privileges to check updates"
fi
else
packages="apt not available"
fi
fi
;;
dnf|yum)
# Check if we can run as non-root
if timeout 5 $package_manager check-update -q &>/dev/null; then
# Capture the output in a variable and ensure it's a valid number
check_output=$($update_check_cmd)
if [[ "$check_output" =~ ^[0-9]+$ ]]; then
packages_available=$check_output
if [ $packages_available -gt 0 ]; then
packages="${packages_available} updates can be installed"
need_updates=true
else
packages="0 updates available"
fi
else
packages="Error checking updates"
fi
else
packages="Need root to check updates"
fi
;;
pacman)
if pacman -Qu &>/dev/null; then
# Ensure we get a clean numeric value
check_output=$(pacman -Qu | wc -l)
if [[ "$check_output" =~ ^[0-9]+$ ]]; then
packages_available=$check_output
if [ $packages_available -gt 0 ]; then
packages="${packages_available} updates can be installed"
need_updates=true
else
packages="0 updates available"
fi
else
packages="Error checking updates"
fi
else
packages="Need to run 'pacman -Sy' to check updates"
fi
;;
zypper)
if timeout 5 zypper list-updates &>/dev/null; then
# Ensure we get a clean numeric value
check_output=$(zypper list-updates | grep '^v ' | wc -l)
if [[ "$check_output" =~ ^[0-9]+$ ]]; then
packages_available=$check_output
if [ $packages_available -gt 0 ]; then
packages="${packages_available} updates can be installed"
need_updates=true
else
packages="0 updates available"
fi
else
packages="Error checking updates"
fi
else
packages="Need root to check updates"
fi
;;
apk)
if timeout 5 apk version -v &>/dev/null; then
# Ensure we get a clean numeric value
check_output=$(apk version -v | grep -c upgradable)
if [[ "$check_output" =~ ^[0-9]+$ ]]; then
packages_available=$check_output
if [ $packages_available -gt 0 ]; then
packages="${packages_available} updates can be installed"
need_updates=true
else
packages="0 updates available"
fi
else
packages="Error checking updates"
fi
else
packages="Need root to check updates"
fi
;;
*)
packages="Unknown package manager"
;;
esac
# Check for release upgrade
release_upgrade=""
if [ -x /usr/lib/ubuntu-release-upgrader/release-upgrade-motd ]; then
if [ -f /var/lib/ubuntu-release-upgrader/release-upgrade-available ]; then
release_upgrade="$(cat /var/lib/ubuntu-release-upgrader/release-upgrade-available)"
need_updates=true
fi
fi
}
################################
# CPU Temperature
################################
get_cpu_temp() {
cputemp="N/A"
# Method 1: lm-sensors
if command -v sensors >/dev/null 2>&1; then
temp=$(LC_ALL=C sensors | grep -m 1 -E 'Core 0|CPU Temp|Tdie' | grep -o -E '[0-9]+\.[0-9]+' | head -n1)
if [ ! -z "$temp" ]; then
cputemp=$(printf "%.0f" $temp)
fi
fi
# Method 2: /sys/class/thermal
if [ "$cputemp" = "N/A" ] && [ -d /sys/class/thermal ]; then
for zone in /sys/class/thermal/thermal_zone*/temp; do
if [ -f "$zone" ]; then
temp=$(cat "$zone")
if [ $temp -gt 0 ]; then
# Convert millidegrees to degrees
cputemp=$(( temp / 1000 ))
break
fi
fi
done
fi
# Method 3: Raspberry Pi specific
if [ "$cputemp" = "N/A" ] && [ -f /sys/class/thermal/thermal_zone0/temp ]; then
temp=$(cat /sys/class/thermal/thermal_zone0/temp)
cputemp=$(( temp / 1000 ))
fi
# Add color warning if temperature is high
if [[ "$cputemp" != "N/A" ]]; then
if [[ "$cputemp" -gt "65" ]]; then
cputemp="${ong}${blk}${cputemp}${deg_symbol}C${dfl}"
else
cputemp="${cputemp}${deg_symbol}C"
fi
fi
}
################################
# GPU Information
################################
get_gpu_info() {
gpu_info="N/A"
gpu_temp="N/A"
gpu_util="N/A"
# Check for NVIDIA GPU with nvidia-smi
if command -v nvidia-smi >/dev/null 2>&1; then
gpu_info=$(nvidia-smi --query-gpu=name --format=csv,noheader | head -n1 | cut -c -30)
gpu_temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader | head -n1)
gpu_util=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader | head -n1 | cut -d' ' -f1)
# Add color to GPU temperature
if [[ "$gpu_temp" != "N/A" ]]; then
if [[ "$gpu_temp" -gt "70" ]]; then
gpu_temp="${ong}${blk}${gpu_temp}${deg_symbol}C${dfl}"
else
gpu_temp="${gpu_temp}${deg_symbol}C"
fi
fi
have_gpu=true
return
fi
# Check for AMD GPU with rocm-smi
if command -v rocm-smi >/dev/null 2>&1; then
gpu_info=$(rocm-smi --showproductname | grep -v "===" | head -n1 | awk '{print $2}' | cut -c -30)
gpu_temp=$(rocm-smi --showtemp | grep -v "===" | head -n1 | awk '{print $2}')
# Add color to GPU temperature
if [[ "$gpu_temp" != "N/A" ]]; then
if [[ "$gpu_temp" -gt "70" ]]; then
gpu_temp="${ong}${blk}${gpu_temp}${deg_symbol}C${dfl}"
else
gpu_temp="${gpu_temp}${deg_symbol}C"
fi
fi
have_gpu=true
return
fi
# Basic GPU detection through lspci
if command -v lspci >/dev/null 2>&1; then
gpu_info=$(lspci | grep -i 'vga\|3d\|display' | head -n1 | cut -d: -f3 | cut -c -30)
if [ ! -z "$gpu_info" ]; then
have_gpu=true
return
fi
fi
have_gpu=false
}
################################
# Get Container/VM Information
################################
get_container_info() {
container_type="N/A"
container_stats="N/A"
in_container=false
# Check if running in Docker
if [ -f /.dockerenv ] || grep -q docker /proc/1/cgroup 2>/dev/null; then
container_type="Docker"
in_container=true
fi
# Check if running in LXC/LXD
if grep -q lxc /proc/1/cgroup 2>/dev/null; then
container_type="LXC"
in_container=true
fi
# Check if running in Kubernetes
if [ -f /var/run/secrets/kubernetes.io/serviceaccount/namespace ]; then
container_type="Kubernetes Pod"
in_container=true
fi
# Check for systemd-nspawn
if [ -d /run/systemd/nspawn ]; then
container_type="systemd-nspawn"
in_container=true
fi
# Check for VM
if [ "$in_container" = false ]; then
if command -v systemd-detect-virt >/dev/null 2>&1; then
virt=$(systemd-detect-virt)
if [ "$virt" != "none" ]; then
container_type="VM ($virt)"
in_container=true
fi
elif [ -e /proc/cpuinfo ]; then
if grep -q "hypervisor" /proc/cpuinfo; then
container_type="VM (unknown)"
in_container=true
fi
fi
fi
# Get Docker container stats if Docker is installed
if command -v docker >/dev/null 2>&1 && [ "$in_container" = false ]; then
container_count=$(docker ps -q 2>/dev/null | wc -l || echo "0")
if [ "$container_count" -gt 0 ]; then
container_stats="$container_count running Docker containers"
have_containers=true
else
container_stats="No running containers"
have_containers=false
fi
fi
# Get LXC container stats if LXC is installed
# if command -v lxc >/dev/null 2>&1 && [ "$in_container" = false ]; then
# container_count=$(lxc list --format csv -c s | grep -c RUNNING 2>/dev/null || echo "0")
# if [ "$container_count" -gt 0 ]; then
# container_stats="$container_stats, $container_count running LXC containers"
# have_containers=true
# fi
# fi
}
# Get public IP address using multiple fallbacks
get_wan_ip() {
wan_ip="Unavailable"
# Try multiple services with a timeout to avoid hanging
if command -v curl >/dev/null 2>&1; then
wan_ip=$(timeout 2 curl -s ifconfig.me 2>/dev/null || \
timeout 2 curl -s icanhazip.com 2>/dev/null || \
timeout 2 curl -s ipecho.net/plain 2>/dev/null || \
timeout 2 curl -s api.ipify.org 2>/dev/null || \
echo "Unavailable")
elif command -v wget >/dev/null 2>&1; then
wan_ip=$(timeout 2 wget -qO- ifconfig.me 2>/dev/null || \
timeout 2 wget -qO- icanhazip.com 2>/dev/null || \
timeout 2 wget -qO- ipecho.net/plain 2>/dev/null || \
timeout 2 wget -qO- api.ipify.org 2>/dev/null || \
echo "Unavailable")
elif command -v fetch >/dev/null 2>&1; then
wan_ip=$(timeout 2 fetch -qo- ifconfig.me 2>/dev/null || \
timeout 2 fetch -qo- icanhazip.com 2>/dev/null || \
timeout 2 fetch -qo- ipecho.net/plain 2>/dev/null || \
timeout 2 fetch -qo- api.ipify.org 2>/dev/null || \
echo "Unavailable")
fi
echo "$wan_ip"
}
################################
# Get Service Status
################################
get_service_status() {
services_down=()
if command -v systemctl >/dev/null 2>&1; then
for service in $critical_services; do
if systemctl is-active --quiet $service 2>/dev/null; then
# Service is running, do nothing
:
elif systemctl is-enabled --quiet $service 2>/dev/null; then
# Service is enabled but not running
services_down+=("$service")
fi
done
elif command -v service >/dev/null 2>&1; then
for service in $critical_services; do
if service $service status &>/dev/null; then
# Service is running, do nothing
:
else
# Check if service exists but is not running
if [ -f "/etc/init.d/$service" ]; then
services_down+=("$service")
fi
fi
done
fi
if [ ${#services_down[@]} -gt 0 ]; then
service_status="${lrd}Services down: ${services_down[*]}${dfl}"
have_service_issues=true
else
service_status="${grn}All monitored services running${dfl}"
have_service_issues=false
fi
}
################################
# Get Security Information
################################
get_security_info() {
failed_logins=0
last_login="N/A"
ssh_sessions=0
# Get failed login attempts
if command -v journalctl >/dev/null 2>&1; then
failed_logins=$(journalctl -u sshd --since yesterday | grep -c "Failed password")
elif [ -f /var/log/auth.log ]; then
failed_logins=$(grep -c "Failed password" /var/log/auth.log)
fi
# Get last successful login
if command -v last >/dev/null 2>&1; then
last_user=$(who am i | awk '{print $1}')
if [ ! -z "$last_user" ]; then
last_login=$(last -n 2 $last_user | head -n 1 | awk '{print $4" "$5" "$6" "$7}')
fi
fi
# Get current SSH sessions
if command -v who >/dev/null 2>&1; then
ssh_sessions=$(who | grep -c "pts")
fi
# Get firewall status without requiring root privileges
if command -v ufw >/dev/null 2>&1; then
# Check if ufw is loaded in kernel modules - doesn't require root
if lsmod | grep -q "^ufw"; then
# Check if there are any ufw rules by looking at iptables (if readable)
if [ -r /etc/ufw/user.rules ]; then
ufw_rules=$(grep -c "^-A" /etc/ufw/user.rules)
if [ $ufw_rules -gt 0 ]; then
firewall_status="UFW: likely active with $ufw_rules rules"
else
firewall_status="UFW: likely inactive (no rules found)"
fi
else
# Check status of ufw service without sudo
if systemctl is-active --quiet ufw 2>/dev/null; then
firewall_status="UFW: service running"
else
firewall_status="UFW: service not running"
fi
fi
else
firewall_status="UFW: module not loaded (inactive)"
fi
elif command -v firewall-cmd >/dev/null 2>&1; then
# For firewalld, check if the service is running
if systemctl is-active --quiet firewalld 2>/dev/null; then
firewall_status="firewalld: service running"
else
firewall_status="firewalld: service not running"
fi
elif command -v iptables >/dev/null 2>&1; then
# Check for iptables rules (non-root might not see all)
if iptables -L -n 2>/dev/null | grep -q "REJECT\|DROP"; then
firewall_status="iptables: active"
elif [ -r /etc/iptables/rules.v4 ] || [ -r /etc/sysconfig/iptables ]; then
# Check for saved rules files
firewall_status="iptables: likely active (rules file exists)"
elif systemctl is-active --quiet iptables 2>/dev/null; then
firewall_status="iptables: service running"
else
firewall_status="iptables: status unknown"
fi
else
firewall_status="No firewall detected"
fi
}
################################
# Get SMART Disk Status
################################
get_smart_status() {
smart_alerts=()
if command -v smartctl >/dev/null 2>&1; then
for disk in $(lsblk -d -o name | grep -E "^($allowed_disk_prefixes_string)" | grep -v -E "^($disallowed_disk_prefixes_string)"); do
# Try to run smartctl without sudo first
smart_status=$(smartctl -H /dev/$disk 2>/dev/null)
if [ $? -ne 0 ]; then
# If failed, try with sudo if available (won't work without password prompt in most cases)
if command -v sudo >/dev/null 2>&1; then
smart_status=$(sudo -n smartctl -H /dev/$disk 2>/dev/null || echo "Permission denied")
else
smart_status="Permission denied"
fi
fi
if echo "$smart_status" | grep -q "FAILED"; then
smart_alerts+=("${lrd}/dev/$disk FAILED${dfl}")
elif echo "$smart_status" | grep -q "Permission denied"; then
continue
fi
done
fi
if [ ${#smart_alerts[@]} -gt 0 ]; then
smart_status="${lrd}SMART Alerts: ${smart_alerts[*]}${dfl}"
have_smart_issues=true
else
smart_status="${grn}No SMART alerts detected${dfl}"
have_smart_issues=false
fi
}
################################
# Get Top Processes
################################
get_top_processes() {
if command -v ps >/dev/null 2>&1; then
# Get top 3 CPU processes
top_cpu=$(ps -eo pcpu,pid,user,args --sort=-pcpu | head -n 4 | tail -n 3 | awk '{printf "%-5s %-8s %-15.15s\n", $1"%", $3, $4}')
# Get top 3 memory processes
top_mem=$(ps -eo pmem,pid,user,args --sort=-pmem | head -n 4 | tail -n 3 | awk '{printf "%-5s %-8s %-15.15s\n", $1"%", $3, $4}')
fi
}
################################
# Main Logic
################################
# Run detection functions
detect_os
detect_package_manager
check_for_updates
get_cpu_temp
# Get network information
location=$(uname -a | awk '{print $2}')
image_version=$(uname -r)
software_version="$OS $VER"
mem_usage=$(LC_ALL=C free -m | grep Mem | awk '{print $3"M/"$2"M"}')
# Get swap usage
swap_usage=$(LC_ALL=C free -m | grep Swap | awk '{if ($2 > 0) print $3"M/"$2"M"; else print "None"}')
# Get number of CPUs and load averages
num_cpus=$(nproc 2>/dev/null || grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 1)
load_check=$(LC_ALL=C uptime | sed -r 's|.*load average: ([\.0-9]+), ([\.0-9]+), ([\.0-9]+)|\1 \2 \3|g')
load_averages=$(echo "$load_check $num_cpus" | awk '{printf "5min: %.0f%% ", $1/$4*100} {printf "10min: %.0f%% ", $2/$4*100} {printf "15min: %.0f%%", $3/$4*100}' ORS=' ')
# Calculate CPU utilization
# Read /proc/stat file (for first datapoint)
read cpu user nice system idle iowait irq softirq steal guest < /proc/stat
# compute active and total utilizations
cpu_active_prev=$((user+system+nice+softirq+steal))
cpu_total_prev=$((user+system+nice+softirq+steal+idle+iowait))
sleep 0.3
# Read /proc/stat file (for second datapoint)
read cpu user nice system idle iowait irq softirq steal guest < /proc/stat
# compute active and total utilizations
cpu_active_cur=$((user+system+nice+softirq+steal))
cpu_total_cur=$((user+system+nice+softirq+steal+idle+iowait))
# compute CPU utilization (%)
cpu_util=$((100*( cpu_active_cur-cpu_active_prev ) / (cpu_total_cur-cpu_total_prev) ))
# Get uptime in human-readable format
# NOTE: this token-position parsing is locale/format-fragile (procps wording
# and column count shift across distros); LC_ALL=C at least stabilizes the
# wording. A /proc/uptime-based rewrite would be more robust; tracked as follow-up.
uptime_raw="$(LC_ALL=C uptime)"
if [[ "$(grep -iq day <<< "$uptime_raw" ; echo $?)" == "0" ]] ; then
# first logic gate, if the system has been online for at least a day
if [[ "$(grep -iq min <<< "$uptime_raw" ; echo $?)" == "0" ]] ; then
# nested logic gate. If the system has been online for more than a day
# but less than a day and an hour, the tokens shift and requires alternate parsing
s_uptime=$(awk '{print $3 " " $4 " " $5 " Minutes"}' <<< "$uptime_raw")
else # gate for if the system has been online longer than one day and one hour
s_uptime=$(awk '{print $3 " " $4 " " $5 " Hours"}' <<< "$uptime_raw")
fi
else # logic gate for if the system has been online less than a day but more than an hour
if [[ "$(grep -iq min <<< "$uptime_raw" ; echo $?)" == "1" ]] ; then
s_uptime=$(awk '{print $3 " Hours "}' <<< "$uptime_raw")
else
s_uptime=$(awk '{print $3 " Minutes "}' <<< "$uptime_raw")
fi
fi
# Get additional information if enabled
if [ "$show_gpu_info" = true ]; then
get_gpu_info
fi
if [ "$show_container_info" = true ]; then
get_container_info
fi
if [ "$show_service_status" = true ]; then
get_service_status
fi
if [ "$show_security_info" = true ]; then
get_security_info
fi
if [ "$show_smart_status" = true ]; then
get_smart_status
fi
if [ "$show_top_processes" = true ]; then
get_top_processes
fi
# Check for fsck message
if [ -x /usr/lib/update-notifier/update-motd-fsck-at-reboot ]; then
fsck_needed="$(exec /usr/lib/update-notifier/update-motd-fsck-at-reboot)"
fi
# Check if reboot required by updates
reboot_required=""
if [ -f /var/run/reboot-required ]; then
reboot_required=$(cat /var/run/reboot-required)
fi
# Network display and filtering
declare -a adapters=()
declare -a ips=()
declare -a macs=()
declare -a ifups=()
for device in $(ls /sys/class/net/ 2>/dev/null | grep -v "$filtered_adapters"); do
adapters+=($device)
_ip=$(ip -o -f inet addr show $device 2>/dev/null | awk '{print $4}' | cut -d/ -f 1 | head -n 1)
if valid-ip $_ip; then
ips+=("$_ip")
ifups+=("up")
else
ips+=("${red}disconnected${dfl}")
ifups+=("down")
fi
if [ -f /sys/class/net/${device}/address ]; then
macs+=("$(cat /sys/class/net/${device}/address)")
else
macs+=("unknown")
fi
done
# Run as a background process to avoid hanging the login
wan_ip=$(get_wan_ip &)
# Disk array setup
declare -a logicals=()
declare -a mounts=()
declare -a usages=()
declare -a freespaces=()
diskinfo=$(/bin/df -h | grep "$allowed_disk_prefixes_string" | grep -v "$disallowed_disk_prefixes_string")
logicals=($(echo "$diskinfo" | awk '{print $1}'))
mounts=($(echo "$diskinfo" | awk '{print $6}'))
usages=($(echo "$diskinfo" | awk '{print $5}'))
freespaces=($(echo "$diskinfo" | awk '{print $4}'))
# Add color to disk usage if more than 90%
for i in "${!usages[@]}"; do
usage="${usages[$i]}"
percentage="${usage%\%}"
if [[ "$percentage" -gt "90" ]]; then
usages[$i]="${lrd}${usage}${dfl}"
elif [[ "$percentage" -gt "75" ]]; then
usages[$i]="${ong}${usage}${dfl}"
fi
done
###############################################
# Display the formatted information
###############################################
boxtop
boxline ""
boxline "${bld}${unl}System Information:${dfl} ${grn}${unl}$location${dfl}"
boxline " ${bld}OS:${dfl} ${ong}${software_version}${dfl} | ${bld}Kernel:${dfl} ${pur}${image_version}${dfl}"
boxline " ${bld}Uptime:${dfl} ${s_uptime}"
# Container information
if [ "$show_container_info" = true ] && [[ "$in_container" = true || "$have_containers" = true ]]; then
boxline ""
boxline "${bld}${unl}Container/VM Information:${dfl}"
if [ "$in_container" = true ]; then
boxline " ${bld}Running in:${dfl} ${ylw}${container_type}${dfl}"
fi
if [ "$have_containers" = true ]; then
boxline " ${bld}Container Status:${dfl} ${container_stats}"
fi
fi
# Network information
boxline ""
boxline "${bld}${unl}Network Information:${dfl}"
boxline " |${unl}Interface IP Address MAC Address${dfl}"
boxline " |-------------------------------------------------------"
# Echo out network arrays with consistent spacing
for ((i=0; i<"${#adapters[@]}"; i++ )); do
# Skip displaying disconnected interfaces if flag is false
if [[ $show_disconnected != true ]] && [[ ${ifups[$i]} != "up" ]]; then
continue
fi
# Extract plain text without color codes for formatting
ip_plain=$(echo "${ips[$i]}" | sed 's/\x1b\[[0-9;]*m//g')
# Format columns with fixed widths, adding colors as requested
interface_col=$(printf "${cyn}%-15s${dfl}" "${adapters[$i]}")
# Handle IP color - yellow for connected, keep existing red for disconnected
if [[ ${ifups[$i]} == "up" ]]; then
# For connected IPs, use yellow instead of cyan
ip_col=$(printf "${ylw}%-19s${dfl}" "$ip_plain")
else
# For disconnected IPs, keep the red color but maintain spacing
ip_col="${ips[$i]}$(printf '%*s' $((19 - ${#ip_plain})) '')"
fi
# Add light blue for MAC addresses
mac_col=$(printf "${lbl}%s${dfl}" "${macs[$i]}")
# Combine the formatted columns
formatted_line="|${interface_col}${ip_col}${mac_col}"
boxline " $formatted_line"
done
# Display WAN IP separately
boxline " |-------------------------------------------------------"
boxline " ${bld}WAN IP:${dfl} ${grn}${wan_ip}${dfl}"
# System status
boxline ""
boxline "${bld}${unl}System Resources:${dfl}"
boxline " ${bld}CPU Load:${dfl} ${load_averages}"
boxline " ${bld}CPU Temp:${dfl} ${lbl}${cputemp}${dfl} | ${bld}Utilization:${dfl} ${lrd}${cpu_util}%${dfl}"
# GPU information if available
if [ "$show_gpu_info" = true ] && [ "$have_gpu" = true ]; then
boxline " ${bld}GPU:${dfl} ${ylw}${gpu_info}${dfl}"
if [ "$gpu_temp" != "N/A" ]; then
boxline " ${bld}GPU Temp:${dfl} ${lbl}${gpu_temp}${dfl} | ${bld}Utilization:${dfl} ${lrd}${gpu_util}%${dfl}"
fi
fi
# Memory usage
boxline " ${bld}Memory:${dfl} ${mem_usage} | ${bld}Swap:${dfl} ${swap_usage}"
# Storage info
boxline " ${bld}${unl}Disk Info:${dfl}"
boxline " |${unl}Usage Free Mount Volumes${dfl}"
boxline " |-----------------------------------------------"
# Loop through each disk and display info with precise column widths
for ((i=0; i<"${#logicals[@]}"; i++)); do
# Extract the usage text without color codes for formatting
usage_plain=$(echo "${usages[$i]}" | sed 's/\x1b\[[0-9;]*m//g')
# Format the columns
usage_col=$(printf "%-8s" "$usage_plain")
free_col=$(printf "%-12s" "${freespaces[$i]}")
mount_col=$(printf "%-13s" "${mounts[$i]}")
volume_col="${logicals[$i]}"
# Replace the plain text with the colored version, maintaining the same length
usage_col="${usages[$i]}$(printf '%*s' $((8 - ${#usage_plain})) '')"
# Combine the formatted columns
formatted_line="|${usage_col}${free_col}${mount_col}${volume_col}"
boxline " $formatted_line"
done
# SMART status if enabled and issues detected
if [ "$show_smart_status" = true ] && [ "$have_smart_issues" = true ]; then
boxline ""
boxline "${bld}${unl}Disk Health:${dfl}"
boxline " ${smart_status}"
fi
# Service status if enabled and issues detected
if [ "$show_service_status" = true ] && [ "$have_service_issues" = true ]; then
boxline ""
boxline "${bld}${unl}Service Status:${dfl}"
boxline " ${service_status}"
fi
# Security information if enabled
if [ "$show_security_info" = true ]; then
boxline ""
boxline "${bld}${unl}Security Information:${dfl}"
boxline " ${bld}Failed logins (24h):${dfl} ${failed_logins}"
boxline " ${bld}Active SSH sessions:${dfl} ${ssh_sessions}"
if [ "$last_login" != "N/A" ]; then
boxline " ${bld}Last login:${dfl} ${last_login}"
fi
boxline " ${bld}Firewall:${dfl} ${firewall_status}"
fi
# Power information if enabled
if [ "$show_power_info" = true ]; then
get_power_info
if [[ "$system_power" != "N/A" || "$cpu_power" != "N/A" || "$gpu_power" != "N/A" || "$battery_power" != "N/A" ]]; then
boxline ""
boxline "${bld}${unl}Power Usage:${dfl}"
if [[ "$system_power" != "N/A" ]]; then
boxline " ${bld}System:${dfl} ${ong}${system_power}${dfl}"
fi
if [[ "$cpu_power" != "N/A" ]]; then
boxline " ${bld}CPU:${dfl} ${lrd}${cpu_power}${dfl}"
fi
if [[ "$gpu_power" != "N/A" ]]; then
boxline " ${bld}GPU:${dfl} ${lrd}${gpu_power}${dfl}"
fi
if [[ "$battery_power" != "N/A" ]]; then
boxline " ${bld}Battery:${dfl} ${ylw}${battery_power}${dfl}"
fi
# Show detailed breakdown if requested
if [[ "$show_power_details" == "true" && ${#power_sources[@]} -gt 0 ]]; then
boxline " ${bld}${unl}Power Details:${dfl}"
for ((i=0; i<${#power_sources[@]}; i++)); do
boxline " $(printf "%-25s %3d%s" "${power_sources[$i]}:" "${power_values[$i]}" "${power_units[$i]}")"
done
fi
fi
fi
# Top processes if enabled
if [ "$show_top_processes" = true ]; then
boxline ""
boxline "${bld}${unl}Top CPU Processes:${dfl}"
echo "$top_cpu" | while IFS= read -r line; do
boxline " $line"
done
boxline ""
boxline "${bld}${unl}Top Memory Processes:${dfl}"
echo "$top_mem" | while IFS= read -r line; do
boxline " $line"
done
fi
# Update information if needed
if [[ "$need_updates" == "true" ]]; then
boxline ""
boxline "${bld}${unl}Updates:${dfl}"
boxline " ${packages}"
if [ ! -z "$supdates" ]; then
boxline " ${supdates}"
fi
if [ ! -z "$release_upgrade" ]; then
boxline " ${release_upgrade}"
fi
fi
# System maintenance notifications
if [ ! -z "${fsck_needed}" ] || [ ! -z "${reboot_required}" ]; then
boxline ""
boxline "${bld}${unl}System Maintenance:${dfl}"
boxline " ${fsck_needed} ${reboot_required}"
fi
boxline ""
boxbottom