260 lines
8.9 KiB
Bash
Executable File
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
|