No description
  • TypeScript 95.5%
  • Svelte 4.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Julian Imhof c69ececa87
feat: make the BMC reads host-owned task runs
Core deprecated running host work inside a route handler (core#21): the module
declares an action, the host owns the run's state, ordering and clock.

/refresh and /sel/clear both force a full re-collect, and a SEL elist is ~1.3s
of ipmitool. Until now either could land on top of the collector's own
load(true) with nothing arbitrating. They now share `lock: () => "ipmi"`, so
the BMC has one writer at a time, and onSelfConflict "drop" says the honest
thing about a second forced re-read: it is the same re-read.

Clearing the SEL is destructive and args-free, which is the case for
`console: true` — wiping the event log should leave a row naming who did it,
and Activity is now able to offer the button as well as record it.

Both routes are bound to schema actions and the Android app has no notion of a
run yet, so they start the run and wait for its verdict rather than answering
{ runId }. The 503 carrying `error`/`hint` is what puts the BMC's complaint
next to the button; `hint` keeps its own key because it is the actionable half
("pass /dev/ipmi0 into the container"). `runId` rides along for the web shell,
which follows the run to show its steps.

/identify stays a plain route. It is one ipmitool call that returns
immediately and changes nothing but a blinking LED; a run row per LED blink is
noise, and the deprecation is about work long enough to watch.

`expose` names result only — sensor counts are structure, while state.error
and its hint name the exec strategy and the device path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 12:34:47 +02:00
backend feat: make the BMC reads host-owned task runs 2026-08-12 12:34:47 +02:00
frontend feat: IPMI module — BMC sensors, chassis state and the System Event Log 2026-07-28 14:54:55 +02:00
CLAUDE.md feat: IPMI module — BMC sensors, chassis state and the System Event Log 2026-07-28 14:54:55 +02:00
opsdeck.module.json style: use the dial icon instead of the borrowed plug 2026-07-28 15:32:04 +02:00
README.md feat: IPMI module — BMC sensors, chassis state and the System Event Log 2026-07-28 14:54:55 +02:00

OpsDeck IPMI module

The baseboard management controller's view of the machine, as an OpsDeck module: fans, power supplies, voltage rails, inlet and DIMM temperatures, chassis intrusion, power draw and the System Event Log.

Why this is not part of the hardware module

Almost none of this is reachable through Linux. On a server board, hwmon typically exposes the CPU die, the NIC and a GPU and nothing else — a Supermicro H12SSL-i reports zero fan*_input, zero voltage rails, zero PSU status and no inlet temperature. A fan failing on such a machine is invisible to lm-sensors right up to the point where the CPU throttles.

The BMC has all of it, plus two things nothing else has:

  • The System Event Log — correctable and uncorrectable ECC errors, thermal trips, CATERR, PSU failures and AC loss, chassis intrusion, all recorded with timestamps by a processor that keeps running when the OS does not. Correctable ECC counts are the earliest warning a DIMM gives.
  • Power draw in watts (DCMI), measured rather than estimated.

Keeping it separate also keeps hardware generic: most machines have no BMC, and this module needs a device passed into the container.

Requirements

The host needs ipmitool and a BMC:

apt-get install ipmitool
modprobe ipmi_devintf        # plus ipmi_si; usually automatic
ls -l /dev/ipmi0             # crw------- root root

The OpsDeck container needs that device. /dev/ipmi0 is root-owned 0600 and Docker's device cgroup denies char devices that were not passed in, so this is required no matter how the binary is reached:

services:
  opsdeck:
    devices:
      - /dev/zfs
      - /dev/ipmi0        # <- IPMI

Commands run through ctx.host (chroot into the host-root bind mount), so the host's ipmitool is used, with whatever vendor OEM support its version has. The module needs no extra capabilities and does not run privileged. When something is missing it says which thing and what to change — see the Status field on the module page.

Install it:

OPSDECK_EXTERNAL_MODULES=https://git.example.com/you/module_ipmi.git#<sha>

What it shows

Module page

Headline overall sensor health, power draw, hottest sensor, cooling summary
Charts temperatures, fan speeds and power draw over the last 2 h
Chassis system power, PSU presence and failure, power/cooling/drive faults, intrusion, power restore policy
Sensors every SDR entry with its reading, the BMC's own verdict and the threshold it is judged against
BMC controller vendor and firmware, event-log usage, last read, host access

Event log page — the SEL, newest first, each record classified critical / warning / info, with an admin-only clear.

Dashboard card — power draw, hottest sensor, and one line each for cooling, power supplies and the event log.

Metrics: ipmi.temp, ipmi.fan, ipmi.volt (tagged per sensor), ipmi.power, ipmi.sensors.warn, ipmi.sensors.crit.

Alerts

Notifications are edge-triggered — a machine with two unpopulated fan headers has them all year, and announcing that every minute is how people learn to dismiss the one alert that matters. The first tick after a restart only establishes the baseline.

  • a sensor crossing into the BMC's warning or critical range
  • a sensor that had a reading and stops having one (the fan-died case; headers that were never populated stay quiet)
  • a power supply reporting failure, AC loss or absence
  • new SEL records above info
  • chassis intrusion, cooling fault, drive fault, power fault
  • the BMC becoming unreachable (once per distinct failure)

Configuration

variable default
OPSDECK_MOD_IPMI_INTERVAL_SEC 60 poll interval, clamped to 153600
OPSDECK_MOD_IPMI_IDENTIFY_SECONDS 15 how long the identify LED blinks

A poll is one sdr elist (~1.3 s) plus three cheap commands. The event log is only re-read when sel info says it changed, and the sensor thresholds are read once because they are static.

Deliberately not here

Power control. ipmitool chassis power cycle issued from a web UI running on the machine being cycled kills the thing serving the button, and the only situation where it helps — a wedged host — is the situation where OpsDeck is down too. Doing it properly means talking to the BMC out-of-band from a different machine, which is a different deployment, not a button. The identify LED is here because it is the useful half with none of the risk.

IPMI over LAN. -I lanplus would let one OpsDeck watch other machines' BMCs, but every way ipmitool accepts a password from this module's position puts it in a process argument list, and /proc/*/cmdline is world-readable. Half-safe credential handling is worse than none, so this module is in-band only.

Development

No standalone build — the module builds inside an OpsDeck core checkout:

cp -r . <core>/packages/modules/ipmi
cd <core>
deno check packages/modules/ipmi/backend/mod.ts
deno test --allow-read packages/modules/ipmi/backend/ipmitool_test.ts
deno task build:modules
OPSDECK_AUTH=disabled OPSDECK_FAKE_DATA=1 OPSDECK_MODULES=ipmi deno task start
# http://localhost:8080/m/ipmi

OPSDECK_FAKE_DATA=1 runs a synthetic BMC — it fabricates ipmitool's text and feeds it to the real parsers, so the fake path exercises the same code the real one does. Remove the copy from the core checkout afterwards; this repo is the source of truth.

Against real hardware without deploying, the commands the module runs are:

ipmitool sdr elist
TZ=UTC ipmitool sel elist
ipmitool sensor            # thresholds
ipmitool chassis status
ipmitool dcmi power reading

TZ=UTC is not cosmetic: the BMC's clock, the host's zone and the container's zone are three different opinions, and timestamps are only converted to epoch milliseconds when ipmitool printed them as UTC.