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
-
Install the nut-client extension on a Talos node.
-
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
-
From the NUT primary on another host, issue an FSD broadcast:
-
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.
-
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:
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.
- The extension's containerd restart policy is
restart: always. When upsmon exits with rc=0, containerd re-launches it.
- 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:
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.
Summary
The
nut-clientextension'sSHUTDOWNCMDlifecycle interacts badly with the extension'srestart: alwayspolicy: when the NUT primary broadcasts FSD, the extension'supsmonreceives it, runsSHUTDOWNCMD, exits cleanly — and containerd immediately restarts it. The next poll still seesFSDonupsd, so the cycle repeats roughly every 5 seconds (the upsmonPOLLFREQdefault). The Talos node never powers off. The thrash continues until the FSD flag is cleared on the primary'supsd(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
v1.12.5(latest releasedv1.13.2; lag is not material to this report)nut-clientcarrying NUT 2.8.4 pertalosctl get extensionstatus. Upstreampower/vars.yamlcurrently pinsNUT_VERSION: 2.8.5— the +0.0.1 delta does not affect the lifecycle described here.a1112c2adc28523383a96a65e2ce7a4d1adfa3fb2ced4bed26e2ca4a2ade9a80The structural bug is present on
mainHEAD as of the time of this report:power/nut-client/nut-client.yamlstill setsrestart: always, and there have been no commits topower/nut-client/since 2026-04-10 (a dependency bump). Happy to re-run the repro onv1.13.2if the maintainers prefer; the lifecycle described here is at the extension+containerd layer and does not depend on the Talos kernel version.Repro
Install the
nut-clientextension on a Talos node.Apply an
ExtensionServiceConfigwith the standard secondary upsmon.conf:From the NUT primary on another host, issue an FSD broadcast:
Observe:
talosctl logs ext-nut-clientshows the FSD handling loop.talosctl service ext-nut-clientshows rapid restart cadence. The host never powers off.Clear the FSD flag on the primary's upsd (restart
nut-server) to stop the thrash.Observed behaviour
talosctl logs ext-nut-client:talosctl service ext-nut-client(excerpt):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:
upsmonexits cleanly after runningSHUTDOWNCMD. This is normal NUT secondary behaviour: the binary's job is finished once it has invoked the configured shutdown command.restart: always. When upsmon exits with rc=0, containerd re-launches it.FSDflag onups.statusuntil explicitly cleared (e.g. UPS service / restart ofnut-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/powerofftrick (clever —machineddispatches onargv[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-failureSingle line change in
power/nut-client/nut-client.yaml: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 stopfrom outside, which doesn't happen in this setup).Option 2 —
POWERDOWNFLAG-aware entrypoint wrapperAdd
POWERDOWNFLAG /etc/nut/killpowerto the bundledupsmon.conf. Add a small entrypoint script: if upsmon exits 0 AND/etc/nut/killpowerexists,sleep infinity(signals "host shutdown is in flight; please do not restart"). Otherwise exit non-zero to let containerd'srestartpolicy 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+ bundledPOWERDOWNFLAGset inupsmon.confso 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:
invoked from the primary's FSD path (e.g. as a custom
NOTIFYCMDfor theFSDevent in the primary's upsmon, or from a daemon that watches upsd state). Requirestalosctl+ a validtalosconfigon the primary host. Strip thenut-clientextension 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.