rackhands documentation
rackhands is one static binary that is both a CLI and an MCP server for server BMCs. It speaks Redfish to Dell iDRAC, HPE iLO, Lenovo XCC, Supermicro and any DMTF-conformant BMC, and falls back to IPMI 2.0. It reads freely. It changes nothing unless you enable that class of operation and confirm the single machine it touches.
Install
$ go install github.com/Blackman99/rackhands/cmd/rackhands@latest
You can also download a release archive for linux, darwin or windows (amd64 or arm64) from GitHub Releases. Releases ship with checksums.txt. There is no runtime dependency: no Python, no ipmitool. IPMI is spoken natively.
For a one-off without a config file:
$ export RACKHANDS_PASSWORD='…' $ rackhands --host https://10.20.0.11 --user root health $ rackhands --ipmi 192.168.88.40 --user ADMIN sensors 192.168.88.40 # IPMI-only box
targets.yaml
rackhands reads $RACKHANDS_CONFIG, or ~/.config/rackhands/targets.yaml, or --config FILE. Keys under defaults apply to every target unless that target sets them itself.
defaults:
username: rackhands-ro
password_env: BMC_PASSWORD
targets:
- name: db-01
redfish: https://10.20.0.11 # https:// assumed if no scheme
ipmi: 10.20.0.11 # optional; :623 assumed
tags: [prod, db]
allow: [power] # the per-target key for dangerous classes
- name: lab-x10
ipmi: 192.168.88.40 # IPMI only
password_file: ~/.config/rackhands/lab.pass
audit_log: ~/.local/state/rackhands/audit.jsonl
| Key | Meaning |
|---|---|
| name | Unique target name. You use it in commands and type it to confirm. |
| redfish | BMC base URL. If you omit it, the target is IPMI-only. |
| ipmi | host[:port] for IPMI 2.0 lanplus. With redfish set, this is the fallback. |
| ipmi_lan15 | Use IPMI 1.5 (lan) instead of lanplus, for very old BMCs. |
| username, password, password_env, password_file | Credentials. Prefer password_env or password_file. A read-only BMC account is enough for every read command. |
| insecure | Skip TLS verification. The default is true because BMCs ship self-signed certificates. Prefer tls_fingerprint or ca_file with insecure: false. |
| tls_fingerprint | SHA-256 of the BMC leaf certificate (hex, colon-hex, or sha256: prefix). Safer than insecure: true: the connection fails closed on mismatch. When set, the pin is the trust model for that target (it wins over insecure / ca_file). Print with rackhands tls-fingerprint TARGET. |
| ca_file | PEM bundle used to verify the BMC certificate (proper PKI). Ignored for trust when tls_fingerprint is set. |
| session_auth | Open one Redfish session per run (and delete it afterwards) instead of sending Basic auth on every request. |
| vendor | Force a profile: dell, hpe, lenovo, supermicro or generic. It is normally auto-detected from the service root and manager. |
| allow | Dangerous classes this machine may receive: power, boot, bmc-reset, firmware, virtual-media or all. |
| tags | Lets you select machines with tag:NAME in read commands. |
| timeout | Per-request timeout, e.g. 20s (the default). A whole command against one BMC gets three times this. |
Read commands
A TARGET is a name, all (the default), or tag:NAME. Reads run against up to 8 BMCs at a time.
| Command | What you get |
|---|---|
| rackhands targets | Configured BMCs, addresses, tags and the dangerous classes each one effectively has enabled right now. |
| rackhands inventory [T…] | Vendor, model, serial, BIOS and BMC firmware, CPU and memory summary, power state, and the effective privilege of the account rackhands logs in with (see read-only account). |
| rackhands health [T…] | Overall OK / Warning / Critical plus every component that is not OK. It uses vendor rollups where they exist (iDRAC subsystem rollups, iLO AggregateHealthStatus), and exits 1 if anything is unhealthy. |
| rackhands power [T…] | Power state. |
| rackhands sensors T [--kind K] [--problems] | Temperatures, fans, voltages, PSUs and power draw, with the critical threshold and health. K is one of temperature, fan, voltage, psu, power. |
| rackhands events T [-n 30] [--min warning] [--all-logs] | Newest entries of the hardware event log, newest first, with normalised severity. --all-logs adds the Lifecycle and audit logs. |
| rackhands firmware T | Installed firmware versions. Staged and previous copies are hidden. |
| rackhands boot T | Boot order (with display names), current override, and allowed override targets. |
| rackhands vendors | The vendor quirks rackhands handles. |
| rackhands tls-fingerprint T | Print the BMC leaf certificate SHA-256 (also rackhands pin T) so you can paste it into tls_fingerprint:. Read-only TLS probe; no BMC login. |
Dangerous operations
| Class | Command | Redfish | IPMI |
|---|---|---|---|
| power | rackhands power T on|off|force-off|restart|force-restart|cycle|nmi | ComputerSystem.Reset, mapped to what the BMC lists in AllowableValues | Chassis Control |
| boot | rackhands boot T --override pxe|cd|hdd|bios|none [--persistent] | PATCH Boot (If-Match ETag) | Set System Boot Options (EFI) |
| bmc-reset | rackhands bmc-reset T | Manager.Reset (GracefulRestart, or no body on iLO) | BMC Cold Reset |
| firmware | rackhands firmware-update T IMAGE_URL | UpdateService.SimpleUpdate with ImageURI | n/a |
| virtual-media | rackhands vmedia T insert ISO_URL | eject | InsertMedia / EjectMedia, or the iLO OEM actions | n/a |
Each one prints a plan first: what will be sent, to which URL, with which body, and the current state. Then it asks you to type the target name. In scripts, pass --yes NAME (it must be the exact name of the target). --dry-run prints the plan and stops.
$ rackhands --allow power power db-01 force-off
PLAN power force-off db-01 (Dell PowerEdge R750, ResetType=ForceOff, currently On)
POST /redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset {"ResetType":"ForceOff"}
via redfish
Type the target name (db-01) to confirm: db-01
DONE db-01: accepted (HTTP 204)
The safety model
- Off by default. Without
--allow(orRACKHANDS_ALLOW), every dangerous command is refused with exit 3, and the MCP server does not list the tools at all. - Two keys. The runtime
--allow CLASSand the target'sallow:list must both name the class. A mistyped target name or a box that nobody opened is refused. Ad-hoc--hosttargets have no config file, so for them only the runtime key applies. - One machine.
all,tag:and multiple names are refused for dangerous classes. - Confirmation. On the CLI you type the name, or pass
--yes NAME. With no terminal and no--yes, rackhands refuses. Over MCP the first call returns the plan and a token. The token is HMAC-signed, valid for 2 minutes and usable once. It is bound to the target, class, action, arguments and the protocol that will carry the request. - Audit. Every refusal, plan, rejected token, execution and failure is appended to a JSONL file.
Defence in depth: a read-only BMC account
Give agents a read-only BMC account, and keep a separate, privileged config for humans. The gate above is enforced by rackhands. The strongest guarantee is enforced by the BMC: an account with the Redfish ReadOnly role (on iLO, an account without the Virtual Power and Reset, iLO Config, Virtual Media and Host BIOS privileges) or the IPMI User privilege cannot power anything off, whatever program holds the password. Every read command works with such an account.
rackhands inventory and the MCP get_inventory tool report the effective privilege of the account each target logs in with, under account: role, can_control, privileges and where it came from. Over Redfish rackhands finds its own user in AccountService/Accounts and reads RoleId and the role's privileges, or the iLO Oem.Hpe.Privileges flags. Over IPMI it uses Get Session Info and Get User Access, and a read-only IPMI account that is refused an Administrator session gets a User session instead. When a target has no dangerous allow: classes but its account could power it off, rackhands adds a warning (and prints WARN on stderr). This is detection only: rackhands never changes BMC accounts. When the BMC hides AccountService from the account, the role is Unknown.
$ rackhands --json inventory x11 | jq '.[0].data.account'
{"user": "ADMIN", "role": "Administrator", "can_control": true,
"warning": "logs in as Administrator, which can power off or reconfigure this machine, but no dangerous class is allowed for it; use a read-only BMC account (Redfish ReadOnly role, IPMI User privilege)", …}
MCP server
{"mcpServers": {"rackhands": {
"command": "rackhands",
"args": ["mcp"],
"env": {"BMC_PASSWORD": "…"}
}}}
Point the MCP server at a config whose BMC accounts are read-only (Redfish ReadOnly, IPMI User); get_inventory shows each account's effective role and warns about over-privileged ones (details).
Add "--allow", "power" to args to expose power_action, and nothing else. Use --config to point at a specific targets file.
| Tool | Arguments |
|---|---|
| list_targets | none |
| get_inventory, get_health, get_power_state | targets: names, "all" or "tag:x" |
| get_sensors | target, kind?, only_problems? |
| get_event_log | target, limit?, min_severity?, all_logs? |
| get_firmware_inventory, get_boot_config | target |
| power_action (power) | target, action, confirm_token? |
| reset_bmc (bmc-reset) | target, confirm_token? |
| set_boot_override (boot) | target, boot_target, persistent?, confirm_token? |
| update_firmware (firmware) | target, image_uri, confirm_token? |
| virtual_media (virtual-media) | target, action, image?, confirm_token? |
Read tools are annotated readOnlyHint, and dangerous ones destructiveHint. A dangerous tool called without a token returns {"status": "confirmation_required", "plan": …, "confirm_token": …, "next": …}. Called again with the token, it returns {"status": "done", "result": …}. The server instructions tell the agent to show the plan and wait for a human yes. The token makes sure that what runs is exactly what was shown.
Vendor notes
| Vendor | What rackhands does differently |
|---|---|
| Dell iDRAC 8/9 | Reads the hardware log from Sel, with the Lifecycle log only under --all-logs. Adds the DellSystem subsystem rollups to health. Hides Previous- and Available- firmware copies. |
| HPE iLO 4/5/6 | Uses AggregateHealthStatus from Oem.Hpe (or Oem.Hp on iLO 4) for health. Maps IML Oem.Hpe.Severity (Caution becomes Warning). Fetches log entries that are not inlined. Sends Manager.Reset without a ResetType. Uses the OEM virtual media actions. Ignores thresholds reported as 0. |
| Lenovo XCC/XCC2 | Reads PlatformLog. Uses the Sensors collection when there is no Thermal/Power. Sends an If-Match ETag on every PATCH; XCC rejects a PATCH without one. |
| Supermicro X11+ | Reads Log1 on the manager. On X11 boards whose virtual media is OEM-only, virtual media is reported as unsupported up front instead of failing halfway. |
| Generic DMTF | Uses the standard schema only: Thermal/Power or Sensors, LogServices with LogEntryType SEL, SimpleUpdate, InsertMedia. |
Units are normalised (Cel to C, Percent to %), severities map to OK / Warning / Critical, and event times are UTC.
IPMI and fallback
With both redfish and ipmi set, rackhands tries Redfish first. It falls back to IPMI when the service is unreachable, does not support the operation, or answers 404, 501 or 503. It does not fall back on 401 or 403: a wrong password should be fixed, not hidden by trying another protocol. Results say via: "ipmi (fallback)", and JSON adds fallback_reason.
Over IPMI you get FRU inventory, threshold sensors with health from the threshold status, SEL, chassis status faults, power state and control, boot device and BMC cold reset. Firmware update and virtual media are Redfish-only. Over IPMI, firmware shows only the BMC revision, and boot order is not available. SDR and SEL walks retry automatically when another client cancels the reservation.
Output and exit codes
--json prints a list of {"target", "via", "fallback_reason"?, "data"?, "error"?} for fleet reads, and {"target", "via", "data"} for single-target reads. One unreachable BMC does not hide the others: its entry carries an error.
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | health found something that is not OK |
| 2 | An error occurred (including any BMC in a fleet read failing) |
| 3 | Refused, unsupported, or not confirmed; nothing was sent |
Audit log
The audit log is at ~/.local/state/rackhands/audit.jsonl by default. Override it with --audit-log FILE, RACKHANDS_AUDIT_LOG or audit_log:, or turn it off with --audit-log off. One JSON object per line:
{"time":"2026-10-12T08:14:03Z","actor":"mcp","phase":"planned","op":{"target":"db-01","class":"power","action":"force-off","args":{"via":"redfish"}},"summary":"power force-off db-01 …","method":"POST","path":"/redfish/v1/Systems/…/ComputerSystem.Reset","body":{"ResetType":"ForceOff"}}
{"time":"2026-10-12T08:14:31Z","actor":"mcp","phase":"executed","op":{…},"result":"accepted (HTTP 204)"}
Phases: refused, planned, token-rejected, executed, failed.
How it is tested
Unit tests replay vendor mockups (Dell R750, HPE DL380 Gen10, Lenovo SR650 V2, Supermicro X11) through an in-process Redfish server that records every write. CI then runs the real binary (test/integration.sh) against the DMTF Redfish-Mockup-Server, which serves DMTF's public-rackmount1 mockup and the four vendor mockups, and against OpenIPMI ipmi_sim over a real RMCP+ session. The MCP server is exercised over stdio through the full plan, token and done sequence.
FAQ
Why not just give the agent ipmitool or curl? Because nothing stands between "check the fans" and chassis power off. rackhands gives the agent the reads and nothing else unless you open a class, and even then a human approves each request.
Can I use it without an agent? Yes. The CLI is a complete tool on its own.
Does it change BIOS settings, RAID, or BMC users? No. See GOAL.md.
My vendor behaves differently. Open an issue with an anonymised capture of the relevant Redfish resources. New quirks are added as mockups first.