Deploy Network UPS Tools to monitor the server-room UPS units and feed data to
Home Assistant for power-usage tracking.
APC Smart-UPS C 1500 is fully operational: battery charge, runtime, load,
voltage, and status all reporting via usbhid-ups + APC HID 0.100 subdriver.
upsd listens on Tailscale (100.121.189.98:3493) for HA polling; local upsmon
provides graceful hypervisor shutdown on battery-low.
Key deployment fix: the udev rule must cover SUBSYSTEM=="usb" (raw
/dev/bus/usb) in addition to hidraw, because usbhid-ups opens the raw USB
device file after dropping to the nut user via setuid(). Matching only hidraw
causes EACCES on driver start.
Tripp Lite UPS (09ae:3016) is blocked — driver matches TrippLite HID 0.85
subdriver but fails reading the 878-byte HID Report Descriptor (EAGAIN via USB
hub). Needs physical reseat to direct motherboard USB port. Driver masked to
prevent restart-loop spam.
Files: ups/discover.sh, ups/setup.sh (idempotent), ups/status.sh, ups/README.md
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
60 lines
2.3 KiB
Bash
60 lines
2.3 KiB
Bash
#!/usr/bin/bash
|
|
#
|
|
# ups/status.sh — query NUT UPS state + service health (read-only)
|
|
#
|
|
# Usage (run ON the target host via remote.sh):
|
|
# PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file ups/status.sh
|
|
#
|
|
# shellcheck disable=SC2012 # ss/awk field extraction is intentional
|
|
set -uo pipefail
|
|
|
|
APC_NAME="${APC_NAME:-apc-smartups-c1500}"
|
|
TRIPP_NAME="${TRIPP_NAME:-tripp-lite-ups}"
|
|
|
|
echo "======================================================"
|
|
echo " NUT UPS Status on $(hostname)"
|
|
echo "======================================================"
|
|
|
|
echo ""
|
|
echo "--- Services ---"
|
|
for svc in "nut-driver@${APC_NAME}" "nut-driver@${TRIPP_NAME}" nut-server nut-monitor; do
|
|
if systemctl list-unit-files "$svc" >/dev/null 2>&1; then
|
|
printf " %-42s " "$svc"
|
|
systemctl is-active "$svc" 2>/dev/null || echo "(unknown)"
|
|
fi
|
|
done
|
|
|
|
for ups in "$APC_NAME" "$TRIPP_NAME"; do
|
|
echo ""
|
|
echo "--- ${ups} ---"
|
|
if upsc "${ups}@localhost" >/tmp/.nutstatus.$$ 2>&1; then
|
|
awk -v u="$ups" '
|
|
BEGIN{printf " %s\n", u}
|
|
/^battery\.charge:/ {printf " battery.charge: %s\n", $3}
|
|
/^battery\.runtime:/ {printf " battery.runtime: %ss (%.0f min)\n", $3, $3/60}
|
|
/^battery\.voltage:/ {printf " battery.voltage: %s\n", $3}
|
|
/^ups\.status:/ {printf " ups.status: %s\n", $3}
|
|
/^ups\.load:/ {printf " ups.load: %s%%\n", $3}
|
|
/^ups\.power:/ {printf " ups.power: %s\n", $3}
|
|
/^ups\.realpower:/ {printf " ups.realpower: %s W\n", $3}
|
|
/^input\.voltage:/ {printf " input.voltage: %s\n", $3}
|
|
/^output\.voltage:/ {printf " output.voltage: %s\n", $3}
|
|
/^ups\.model:/ {printf " ups.model: %s\n", $3}
|
|
/^ups\.serial:/ {printf " ups.serial: %s\n", $3}
|
|
/^device\.mfr:/ {printf " device.mfr: %s\n", $3}
|
|
' /tmp/.nutstatus.$$
|
|
echo " (full dump: upsc ${ups}@localhost)"
|
|
else
|
|
echo " NOT RESPONDING:"
|
|
sed 's/^/ /' /tmp/.nutstatus.$$
|
|
fi
|
|
rm -f /tmp/.nutstatus.$$
|
|
done
|
|
|
|
echo ""
|
|
echo "--- upsd LISTEN sockets ---"
|
|
ss -ltnp 2>/dev/null | grep -E "3493|nut" | sed 's/^/ /' || echo " (upsd not listening on 3493)"
|
|
|
|
echo ""
|
|
echo "======================================================"
|