Files

260 lines
8.9 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (c) Authors: https://www.armbian.com/authors
#
# This file is licensed under the terms of the GNU General Public
# License version 2. This program is licensed "as is" without any
# warranty of any kind, whether express or implied.
#
# DO NOT EDIT THIS FILE — use /etc/default/armbian-motd for overrides.
set -o pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
THIS_SCRIPT="sysinfo"
# Defaults (override in /etc/default/armbian-motd)
MOTD_DISABLE=""
PRIMARY_INTERFACE="eth0"
PRIMARY_DIRECTION="rx" # rx | tx | both | (anything else to disable)
STORAGE="/dev/sda1"
CPU_TEMP_LIMIT=60
CPU_TEMP_OFFSET=0 # °C offset
HDD_TEMP_LIMIT=60
AMB_TEMP_LIMIT=40
[[ -f /etc/default/armbian-motd ]] && . /etc/default/armbian-motd
for f in ${MOTD_DISABLE}; do
[[ "$f" == "$THIS_SCRIPT" ]] && exit 0
done
# Optional vendor helpers (ambienttemp, batteryinfo, getboardtemp)
if [[ -r /usr/lib/armbian/armbian-allwinner-battery ]]; then
# shellcheck disable=SC1091
. /usr/lib/armbian/armbian-allwinner-battery
fi
# Provide safe no-op stubs if functions are missing
command -v ambienttemp >/dev/null 2>&1 || ambienttemp() { echo ""; }
command -v batteryinfo >/dev/null 2>&1 || batteryinfo() { :; }
command -v getboardtemp >/dev/null 2>&1 || getboardtemp() { board_temp=""; }
display() {
# $1=name $2=value $3=red_limit $4=min_show_limit $5=unit $6=after
# Battery uses inverse threshold (lower is worse)
local name="$1" val="$2" red="$3" min="$4" unit="$5" after="$6"
[[ -z "$val" ]] && return 0
if awk -v v="$val" -v m="$min" -v inv="$name" 'BEGIN{
if (v+0 <= 0) exit 1;
if (inv=="Battery") exit !(v+0 >= m+0); else exit !(v+0 >= m+0)
}'; then
printf "%-14s" "${name}:"
if awk -v v="$val" -v r="$red" -v inv="$name" 'BEGIN{
if (inv=="Battery") exit !(v+0 < r+0); else exit !(v+0 > r+0)
}'; then
printf "\e[0;91m %s" "$val"
else
printf "\e[0;92m %s" "$val"
fi
printf "%s\x1B[0m" "$unit"
printf "%-11s\t" "$after"
return 1
fi
return 0
}
storage_info() {
# Root filesystem
local rootInfo storageInfo
rootInfo="$(df -hP /)"
root_usage="$(awk 'NR==2{gsub(/%/,"",$5); print $5}' <<<"$rootInfo")"
root_total="$(awk 'NR==2{print $2}' <<<"$rootInfo")"
# Optional extra storage (only if it exists and is not the root FS)
if df -hP "$STORAGE" &>/dev/null; then
storageInfo="$(df -hP "$STORAGE" | awk 'NR==2')"
if [[ -n "$storageInfo" ]] && ! grep -q " $STORAGE " <<<"$rootInfo"; then
storage_usage="$(awk '{gsub(/%/,"",$5); print $5}' <<<"$storageInfo")"
storage_total="$(awk '{print $2}' <<<"$storageInfo")"
if command -v smartctl >/dev/null 2>&1; then
local disk="${STORAGE%[0-9]}"
storage_temp="$(smartctl -l scttempsts "$disk" 2>/dev/null \
| awk -F': *' '/Current Temperature:/ {print $(NF-1)}')"
fi
fi
fi
}
# Collect sensor and storage data
amb_temp="$(ambienttemp)"
batteryinfo
storage_info
getboardtemp
critical_load=80
# Uptime, users, and load
users="$(who 2>/dev/null | wc -l | tr -d ' ')"
# Format uptime compactly: "1d 6h", "6h 17m", "17m", etc.
format_uptime() {
local uptime_sec=$(cat /proc/uptime | awk '{print int($1)}')
local days=$((uptime_sec / 86400))
local hours=$(((uptime_sec % 86400) / 3600))
local minutes=$(((uptime_sec % 3600) / 60))
local parts=()
(( days > 0 )) && parts+=("${days}d")
(( hours > 0 )) && parts+=("${hours}h")
(( minutes > 0 )) && parts+=("${minutes}m")
# If system just started, show 1m
((${#parts[@]} == 0)) && parts=("1m")
echo "${parts[*]}"
}
time="$(format_uptime)"
cpucount="$(grep -c ^processor /proc/cpuinfo 2>/dev/null || echo 1)"
read -r load1 _ < /proc/loadavg
[[ "$load1" == 0.0* ]] && load1="0.10"
load="$(awk -v l="$load1" -v c="$cpucount" 'BEGIN{printf("%.0f",(l/c)*100)}')"
# Memory / swap
read -r mem_total_kb _ < <(awk '/MemTotal:/ {print $2" "$3; exit}' /proc/meminfo)
mem_total_mb="$(( mem_total_kb / 1024 ))"
mem_used_kb="$(awk '
/MemTotal:/ {t=$2}
/MemFree:/ {f=$2}
/Buffers:/ {b=$2}
/^Cached:/ {c=$2}
END{printf("%d", t-f-b-c)}
' /proc/meminfo)"
memory_usage="$(awk -v u="$mem_used_kb" -v t="$mem_total_kb" 'BEGIN{printf("%.0f", (u/t)*100)}')"
swap_total_mb="$(awk '/SwapTotal:/ {print int($2/1024)}' /proc/meminfo)"
swap_used_mb="$(awk '/SwapFree:/ {sf=$2} /SwapTotal:/ {st=$2} END{print int((st-sf)/1024)}' /proc/meminfo)"
if [[ "${swap_total_mb:-0}" -gt 0 ]]; then
swap_usage="$(awk -v u="$swap_used_mb" -v t="$swap_total_mb" 'BEGIN{printf("%3.0f", (u/t)*100)}')"
else
swap_total_mb=0
swap_usage=0
fi
# --- spacing before Performance ---
# Add a blank line only if something printed before (e.g., IP, containers, AP)
if [[ -e /run/armbian-motd/.after_header.printed ]] \
|| [[ -e "${XDG_RUNTIME_DIR:-/tmp}/armbian-motd/.after_header.printed" ]]; then
echo ""
fi
printf "\e[0;90m Performance: \x1B[0m\n\n"
display " Load" "${load%% *}" "$critical_load" "0" "%" ""
printf " Uptime: \x1B[92m%s\x1B[0m\t" "${time:-unknown}"
display " Local users" "${users:-0}" "3" "2" "" ""
echo ""
# Humanize totals
if (( mem_total_mb > 1000 )); then
memory_total="$(awk -v m="$mem_total_mb" 'BEGIN{printf("%.2fG", m/1024)}')"
else
memory_total="${mem_total_mb}M"
fi
if (( swap_total_mb > 500 )); then
swap_total_disp="$(awk -v s="$swap_total_mb" 'BEGIN{printf("%.2fG", s/1024)}')"
else
swap_total_disp="${swap_total_mb}M"
fi
display " Memory usage" "$memory_usage" "70" "0" "%" " of ${memory_total}"
display " Zram usage" "$swap_usage" "75" "0" "%" " of ${swap_total_disp}"
echo ""
# Temperatures & root usage
# Apply optional CPU temp offset if both numeric
if [[ -n "${board_temp:-}" ]] && awk -v t="$board_temp" -v o="$CPU_TEMP_OFFSET" 'BEGIN{exit !(t ~ /^-?[0-9.]+$/ && o ~ /^-?[0-9.]+$/)}'; then
board_temp="$(awk -v t="$board_temp" -v o="$CPU_TEMP_OFFSET" 'BEGIN{printf("%.1f", t+o)}')"
fi
# Truncate temperature decimals (e.g. 37.6 → 37)
cpu_temp_int=$(printf '%.0f\n' "${board_temp%%.*}" 2>/dev/null || echo "0")
amb_temp_int=$(printf '%.0f\n' "${amb_temp%%.*}" 2>/dev/null || echo "0")
display " CPU temp" "$cpu_temp_int" "$CPU_TEMP_LIMIT" "0" "°C" ""
display " Ambient temp" "$amb_temp_int" "$AMB_TEMP_LIMIT" "0" "°C" ""
display " Usage of /" "${root_usage:-0}" "90" "1" "%" " of ${root_total} ${overlay_root}"
if command -v overlayroot-chroot >/dev/null 2>&1 && findmnt -k /media/root-ro | tail -1 | grep -qw /media/root-ro; then
echo -ne "\x1B[91m(read only rootfs)\x1B[0m"
fi
echo ""
# Optional secondary storage / disk temp / battery
a=0
display " storage /" "${storage_usage:-0}" "90" "1" "%" " of ${storage_total:-}" ; a=$((a+$?))
display " storage temp" "${storage_temp:-0}" "$HDD_TEMP_LIMIT" "0" "°C" "" ; a=$((a+$?))
display " Battery" "${battery_percent:-0}" "20" "1" "%" "${status_battery_text:-}" ; a=$((a+$?))
(( a > 0 )) && echo ""
# vnStat daily traffic (if available)
if [[ -n "$PRIMARY_INTERFACE" ]] && command -v vnstat >/dev/null 2>&1; then
if vnstat -i "$PRIMARY_INTERFACE" &>/dev/null; then
# Use C locale so decimals are '.' and units are ASCII.
line="$(LC_ALL=C vnstat -i "$PRIMARY_INTERFACE" --oneline 2>/dev/null)"
# Fields 4 and 5 are today's RX;TX totals. They may be like "205.88KiB" or "205.88 KiB".
rx_raw="$(cut -d';' -f4 <<<"$line")"
tx_raw="$(cut -d';' -f5 <<<"$line")"
# Normalize: extract integer value (truncate decimals) and unit; ensure exactly one space.
norm_val() { sed -E 's/^([0-9]+)(\.[0-9]+)?([[:space:]]*)([A-Za-z]+).*/\1/'; }
norm_unit() { sed -E 's/^([0-9]+)(\.[0-9]+)?([[:space:]]*)([A-Za-z]+).*/\4/'; }
traffic_rx_value="$(printf '%s' "$rx_raw" | norm_val)"
traffic_rx_unit="$( printf '%s' "$rx_raw" | norm_unit)"
traffic_tx_value="$(printf '%s' "$tx_raw" | norm_val)"
traffic_tx_unit="$( printf '%s' "$tx_raw" | norm_unit)"
# Handle "Not enough" (no data yet)
if [[ "$rx_raw$tx_raw" == *"Not enough"* ]]; then
traffic_rx_value="n/a"; traffic_rx_unit=""
traffic_tx_value="n/a"; traffic_tx_unit=""
fi
case "$PRIMARY_DIRECTION" in
tx)
display " TX today" "$traffic_tx_value" 500 0 " ${traffic_tx_unit}" "" ;;
rx)
display " RX today" "$traffic_rx_value" 500 0 " ${traffic_rx_unit}" "" ;;
both)
display " TX today" "$traffic_tx_value" 500 0 " ${traffic_tx_unit}" ""
display " RX today" "$traffic_rx_value" 500 0 " ${traffic_rx_unit}" "" ;;
*) : ;;
esac
echo "" # force newline after vnStat output
fi
fi
# ZFS pool summary
if command -v zpool >/dev/null 2>&1; then
zpoolstatus="$(zpool status 2>&1 || true)"
if grep -q 'not loaded' <<<"$zpoolstatus"; then
: # no line
elif grep -Eq 'degraded|OFFLINE' <<<"$zpoolstatus"; then
printf " ZFS pool: "
echo -ne "\e[0;91mDegraded\x1B[0m"
echo ""
elif grep -q 'no pools available' <<<"$zpoolstatus"; then
printf " ZFS pool: "
echo -ne "n/a"
echo ""
else
printf " ZFS pool: "
echo -ne "\e[0;92mOnline\x1B[0m"
echo ""
fi
fi
exit 0