Skip to content

UPS control

Eneru can send commands and write variables to a UPS through NUT's upscmd and upsrw clients — battery self-tests, beeper toggles, transfer-voltage tuning, and so on. It wraps the same NUT CLIs the daemon already uses for upsc, so there is nothing new to install beyond nut-client.

Safety model

UPS control is a write surface, and Eneru treats it that way:

  • Off by default. Set nut_control.enabled: true to turn it on.
  • Auth is mandatory. Eneru refuses to start if nut_control.enabled is set while api.auth.enabled is false — "auth disabled" always means read-only. Every control request requires a valid credential (a logged-in session token or an API key). See Authentication.
  • Allowlisted. Only commands in allowed_commands and variables in allowed_variables can be invoked; anything else is rejected with 403 before any NUT call. Calibration and forced-shutdown (FSD) are not in the defaults, and allowed_variables is empty by default because upsrw can change protective settings.
  • Audited. Each control action (allowed, denied, or failed) is logged with the principal who initiated it. (v7.0 adds a tamper-evident audit log.)

Configuration

api:
  enabled: true
  auth:
    enabled: true            # required for nut_control

nut_control:
  enabled: true
  username: "eneru"          # NUT upsd.users account with INSTCMD/SET actions
  password: "secret"
  allowed_commands:
    - test.battery.start
    - test.battery.start.quick
    - beeper.toggle
  allowed_variables:
    - input.transfer.low
    - input.transfer.high
  timeout: 10

The NUT account must have the matching actions granted in upsd.users, e.g.:

[eneru]
  password = secret
  instcmds = test.battery.start
  instcmds = beeper.toggle
  actions = SET

Endpoints

All require authentication and nut_control.enabled.

Method Path Purpose
GET /api/v1/ups/{name}/commands Allowlisted commands the UPS supports (plus the full supported set)
POST /api/v1/ups/{name}/command Run a command: body {"command": "beeper.toggle"}
GET /api/v1/ups/{name}/variables Allowlisted writable variables and current values
PUT /api/v1/ups/{name}/variables/{var} Set a variable: body {"value": "200"}

{name} is the UPS name (UPS@host) or its sanitized id. Commands against the same UPS are serialized so two requests can't race an INSTCMD/SET.

# Trigger a quick battery self-test (with a session bearer token)
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"command":"test.battery.start.quick"}' \
  http://127.0.0.1:9191/api/v1/ups/UPS@localhost/command

A NUT-side failure (driver offline, permission denied) is returned as 502 with the underlying message; a disallowed command or variable is 403.

The web dashboard uses the same endpoints. When signed in, it shows command buttons and writable-variable forms only for the names returned by the allowlisted list endpoints.

Per-group overrides (multi-UPS)

When different UPSes live on separate upsd servers with different credentials, override the global nut_control per group. The global block still gates the feature (enabled); each group supplies its own credentials/allowlists, falling back to the global values for any field it omits:

nut_control:
  enabled: true            # master switch (global)
  username: eneru
  password: secret
  allowed_commands: [beeper.toggle]

ups:
  - name: "UPS1@hostA"
    nut_control:           # overrides for this UPS only
      username: opA
      password: pwA
      allowed_commands: [test.battery.start, beeper.toggle]
  - name: "UPS2@hostB"     # no override -> uses the global credentials