Read the real UPSes: upsd lives on 3051, not 3493
Nothing was listening on 3493, so the dashboard said "connection refused" and fell back to the simulator while upsd was healthy the whole time. It binds a nonstandard port: `ss -ltnH` + a NUT handshake found it, and `upsc -L localhost:3051` lists both batteries. No host config had to change — dev now forwards 3494 -> 3051 and `/ups` shows real numbers, including an honest "battery is 5.3 years old" on the APC. Docs now say what to check before blaming the service (`ss -ltnH`, `printf 'LIST UPS\n' | nc 127.0.0.1 PORT`, `upsc -L host:port`), and the remaining UPS gap is rewritten: reads work, instant commands still need a scoped upsd.users entry — which needs a *read* of that file, because `tee` writing it blind would drop the users upsmon authenticates as. Real-hardware quirks recorded while verifying: battery.mfr.date is the date that means something (battery.date is often an unset placeholder), CyberPower reports `battery.mfr.date: CPS`, and neither driver reports a battery temperature — all three already render as unknown rather than a invented number. LLM-Generated: true
This commit is contained in:
@@ -123,12 +123,16 @@ That scheme is the history API's key — treat it as a public interface.
|
||||
- Agents run from `~/lab-agent/host_agent.py` on each host, forwarded to `127.0.0.1:9121/9122/9123`
|
||||
by `scripts/dev-tunnels.sh` (`DEV_TUNNELS`). Nothing listens on the hosts in dev.
|
||||
- Engines are reached directly: gufo `deskwork.reeselink.com:8731`, vLLM `driveripper.reeselink.com:8000`.
|
||||
- NUT 2.8.5 on driveripper: **configs live in `/etc/ups/`, not `/etc/nut/`**. `nut-server`
|
||||
is active and `nut-driver@apc` + `nut-driver@cyberpower` are both running, so `ups.conf`
|
||||
already exports two driver instances; a CyberPower PR1500LCDRT2U is attached on USB
|
||||
(`0764:0601`). **`upsd` does not bind 3493**, so reads fail with *Connection refused* and
|
||||
the dashboard falls back to the labelled simulator. A restart of `nut-server` does not fix
|
||||
it. See *Known gaps* — this needs root logs and a config read.
|
||||
- NUT 2.8.5 on driveripper: **configs live in `/etc/ups/`, not `/etc/nut/`**, and **upsd is
|
||||
on port 3051, not 3493**. Nothing listens on 3493 at all, so a default client reports
|
||||
*Connection refused* and it looks like a dead service — `ss -ltnH` plus `printf 'LIST UPS\n'
|
||||
| nc 127.0.0.1 3051` (answers `BEGIN LIST UPS`) is how that was pinned down. Two UPSes are
|
||||
exported over USB and read live through `DEV_FORWARDS="3494:driveripper-ops:3051"`:
|
||||
`apc` = APC Back-UPS RS 1500MS, `cyberpower` = CyberPower PR1500LCDRT2U.
|
||||
Real-hardware quirks the parsers already handle: `battery.mfr.date` is the useful date
|
||||
(`battery.date` is often an unset placeholder like `2001-09-25`), CyberPower reports
|
||||
`battery.mfr.date: CPS` (not a date → `unknown`, never a fake age), neither driver reports
|
||||
a battery temperature, and `ups.test.result: No test initiated` is the factory state.
|
||||
- Host access is via ssh keys as `ops`. Passwordless sudo exists but is command-scoped; the
|
||||
set we rely on is documented in `deploy/nut-sudoers` (additions are listed there for the
|
||||
work that is still blocked).
|
||||
@@ -186,12 +190,14 @@ unconfigured. Escape anything upstream-provided through `DashCharts.escapeHtml`.
|
||||
|
||||
## Known gaps / next work
|
||||
|
||||
1. **Real UPS data (blocking issue, host-side).** `upsd` on driveripper runs but binds
|
||||
nothing. Needed: root journal for `nut-server` (`journalctl -u nut-server`), a read of
|
||||
`/etc/ups/{upsd.conf,upsd.users,ups.conf}`, then `LISTEN 127.0.0.1` (plus a `dashboards`
|
||||
user with scoped `instcmds`). Then: `DEV_FORWARDS="3494:driveripper-ops:3493"`,
|
||||
`NUT_HOST=127.0.0.1 NUT_PORT=3494`, unset `NUT_SIMULATE`, and re-verify `/ups` against a
|
||||
real battery. Until then every UPS number is modelled, and the page says so.
|
||||
1. **UPS instant commands are read-only in practice.** Reads work against real upsd (see
|
||||
above), but every button is gated off with "NUT_USER / NUT_PASSWORD must name a user with
|
||||
`actions` in upsd.users" because no such user exists yet. Creating one safely needs a
|
||||
*read* of `/etc/ups/upsd.users` first — `sudo tee` can write the file but overwriting it
|
||||
blind would delete the users upsmon already authenticates as, which can take the box down
|
||||
at the wrong moment. `deploy/nut-sudoers` lists the read rules; once they are in, add
|
||||
`[dashboards]` with `instcmds` limited to the battery tests, put the credentials in
|
||||
`.env.dev`, and the buttons come alive. Never grant `actions = -`.
|
||||
2. **GPU coverage is AMD only.** `host_agent.gpus()` reads amdgpu DRM sysfs
|
||||
(`gpu_busy_percent`, `mem_info_vram_*`, hwmon temps, clocks). That covers the current
|
||||
fleet (R9700 + Strix Halo iGPU; gateway has none). An NVIDIA host would report no GPU at
|
||||
|
||||
Reference in New Issue
Block a user