colorize cli

This commit is contained in:
2026-07-13 10:51:27 -04:00
parent 43e9fd18f7
commit 681eb144ae
+54 -12
View File
@@ -83,8 +83,50 @@ state_html() {
esac esac
} }
# Terminal colors (same palette as the HTML), only when stdout is a TTY
# so piped/redirected output stays clean.
COLOR=false
C_BOLD=""
C_RED=""
C_RESET=""
if [ -t 1 ]; then
COLOR=true
C_BOLD=$'\e[1m'
C_RED=$'\e[1;38;2;207;34;46m'
C_RESET=$'\e[0m'
fi
state_txt() {
if [ "$COLOR" = true ]; then
local c
case "$1" in
PASSED|ONLINE) c=$'\e[1;38;2;26;127;55m' ;;
UNKNOWN) c=$'\e[1;38;2;154;103;0m' ;;
*) c=$'\e[1;38;2;207;34;46m' ;;
esac
printf '%s%s%s' "$c" "$1" "$C_RESET"
else
printf '%s' "$1"
fi
}
# $1 = percentage (drives the gradient), $2 = text to print. Callers pad
# $2 to its column width BEFORE calling — ANSI codes are invisible but
# still count as characters, so padding after coloring breaks alignment.
usage_txt() {
if [ "$COLOR" = true ]; then
local hex
hex=$(usage_color "$1")
printf '\e[38;2;%d;%d;%dm%s%s' \
"$((16#${hex:1:2}))" "$((16#${hex:3:2}))" "$((16#${hex:5:2}))" \
"$2" "$C_RESET"
else
printf '%s' "$2"
fi
}
# ─── SMART Disk Health (SATA/SAS) ─────────────────────────────── # ─── SMART Disk Health (SATA/SAS) ───────────────────────────────
STATUS+="SMART (SATA):\n" STATUS+="${C_BOLD}SMART (SATA):${C_RESET}\n"
for disk in /dev/sd?; do for disk in /dev/sd?; do
[ -b "$disk" ] || continue [ -b "$disk" ] || continue
@@ -103,7 +145,7 @@ for disk in /dev/sd?; do
model=$(smartctl -i "$disk" 2>/dev/null | awk -F': +' '/Device Model|Model Number/{print $2; exit}') || true model=$(smartctl -i "$disk" 2>/dev/null | awk -F': +' '/Device Model|Model Number/{print $2; exit}') || true
model="${model:-unknown}" model="${model:-unknown}"
STATUS+=" $disk ($model): $health, ${temp}C, ${hours}h\n" STATUS+=$(printf ' %-13s %-24s %5s %7s ' "$disk" "$model" "${temp}C" "${hours}h")$(state_txt "$health")"\n"
HTML_SATA+="<tr><td $TD>$disk</td><td $TD>$model</td><td $TD>$(state_html "$health")</td><td $TDR>${temp}C</td><td $TDR>${hours}h</td></tr>"$'\n' HTML_SATA+="<tr><td $TD>$disk</td><td $TD>$model</td><td $TD>$(state_html "$health")</td><td $TDR>${temp}C</td><td $TDR>${hours}h</td></tr>"$'\n'
if [ "$health" != "PASSED" ] && [ "$health" != "UNKNOWN" ]; then if [ "$health" != "PASSED" ] && [ "$health" != "UNKNOWN" ]; then
@@ -130,7 +172,7 @@ for disk in /dev/sd?; do
done done
# ─── SMART Disk Health (NVMe) ─────────────────────────────────── # ─── SMART Disk Health (NVMe) ───────────────────────────────────
STATUS+="\nSMART (NVMe):\n" STATUS+="\n${C_BOLD}SMART (NVMe):${C_RESET}\n"
for disk in /dev/nvme[0-9]*; do for disk in /dev/nvme[0-9]*; do
# Match only controller devices (nvme0, nvme1), not namespaces (nvme0n1) # Match only controller devices (nvme0, nvme1), not namespaces (nvme0n1)
[[ "$disk" =~ ^/dev/nvme[0-9]+$ ]] || continue [[ "$disk" =~ ^/dev/nvme[0-9]+$ ]] || continue
@@ -153,7 +195,7 @@ for disk in /dev/nvme[0-9]*; do
# NVMe percentage used — 100% means full rated write endurance consumed # NVMe percentage used — 100% means full rated write endurance consumed
pct_used=$(smartctl -A "$ns" 2>/dev/null | awk -F': +' '/Percentage Used/{print $2; exit}' | tr -d '%') || true pct_used=$(smartctl -A "$ns" 2>/dev/null | awk -F': +' '/Percentage Used/{print $2; exit}' | tr -d '%') || true
STATUS+=" $ns ($model): $health, ${temp}C, ${hours}h\n" STATUS+=$(printf ' %-13s %-24s %5s %7s ' "$ns" "$model" "${temp}C" "${hours}h")$(state_txt "$health")"\n"
HTML_NVME+="<tr><td $TD>$ns</td><td $TD>$model</td><td $TD>$(state_html "$health")</td><td $TDR>${temp}C</td><td $TDR>${hours}h</td></tr>"$'\n' HTML_NVME+="<tr><td $TD>$ns</td><td $TD>$model</td><td $TD>$(state_html "$health")</td><td $TDR>${temp}C</td><td $TDR>${hours}h</td></tr>"$'\n'
if [ "$health" != "PASSED" ] && [ "$health" != "UNKNOWN" ]; then if [ "$health" != "PASSED" ] && [ "$health" != "UNKNOWN" ]; then
@@ -166,16 +208,16 @@ for disk in /dev/nvme[0-9]*; do
done done
# ─── ZFS Pool Health ───────────────────────────────────────────── # ─── ZFS Pool Health ─────────────────────────────────────────────
STATUS+="\nZFS Pools:\n" STATUS+="\n${C_BOLD}ZFS Pools:${C_RESET}\n"
while IFS= read -r pool; do while IFS= read -r pool; do
[ -n "$pool" ] || continue [ -n "$pool" ] || continue
state=$(zpool list -H -o health "$pool" 2>/dev/null) || true state=$(zpool list -H -o health "$pool" 2>/dev/null) || true
state="${state:-UNKNOWN}" state="${state:-UNKNOWN}"
pool_info=$(zpool list -H -o name,size,alloc,free,cap "$pool" 2>/dev/null) || true pool_info=$(zpool list -H -o name,size,alloc,free,cap "$pool" 2>/dev/null) || true
STATUS+=" $pool_info $state\n"
# zpool -H output is tab-separated # zpool -H output is tab-separated
IFS=$'\t' read -r p_name p_size p_alloc p_free p_cap <<< "$pool_info" || true IFS=$'\t' read -r p_name p_size p_alloc p_free p_cap <<< "$pool_info" || true
STATUS+=$(printf ' %-12s %7s %7s %7s ' "${p_name:-$pool}" "$p_size" "$p_alloc" "$p_free")$(usage_txt "${p_cap%\%}" "$(printf '%5s' "$p_cap")")" "$(state_txt "$state")"\n"
cap_cell="<span style=\"color:$(usage_color "${p_cap%\%}")\">$p_cap</span>" cap_cell="<span style=\"color:$(usage_color "${p_cap%\%}")\">$p_cap</span>"
HTML_ZFS+="<tr><td $TD>${p_name:-$pool}</td><td $TDR>$p_size</td><td $TDR>$p_alloc</td><td $TDR>$p_free</td><td $TDR>$cap_cell</td><td $TD>$(state_html "$state")</td></tr>"$'\n' HTML_ZFS+="<tr><td $TD>${p_name:-$pool}</td><td $TDR>$p_size</td><td $TDR>$p_alloc</td><td $TDR>$p_free</td><td $TDR>$cap_cell</td><td $TD>$(state_html "$state")</td></tr>"$'\n'
@@ -215,7 +257,7 @@ done < <(zpool list -H -o name 2>/dev/null || true)
# Quotas are hard limits — writes fail at 100% — so warn before the kids # Quotas are hard limits — writes fail at 100% — so warn before the kids
# hit the wall. Lists every dataset with a quota set (with -p, an unset # hit the wall. Lists every dataset with a quota set (with -p, an unset
# quota reads as 0). # quota reads as 0).
STATUS+="\nQuotas:\n" STATUS+="\n${C_BOLD}Quotas:${C_RESET}\n"
HTML_QUOTA="" HTML_QUOTA=""
while IFS=$'\t' read -r ds used quota; do while IFS=$'\t' read -r ds used quota; do
[ -n "$ds" ] || continue [ -n "$ds" ] || continue
@@ -223,7 +265,7 @@ while IFS=$'\t' read -r ds used quota; do
pct=$(( used * 100 / quota )) pct=$(( used * 100 / quota ))
used_h=$(numfmt --to=iec "$used" 2>/dev/null || echo "$used") used_h=$(numfmt --to=iec "$used" 2>/dev/null || echo "$used")
quota_h=$(numfmt --to=iec "$quota" 2>/dev/null || echo "$quota") quota_h=$(numfmt --to=iec "$quota" 2>/dev/null || echo "$quota")
STATUS+=" $ds: ${pct}% (${used_h}/${quota_h})\n" STATUS+=$(printf ' %-18s ' "$ds")$(usage_txt "$pct" "$(printf '%4s' "${pct}%")")" (${used_h}/${quota_h})\n"
weight="" weight=""
if [ "$pct" -gt "$QUOTA_THRESHOLD" ]; then if [ "$pct" -gt "$QUOTA_THRESHOLD" ]; then
PROBLEMS+="[QUOTA] $ds is at ${pct}% of its ${quota_h} quota\n" PROBLEMS+="[QUOTA] $ds is at ${pct}% of its ${quota_h} quota\n"
@@ -233,7 +275,7 @@ while IFS=$'\t' read -r ds used quota; do
done < <(zfs list -Hp -o name,used,quota -t filesystem 2>/dev/null || true) done < <(zfs list -Hp -o name,used,quota -t filesystem 2>/dev/null || true)
# ─── Disk Space ────────────────────────────────────────────────── # ─── Disk Space ──────────────────────────────────────────────────
STATUS+="\nDisk Space:\n" STATUS+="\n${C_BOLD}Disk Space:${C_RESET}\n"
while IFS= read -r line; do while IFS= read -r line; do
[ -n "$line" ] || continue [ -n "$line" ] || continue
usage=$(echo "$line" | awk '{print $5}' | tr -d '%') usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
@@ -241,7 +283,7 @@ while IFS= read -r line; do
size=$(echo "$line" | awk '{print $2}') size=$(echo "$line" | awk '{print $2}')
used=$(echo "$line" | awk '{print $3}') used=$(echo "$line" | awk '{print $3}')
avail=$(echo "$line" | awk '{print $4}') avail=$(echo "$line" | awk '{print $4}')
STATUS+=" $mount: ${usage}% (${used}/${size}, ${avail} free)\n" STATUS+=$(printf ' %-12s ' "$mount")$(usage_txt "$usage" "$(printf '%4s' "${usage}%")")" (${used}/${size}, ${avail} free)\n"
weight="" weight=""
if [ "$usage" -gt "$SPACE_THRESHOLD" ] 2>/dev/null; then if [ "$usage" -gt "$SPACE_THRESHOLD" ] 2>/dev/null; then
PROBLEMS+="[SPACE] $mount is ${usage}% full\n" PROBLEMS+="[SPACE] $mount is ${usage}% full\n"
@@ -259,7 +301,7 @@ if [ "$ERROR_ONLY" = true ]; then
exit 0 exit 0
fi fi
SUBJECT="[ALERT] $HOSTNAME health check failed" SUBJECT="[ALERT] $HOSTNAME health check failed"
BODY="Health check found issues on $HOSTNAME at $(date):\n\n$PROBLEMS\n---\n" BODY="Health check found issues on $HOSTNAME at $(date):\n\n${C_RED}$PROBLEMS${C_RESET}\n---\n"
else else
if [ -n "$PROBLEMS" ]; then if [ -n "$PROBLEMS" ]; then
SUBJECT="[REPORT] $HOSTNAME health — ISSUES FOUND" SUBJECT="[REPORT] $HOSTNAME health — ISSUES FOUND"
@@ -268,7 +310,7 @@ else
fi fi
BODY="Health report for $HOSTNAME at $(date):\n\n" BODY="Health report for $HOSTNAME at $(date):\n\n"
if [ -n "$PROBLEMS" ]; then if [ -n "$PROBLEMS" ]; then
BODY+="*** ISSUES ***\n\n$PROBLEMS\n" BODY+="${C_RED}*** ISSUES ***\n\n$PROBLEMS${C_RESET}\n"
fi fi
BODY+="--- Status ---\n\n$STATUS\n---\n" BODY+="--- Status ---\n\n$STATUS\n---\n"
fi fi