retry poweroff on actuation failure

This commit is contained in:
2026-08-11 18:40:59 +02:00
parent e4c34784c8
commit 1b25cd0f87
3 changed files with 97 additions and 13 deletions
+21 -13
View File
@@ -168,7 +168,14 @@ func tick(ctx context.Context, src sensor.Sensor, mon *monitor.Monitor, opts *op
}
if res.Shutdown {
powerOff(opts.shutdownCmd, log)
if err := powerOff(opts.shutdownCmd, log); err != nil {
// Re-arm. A failed poweroff must not count as a completed one, or
// the battery reaches cell cutoff and the HAT hard-cuts the
// machine — the exact outcome this feature prevents.
mon.ActuationFailed()
log.Error("poweroff failed; will retry on the next qualifying tick",
"attempt", mon.Retries(), "err", err)
}
}
}
@@ -247,24 +254,25 @@ func startWatchdog(ctx context.Context, lastTick *atomic.Int64, interval time.Du
return func() { close(done) }
}
func powerOff(command string, log *slog.Logger) {
// powerOff runs the configured poweroff command and reports whether it
// succeeded. The caller re-arms on failure.
func powerOff(command string, log *slog.Logger) error {
fields := strings.Fields(command)
if len(fields) == 0 {
log.Error("shutdown requested but -shutdown-cmd is empty")
return
}
if _, err := daemon.SdNotify(false, daemon.SdNotifyStopping); err != nil {
log.Warn("could not notify systemd that we are stopping", "err", err)
return fmt.Errorf("-shutdown-cmd is empty")
}
// Deliberately NOT sending SdNotifyStopping before the attempt. Announcing
// a stop that then fails leaves systemd believing the unit is going away
// while it keeps running. logind ends the session on a successful poweroff
// regardless.
out, err := exec.Command(fields[0], fields[1:]...).CombinedOutput()
if err != nil {
// Do not retry. A poweroff that fails needs a human, and a retry loop
// would fork repeatedly while the battery drains.
log.Error("poweroff command failed",
"cmd", command, "err", err, "output", strings.TrimSpace(string(out)))
return
if trimmed := strings.TrimSpace(string(out)); trimmed != "" {
return fmt.Errorf("%s: %w: %s", command, err, trimmed)
}
return fmt.Errorf("%s: %w", command, err)
}
log.Warn("poweroff requested", "cmd", command)
return nil
}