Skip to content

nut-client: extension thrashes on FSD instead of powering off host #1084

Description

@jhogendorn

Summary

The nut-client extension's SHUTDOWNCMD lifecycle interacts badly with the extension's restart: always policy: when the NUT primary broadcasts FSD, the extension's upsmon receives it, runs SHUTDOWNCMD, exits cleanly — and containerd immediately restarts it. The next poll still sees FSD on upsd, so the cycle repeats roughly every 5 seconds (the upsmon POLLFREQ default). The Talos node never powers off. The thrash continues until the FSD flag is cleared on the primary's upsd (e.g. operator restarts the primary's upsd).

This means the extension does not deliver host shutdown in the canonical NUT-secondary scenario it advertises.

Environment

  • Talos: v1.12.5 (latest released v1.13.2; lag is not material to this report)
  • Extension: nut-client carrying NUT 2.8.4 per talosctl get extensionstatus. Upstream power/vars.yaml currently pins NUT_VERSION: 2.8.5 — the +0.0.1 delta does not affect the lifecycle described here.
  • Schematic ID: a1112c2adc28523383a96a65e2ce7a4d1adfa3fb2ced4bed26e2ca4a2ade9a80
  • Primary: NUT 2.8.0 (Debian 12) on a separate host; UPS via SNMPv1 to an APC NMC2
  • 3 worker nodes installed with the extension; all observe the same behaviour

The structural bug is present on main HEAD as of the time of this report: power/nut-client/nut-client.yaml still sets restart: always, and there have been no commits to power/nut-client/ since 2026-04-10 (a dependency bump). Happy to re-run the repro on v1.13.2 if the maintainers prefer; the lifecycle described here is at the extension+containerd layer and does not depend on the Talos kernel version.

Repro

  1. Install the nut-client extension on a Talos node.

  2. Apply an ExtensionServiceConfig with the standard secondary upsmon.conf:

    apiVersion: v1alpha1
    kind: ExtensionServiceConfig
    name: nut-client
    configFiles:
      - content: |-
          MONITOR <ups>@<primary>:3493 1 <user> <pass> secondary
          SHUTDOWNCMD "/sbin/poweroff"
        mountPath: /usr/local/etc/nut/upsmon.conf
  3. From the NUT primary on another host, issue an FSD broadcast:

    upsmon -c fsd
  4. Observe: talosctl logs ext-nut-client shows the FSD handling loop. talosctl service ext-nut-client shows rapid restart cadence. The host never powers off.

  5. Clear the FSD flag on the primary's upsd (restart nut-server) to stop the thrash.

Observed behaviour

talosctl logs ext-nut-client:

UPS rack-ups-01@<primary>:3493: forced shutdown in progress
Executing automatic power-fail shutdown
Can't invoke wall: No such file or directory
Auto logout and shutdown proceeding
Network UPS Tools upsmon 2.8.4 release
UPS: rack-ups-01@<primary>:3493 (secondary) (power value 1)
No POWERDOWNFLAG value was configured in /usr/local/etc/nut/upsmon.conf!
POWERDOWNFLAG should be a path to file that is normally writeable for root user, ...
Warning: no custom notification command defined, just so you know
upsnotify: failed to notify about state NOTIFY_STATE_READY_WITH_PID: no notification tech defined, ...
UPS rack-ups-01@<primary>:3493: forced shutdown in progress
Executing automatic power-fail shutdown
...

talosctl service ext-nut-client (excerpt):

EVENTS   [Running]: Started task ext-nut-client (PID 81369) for container ext-nut-client (56s ago)
         [Waiting]: Runner Containerd(ext-nut-client) exited without error, going to restart it (1m1s ago)
         [Running]: Started task ext-nut-client (PID 81126) for container ext-nut-client (1m6s ago)
         [Waiting]: Runner Containerd(ext-nut-client) exited without error, going to restart it (1m11s ago)
         [Running]: Started task ext-nut-client (PID 80900) for container ext-nut-client (1m16s ago)
         ...

12+ restart/exit cycles per minute. The pattern continues until the FSD flag is removed from upsd.

The primary's upsd logs confirm the secondary connected and read ups.status "FSD OL" before each restart — so the FSD broadcast is correctly delivered and observed.

Diagnosis

Three things compose to produce the loop:

  1. upsmon exits cleanly after running SHUTDOWNCMD. This is normal NUT secondary behaviour: the binary's job is finished once it has invoked the configured shutdown command.
  2. The extension's containerd restart policy is restart: always. When upsmon exits with rc=0, containerd re-launches it.
  3. The primary's upsd retains the FSD flag on ups.status until explicitly cleared (e.g. UPS service / restart of nut-server). This is correct NUT primary behaviour — FSD is "the shutdown is committed, do not retract".

So every restart of the extension repeats the SHUTDOWNCMD invocation. The /sbin/init-bind-mounted-to-/sbin/poweroff trick (clever — machined dispatches on argv[0]) gets cut off before machined can complete its host-shutdown sequence, because the next FSD invocation reaches the extension container before machined has finished the previous one.

There is no community report of this issue in siderolabs/extensions — searches for "nut", "fsd", "shutdown", "poweroff" return zero matches. This suggests the FSD path is not commonly exercised end-to-end in production; configurations are typically validated by deploying the extension and confirming it polls, without firing a real FSD.

Proposed fixes

Three shapes, in order of minimal-change:

Option 1 — change restart policy to on-failure

Single line change in power/nut-client/nut-client.yaml:

restart: on-failure

Pros: minimal patch, intent is clear (a clean upsmon exit is "job done, don't restart"; real crashes still restart). No new code paths.
Cons: subtle behavioural change if upsmon exits cleanly for non-FSD reasons (e.g. upsmon -c stop from outside, which doesn't happen in this setup).

Option 2 — POWERDOWNFLAG-aware entrypoint wrapper

Add POWERDOWNFLAG /etc/nut/killpower to the bundled upsmon.conf. Add a small entrypoint script: if upsmon exits 0 AND /etc/nut/killpower exists, sleep infinity (signals "host shutdown is in flight; please do not restart"). Otherwise exit non-zero to let containerd's restart policy take effect.

Pros: uses NUT's documented mechanism. Works with any restart policy. Robust to surprises.
Cons: requires entrypoint script + slightly more surface area.

Option 3 — combine the above

restart: on-failure + bundled POWERDOWNFLAG set in upsmon.conf so operators get the canonical NUT lifecycle for free.


Happy to send a PR for whichever shape you prefer. Documenting here first because the bind-mount trick suggests there was deliberate design thinking around the host-shutdown path, and I'd rather defer to your preferred restart-suppression model than guess.

Workaround for operators hitting this today

External orchestration: drive Talos shutdowns from the NUT primary via the Talos API instead of relying on the extension. On the primary host:

talosctl shutdown --nodes <worker-1>,<worker-2>,...

invoked from the primary's FSD path (e.g. as a custom NOTIFYCMD for the FSD event in the primary's upsmon, or from a daemon that watches upsd state). Requires talosctl + a valid talosconfig on the primary host. Strip the nut-client extension from worker schematics in this model — it's a single point of orchestration outside the cluster, which is the right model for power-event coordination on an API-driven OS.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions