driveAtlas: revamp PBS/NUC layout + fix post-outage drive map
Lint / Shell (shellcheck) (push) Failing after 10s

Remap the pbs (NUC5i5RYB/RYH) USB bays after a power outage: the ...615
NVMe dropped to 0B/hardware-error in the hot stacked rear-top bay and was
moved to the cooler front-left port. Slot keys are now position-explicit
(usb-fl/usb-fr/usb-rt/usb-rb) keyed by USB port path, with the front-left
= 3-2 (usb-0:2) move recorded and the root cause (rear-stack thermals,
exhaust vent directly above the rear USB pair) documented.

Rebuild generate_nuc_layout() as a cabinet-projection schematic: 88-col,
metal-shaded isometric chassis + rear panel with the USB pair stacked under
the fan vents and a red "bakes in CPU exhaust" callout, dashed ghost boxes
for empty ports, and green LED bars on occupied drives.

Add reusable helpers: da_color_init (capability-gated 256/truecolor/16
"hardware schematic" palette, honors NO_COLOR/tty/tput/COLORTERM; structure
grey, color encodes state only so it reads in monochrome), da_repeat, and
da_gauge (eighth-block capacity gauge). Every line is a uniform 88 display
columns in mono and color; only SAFE width-1 glyphs used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 15:43:11 -04:00
co-authored by Claude Opus 4.8
parent 028e7bf1bb
commit c018611959
+182 -32
View File
@@ -575,34 +575,174 @@ generate_micro_layout() {
#
# Side effects: Calls build_drive_map() to populate DRIVE_MAP
#------------------------------------------------------------------------------
# ══ driveAtlas :: color + glyph helpers ═══════════════════════════════════════
# da_repeat GLYPH N -- print GLYPH N times (multibyte-safe)
da_repeat() {
local out='' i
for ((i = 0; i < $2; i++)); do out+="$1"; done
printf '%s' "$out"
}
# da_color_init -- set the driveAtlas "hardware schematic" palette globals.
# Structure is steel grey; color encodes STATE only (green=ok, amber/yellow,
# red=hot, blue=cool). All vars are empty strings when color is off, so
# rendering is byte-identical to pure monochrome.
# USE_COLOR=true force on (depth still auto-picked)
# USE_COLOR=false force off
# unset/auto auto: off if NO_COLOR set, stdout not a tty,
# or terminal reports < 256 colors
# Depth: truecolor only when COLORTERM=truecolor/24bit, else 256, else 16.
da_color_init() {
DA_ST='' DA_HL='' DA_DIM='' DA_OK='' DA_YEL='' DA_HOT='' DA_BLU='' DA_CYN=''
DA_SR='' DA_RST=''
local mode="${USE_COLOR:-auto}" ncolors
ncolors="$(tput colors 2>/dev/null)"; ncolors="${ncolors:-0}"
case "$mode" in
false) return 0 ;;
auto)
[ -n "${NO_COLOR-}" ] && return 0
[ ! -t 1 ] && return 0
[ "$ncolors" -lt 256 ] 2>/dev/null && return 0
;;
esac
if [ "${COLORTERM-}" = "truecolor" ] || [ "${COLORTERM-}" = "24bit" ]; then
DA_ST=$'\033[38;2;138;144;151m' # steel mid #8a9097
DA_HL=$'\033[38;2;200;205;211m' # steel bright #c8cdd3
DA_DIM=$'\033[38;2;86;93;102m' # steel shadow #565d66
DA_OK=$'\033[38;2;51;209;122m' # LED green #33d17a
DA_YEL=$'\033[38;2;246;196;69m' # LED amber #f6c445
DA_HOT=$'\033[38;2;255;77;77m' # LED red #ff4d4d
DA_BLU=$'\033[38;2;77;184;255m' # cool blue #4db8ff
DA_CYN=$'\033[38;2;77;216;230m' # cyan accent #4dd8e6
elif [ "$ncolors" -ge 256 ] 2>/dev/null; then
DA_ST=$'\033[38;5;245m' DA_HL=$'\033[38;5;252m' DA_DIM=$'\033[38;5;238m'
DA_OK=$'\033[38;5;40m' DA_YEL=$'\033[38;5;214m' DA_HOT=$'\033[38;5;196m'
DA_BLU=$'\033[38;5;39m' DA_CYN=$'\033[38;5;51m'
else
DA_ST=$'\033[37m' DA_HL=$'\033[1;37m' DA_DIM=$'\033[90m'
DA_OK=$'\033[32m' DA_YEL=$'\033[33m' DA_HOT=$'\033[31m'
DA_BLU=$'\033[34m' DA_CYN=$'\033[36m'
fi
DA_RST=$'\033[0m'
DA_SR="${DA_RST}${DA_ST}" # "return to structure": reset attrs, steel mid
}
# da_gauge PCT WIDTH -- smooth eighth-block capacity gauge over a dim track,
# e.g. "da_gauge 62 15" -> "████████▋░░░░░░". Color it by level at call site
# (DA_OK < 70, DA_YEL < 90, DA_HOT >= 90); pad-then-color as usual.
da_gauge() {
local pct="${1:-0}" w="${2:-10}" full rem cells out='' i
local eighth=('' '▏' '▎' '▍' '▌' '▋' '▊' '▉')
[ "$pct" -lt 0 ] 2>/dev/null && pct=0
[ "$pct" -gt 100 ] 2>/dev/null && pct=100
full=$(( pct * w / 100 )); rem=$(( (pct * w * 8 / 100) % 8 )); cells=$full
for ((i = 0; i < full; i++)); do out+='█'; done
if [ "$rem" -gt 0 ] && [ "$full" -lt "$w" ]; then
out+="${eighth[rem]}"; cells=$((cells + 1))
fi
for ((i = cells; i < w; i++)); do out+='░'; done
printf '%s' "$out"
}
# ══ driveAtlas :: NUC5i5RYB/RYH chassis + drive-slot map ══════════════════════
# 88-col cabinet-projection rendering with live device names from the global
# associative array DRIVE_MAP (keys: int-os usb-fl usb-fr usb-rt usb-rb).
# Occupied ports: solid light boxes, green LED bar + device. EMPTY ports:
# dashed ghost boxes, dim. Alignment: every variable field is padded to its
# final width BEFORE color codes wrap it; borders are literal strings.
generate_nuc_layout() {
local hostname="$1"
build_drive_map
local host="${1:-unknown}"
local model=""
if command -v maybe_sudo >/dev/null 2>&1; then
model="$(maybe_sudo dmidecode -s baseboard-product-name 2>/dev/null | head -n1)"
fi
[ -n "$model" ] || model="NUC5i5RYB/RYH"
local nuc_model
nuc_model="$(maybe_sudo dmidecode -s baseboard-product-name 2>/dev/null | head -n1)"
[[ -z "$nuc_model" ]] && nuc_model="Intel NUC"
da_color_init
printf "┌─────────────────────────────────────────────────────────────┐\n"
printf "│ %-57s │\n" "$hostname - $nuc_model (Mini PC)"
printf "│ │\n"
printf "│ Storage Controllers: │\n"
while IFS= read -r ctrl; do
[[ -n "$ctrl" ]] && printf "│ %-59.59s│\n" "$ctrl"
done < <(get_storage_controllers)
printf "│ │\n"
printf "│ Internal 2.5\" SATA (OS / boot): │\n"
printf "│ ┌────────────────┐ │\n"
printf "│ │ %-14s │ │\n" "${DRIVE_MAP[int-os]:-EMPTY}"
printf "│ └────────────────┘ │\n"
printf "│ │\n"
printf "│ Rear USB 3.0 (stacked): │\n"
printf "│ ┌─────────────────────────┐ │\n"
printf "│ │ TOP (3-4): %-9s │ │\n" "${DRIVE_MAP[usb-top]:-EMPTY}"
printf "│ ├─────────────────────────┤ │\n"
printf "│ │ BOTTOM (3-3): %-9s │ │\n" "${DRIVE_MAP[usb-bot]:-EMPTY}"
printf "│ └─────────────────────────┘ │\n"
printf "└─────────────────────────────────────────────────────────────┘\n"
# fixed-width header fields (pad/truncate so the frame never moves)
local hostp modelp
printf -v hostp '%-22.22s' "$host"
printf -v modelp '%-14.14s' "$model"
# per-USB-slot pieces: device field (6 cols: LED bar + 5-char name),
# box side glyph, label color, border dash -- ghost styling for EMPTY
local fl fr rt rb io fls frs rts rbs flc frc rtc rbc fld frd rtd rbd
local flt frt flb frb rtt rbb
local pre key col v p
for pre in fl:usb-fl:B fr:usb-fr:Y rt:usb-rt:B rb:usb-rb:B; do
key="${pre#*:}"; key="${key%:*}"; col="${pre##*:}"; pre="${pre%%:*}"
v="${DRIVE_MAP[$key]:-EMPTY}"
if [ "$v" = "EMPTY" ]; then
printf -v p ' %-5.5s' "$v"
printf -v "$pre" '%s' "${DA_DIM}${p}${DA_SR}"
printf -v "${pre}s" '%s' "${DA_DIM}${DA_SR}"
printf -v "${pre}c" '%s' "${DA_DIM}"
printf -v "${pre}d" '%s' '╌'
else
printf -v p '%-5.5s' "$v"
printf -v "$pre" '%s' "${DA_OK}${p}${DA_SR}"
printf -v "${pre}s" '%s' '│'
[ "$col" = "Y" ] && printf -v "${pre}c" '%s' "${DA_YEL}" \
|| printf -v "${pre}c" '%s' "${DA_BLU}"
printf -v "${pre}d" '%s' '─'
fi
done
# box edges (11 cols; front tops carry the callout stem junction)
flt="$(da_repeat "$fld" 5)$(da_repeat "$fld" 5)"
frt="$(da_repeat "$frd" 10)"
flb="$(da_repeat "$fld" 11)"; frb="$(da_repeat "$frd" 11)"
rtt="$(da_repeat "$rtd" 11)"; rbb="$(da_repeat "$rbd" 11)"
[ "$fld" = '╌' ] && { flt="${DA_DIM}${flt}${DA_SR}"; flb="${DA_DIM}${flb}${DA_SR}"; }
[ "$frd" = '╌' ] && { frt="${DA_DIM}${frt}${DA_SR}"; frb="${DA_DIM}${frb}${DA_SR}"; }
[ "$rtd" = '╌' ] && rtt="${DA_DIM}${rtt}${DA_SR}"
[ "$rbd" = '╌' ] && rbb="${DA_DIM}${rbb}${DA_SR}"
# internal 2.5" SATA (int-os)
v="${DRIVE_MAP[int-os]:-EMPTY}"
if [ "$v" = "EMPTY" ]; then
printf -v p ' %-5.5s' "$v"; io="${DA_DIM}${p}${DA_SR}"
else
printf -v p '%-5.5s' "$v"; io="${DA_OK}${p}${DA_SR}"
fi
printf "${DA_ST}${DA_HL}┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓${DA_SR}${DA_RST}\n"
printf "${DA_ST}${DA_HL}${DA_SR} ${DA_CYN}driveAtlas${DA_SR} :: ${DA_HL}%s${DA_SR} %s - 115x111x49 mm - tall (RYH) kit ${DA_HL}${DA_SR}${DA_RST}\n" "$hostp" "$modelp"
printf "${DA_ST}${DA_HL}┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛${DA_SR}${DA_RST}\n"
printf " \n"
printf "${DA_ST} FRONT 3/4 ── cool intake side REAR ── hot exhaust side ${DA_RST}\n"
printf " \n"
printf "${DA_ST} ${DA_DIM}▄▄▄▄▄▄ ▄▄▄▄▄▄${DA_SR} < CPU fan vents ${DA_RST}\n"
printf "${DA_ST} ${DA_HOT} v v v hot air${DA_SR} ${DA_RST}\n"
printf "${DA_ST} ${DA_HL}┌──────────────────────────┐${DA_SR} ${DA_HL}╭────────────────────────────────╮${DA_SR} ${DA_RST}\n"
printf "${DA_ST} ${DA_HL}${DA_SR}${DA_DIM}░░░─░░░░░░░─░░░░░░░░─░░░░░${DA_SR}${DA_HL}╱│${DA_SR} ${DA_HL}${DA_SR} ┌%s┐ ${DA_HL}${DA_SR} ${DA_RST}\n" "$rtt"
printf "${DA_ST} ${DA_HL}${DA_SR}${DA_DIM}${DA_SR}${DA_OK}(o)${DA_SR} pwr ${DA_DIM}░░─░░░░░─░░░░░░░─${DA_SR}${DA_HL}${DA_SR}${DA_DIM}${DA_SR}${DA_HL}${DA_SR} ${DA_HL}${DA_SR} (o) ▐═▌ ▐╬▌ %s %s3-4 BLUE${DA_SR} %s ▐▄▌ ${DA_HL}${DA_SR} ${DA_RST}\n" "$rts" "$rtc" "$rts"
printf "${DA_ST} ${DA_HL}┌──────────────────────────┐${DA_SR}${DA_DIM}▒▒${DA_SR}${DA_HL}${DA_SR} ${DA_HL}${DA_SR} DC mDP GbE %s %s %smHDMI${DA_HL}${DA_SR} ${DA_RST}\n" "$rts" "$rt" "$rts"
printf "${DA_ST} ${DA_HL}${DA_SR} ╌ brushed aluminum band ╌${DA_HL}${DA_SR}${DA_DIM}${DA_SR}${DA_HL}${DA_SR} ${DA_HL}${DA_SR} ├───────────┤ ${DA_HL}${DA_SR} ${DA_RST}\n"
printf "${DA_ST} ${DA_HL}${DA_SR} ${DA_BLU}▐███▌${DA_SR} ${DA_YEL}▐███▌${DA_SR} (o) . ${DA_HL}│╱${DA_SR} ${DA_HL}${DA_SR} %s %s3-3 BLUE${DA_SR} %s ${DA_HL}${DA_SR}${DA_HOT}< bakes in${DA_SR} ${DA_RST}\n" "$rbs" "$rbc" "$rbs"
printf "${DA_ST} ${DA_HL}└────┬───────┬─────────────┘${DA_SR} ${DA_HL}${DA_SR} %s %s %s ${DA_HL}${DA_SR}${DA_HOT} CPU exhaust${DA_SR}${DA_RST}\n" "$rbs" "$rb" "$rbs"
printf "${DA_ST} │ │ audio IR ${DA_HL}${DA_SR} └%s┘ ${DA_HL}${DA_SR} ${DA_RST}\n" "$rbb"
printf "${DA_ST} ┌%s┐┌%s┐ ${DA_HL}╰────────────────────────────────╯${DA_SR} ${DA_RST}\n" "$flt" "$frt"
printf "${DA_ST} %s %s3-2 BLUE${DA_SR} %s%s%s3-1? YELLOW${DA_SR}%s ${DA_DIM}▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀${DA_SR} ${DA_RST}\n" "$fls" "$flc" "$fls" "$frs" "$frc" "$frs"
printf "${DA_ST} %s %s %s%s %s %s ${DA_RST}\n" "$fls" "$fl" "$fls" "$frs" "$fr" "$frs"
printf "${DA_ST} %s USB3 data %s%s charging %s ${DA_RST}\n" "$fls" "$fls" "$frs" "$frs"
printf "${DA_ST} └%s┘└%s┘ ${DA_RST}\n" "$flb" "$frb"
printf " \n"
printf "${DA_ST} ${DA_HL}UNDERSIDE / INSIDE${DA_SR} ${DA_HL}NOTES${DA_SR} ${DA_RST}\n"
printf "${DA_ST} ┌──────────────────────────────────┐ ${DA_RST}\n"
printf "${DA_ST} │ internal 2.5\" SATA ── mounted │ - all 4 USB3 jacks hang off ONE xHCI ${DA_RST}\n"
printf "${DA_ST} │ INVERTED in the bottom lid │ controller ── one shared kernel bus ${DA_RST}\n"
printf "${DA_ST} │ %s < int-os - OS / boot │ - M.2 2280 slot ── flat on mainboard ${DA_RST}\n" "$io"
printf "${DA_ST}${DA_DIM} ╌╌╌╌╌ flex cable to mainboard ╌╌ ${DA_SR}│ - \"?\" = port 3-1 id inferred (unverified) ${DA_RST}\n"
printf "${DA_ST} └──────────────────────────────────┘ ${DA_RST}\n"
printf "${DA_ST} ${DA_DIM}o feet o────── VESA ──────o feet o${DA_SR} ${DA_RST}\n"
# optional flourish: controller inventory from the existing helper
if command -v get_storage_controllers >/dev/null 2>&1; then
printf '\n %sSTORAGE CONTROLLERS%s\n' "${DA_HL}" "${DA_RST}"
get_storage_controllers 2>/dev/null | sed "s/^/ /"
fi
}
#------------------------------------------------------------------------------
@@ -778,18 +918,28 @@ declare -A SERVER_MAPPINGS=(
# pbs
# Intel NUC5i5RYB - Proxmox Backup Server
# int-os : internal 2.5\" SATA SSD (Samsung MZNLN128) - OS/boot, controller 00:1f.2
# usb-top : TOP rear USB 3.0 NVMe (Patriot P300 / JMicron bridge) - halfTbNVMeMirror
# usb-bot : BOTTOM rear USB 3.0 NVMe (Patriot P300 / JMicron bridge) - halfTbNVMeMirror
# usb-fl : FRONT-LEFT USB 3.0 (blue) NVMe (Patriot P300 / JMicron bridge) - halfTbNVMeMirror
# usb-fr : FRONT-RIGHT USB 3.0 (yellow, always-on charging) - normally empty
# usb-rt : REAR-TOP USB 3.0 (blue) - now empty
# usb-rb : REAR-BOTTOM USB 3.0 (blue) NVMe (Patriot P300 / JMicron bridge) - halfTbNVMeMirror
#
# The two USB bridges report an IDENTICAL serial, so these drives are keyed by USB
# *port path* (usb-0:N = kernel bus3 port N), NOT by-id/serial. Physical top/bottom
# confirmed by an LED blink test on 2026-07-15:
# TOP rear jack = port 3-4 (usb-0:4) = fw serial P300WCBA24090617615 (label 022411042502235)
# BOTTOM rear jack = port 3-3 (usb-0:3) = fw serial P300WCBA24090617536
# *port path* (usb-0:N = kernel bus3 port N), NOT by-id/serial.
# Port jacks (LED-blink verified 2026-07-15; front-left re-verified 2026-07-18):
# REAR-TOP jack = port 3-4 (usb-0:4) [blue]
# REAR-BOTTOM jack = port 3-3 (usb-0:3) [blue] = fw serial P300WCBA24090617536
# FRONT-LEFT jack = port 3-2 (usb-0:2) [blue] = fw serial P300WCBA24090617615
# FRONT-RIGHT jack = port 3-1 (usb-0:1) [yellow] (port # unverified - jack empty)
#
# 2026-07-18: after a power outage the ...615 drive dropped to 0B / hardware-error in the
# hot stacked REAR-TOP bay; moving it to FRONT-LEFT (3-2) revived it. Root cause was
# thermal - the two NVMe USB bridges sat flush against each other in the rear stack.
["pbs"]="
pci-0000:00:1f.2-ata-4 int-os
pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0 usb-top
pci-0000:00:14.0-usb-0:3:1.0-scsi-0:0:0:0 usb-bot
pci-0000:00:14.0-usb-0:2:1.0-scsi-0:0:0:0 usb-fl
pci-0000:00:14.0-usb-0:1:1.0-scsi-0:0:0:0 usb-fr
pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0 usb-rt
pci-0000:00:14.0-usb-0:3:1.0-scsi-0:0:0:0 usb-rb
"
)