# Shutdown Targets (LEGACY, back-compat only) !!! warning "This per-UPS `shutdown_targets[]` array is LEGACY" New enrollments use the per-machine `monitored_machines[].shutdown_method` model (`none`/`native`/`serial`/`ssh`, via `monitor add --method ...`) — see [Configuration](Configuration.md). `shutdown_targets[]` is still parsed so pre-existing config files keep loading, but a new entry should not be written here. `kind: "serial"` maps onto `shutdown_method: "serial"`; `kind: "remote"` maps onto `shutdown_method: "ssh"`; `kind: "local"` has no `monitored_machines` equivalent — the watcher host is never enrolled as a machine, so a `local` target stays the only way to describe its own shutdown. A shutdown target is a machine the orchestrator knows how to power down. Each UPS can have any number, and target entries are only transports: they say *how* to reach a device. The top-level `shutdown` policy decides *whether* any target command is allowed to run. That policy is disabled by default. The host running the daemon is protected separately by NUT's own `upsmon SHUTDOWNCMD`; orchestrator targets are optional extra actions for the devices a UPS feeds. !!! note "SSH is the fallback, not the primary path" Native NUT (upsmon secondary, enrolled via `monitor add`) is the primary graceful-shutdown mechanism; the `remote` (SSH) / `serial` targets here are a configurable, **default-off** backup. See [SSH vs. native NUT](Shutdown-Mechanisms.md) for the full analysis, the failure modes, and when each path applies. **Deferred:** a native-plus-deadman regime, where a legacy target fired only as a last resort strictly below a native secondary's own LB point, was scoped for this phase and **dropped** — it may return in a future phase. (It was also never actually implemented: `battery_below`/`runtime_below` on a target are parsed but not consulted at runtime — see Triggers, below.) ### Chosen defaults for the open questions These were live-environment unknowns resolved to defaults, not blockers: - **DEADTIME 30** on secondaries (the ≥3×POLLFREQ floor for POLLFREQ 5). - **Per-host nft scope** — each secondary's source IP added to the dedicated `table inet ups_orchestrator` set, not a broad allow. - **Serial-cable presence unknown** → legacy target `kind` defaults to `remote`; switch to `serial` where a console cable exists (network-independent, survives a dark switch). - **`nut` group present** → snippet/secret files at `0640 root:nut`, with a `0600 root:root` fallback where the group is unavailable. ## Kinds - **`serial`**: send the command over a serial console (`device` + `baud`) into a passwordless or auto-login getty. This needs no network, so it keeps working during an outage when the router/switch is also dark. It's the best primary path for a server you can reach over a console cable. - **`remote`**: run the command over SSH. `host` can be a real hostname or an `ssh_config` Host alias; leave `user` empty to connect as just `ssh `, which keeps the real hostname/port/key in `~/.ssh/config`. - **`local`**: the host the daemon runs on. Local targets run *after* every enabled serial/remote target on the same UPS, so the watcher dies last. ## Triggers Auto-shutdown is controlled from one top-level surface: - `shutdown.enabled` must be `true`. - The relevant group must be enabled: - `shutdown.external.enabled` for `serial` and `remote`. - `shutdown.internal.enabled` for `local`. - The UPS must be on battery when `require_power_outage` is true. - The UPS must have been on battery for at least `min_on_battery_seconds`. - The UPS must be close to empty by the group's central `battery_below` and `runtime_below` thresholds. If battery percent and runtime are both available, both must be at or below the group threshold. This prevents a shutdown at, for example, 50% battery while the UPS still reports 30 minutes of runtime. If one reading is unavailable, the available threshold is used. An explicit `remote_shutdown` event still respects this policy; it does not bypass disabled groups or the close-to-empty gate. ## Example !!! danger "Match your console's ACTUAL baud — a wrong value is silent" Read the real rate off the far end: `systemctl cat serial-getty@.service` on that machine. `stty` only configures the local tty and returns success at essentially any recognised rate on the same physical line, so a wrong `baud` here sends garbage down the wire and the orchestrator reports success (rc 0) anyway — it cannot detect a far-end speed mismatch, only a rejected local line configuration. Declare the real speed of the console you are wiring to, not a value copied from an example. ```json { "name": "bigserver", "kind": "serial", "enabled": true, "device": "/dev/serial/by-id/usb-FTDI_FT232R_USB_UART-if00-port0", "baud": 9600, "cmd": "sudo /sbin/shutdown -h now" } ``` ## What each kind needs on the far end - **serial**: an auto-login getty on the target's serial port. Scope the auto-login to that serial tty only; don't loosen SSH or the physical console. - **remote**: key-based SSH (BatchMode) and a passwordless `shutdown` for the SSH user. - **local**: a `sudoers.d` rule for passwordless `shutdown`/`poweroff`, which `deploy/install.sh` writes for the run user.