Skip to content

feat: Reader.Reboot and Reader.Shutdown in mqtt-rpcd (TRA-1316) - #778

Open
mikestankavich wants to merge 5 commits into
mainfrom
feat/tra-1316-mqtt-rpcd-cloud-initiated-clean-reboot-a
Open

mikestankavich wants to merge 5 commits into
mainfrom
feat/tra-1316-mqtt-rpcd-cloud-initiated-clean-reboot-a

Conversation

@mikestankavich

Copy link
Copy Markdown
Contributor

What

mqtt-rpcd takes Reader.Reboot and a new Reader.Shutdown over the broker, so an operator can cleanly reboot or power off a CS463 that has no SSH. A clean reboot is the known fix for the vendor-app wedge. A clean shutdown before planned power work avoids the unclean power cut that causes it. With the stop sweep from #774, a remote reboot also clears a stuck horn.

This is the daemon half only, per the 2026-11-08 re-scope on the ticket. The backend client and the app button come later with a prod release.

Sequence

  1. The request is logged with requested_by (optional param), the reply topic, the id and asked_at. The reply is {action: "reboot"|"shutdown", in_ms: 2000}.
  2. 2 s later, the retained <base>/status goes offline with reason: "reboot" or "shutdown". Then the daemon runs sync and systemctl reboot or systemctl poweroff. It never power-resets. systemd then stops the daemon, whose Stop drives every output off and keeps the reason.
  3. A second request while one is under way is refused (-32603 already going down). So is one while an automatic recovery reboot is announced.
  4. If systemd refuses, the reader is still up: the status goes back online, and a retry is accepted.

GetCapabilities lists both in supports[] when the daemon has a host. The CS463 adapter no longer lists Reader.Reboot as unsupported, because the daemon owns it, like Daemon.Update.

Operator CLI

MQTT_URL=… mqtt-rpcctl reboot   -base trakrf.id/<reader> -yes
MQTT_URL=… mqtt-rpcctl shutdown -base trakrf.id/<reader> -yes   # back only after an on-site power-cycle

Both refuse without -yes, and the refusal says what the command costs. -by defaults to $USER@hostname.

Also in this PR (each its own commit)

  • 1418c135 fix: a refused automatic recovery reboot used to leave the retained status saying offline for as long as the daemon kept running, because every later health update republished offline. The monitor now takes the announcement back.
  • b3d34f55 The backend's hand-kept copy of readerrpc/config.go had a stale comment. rpc.go, daemon.go and config.go are now identical in both copies.

Testing

  • just validate passes. The new daemon tests (internal/readerd/power_test.go, 11 cases, -race) use a fake host and a held timer. They cover: the reply comes before anything happens; the order is status, then host; Stop keeps the reason; a double request is refused; a request is refused during auto-recovery; a host failure brings the status back online; bad params; no host; capabilities; the log fields.
  • Not run on hardware. The bench reader was busy with the TRA-1138 soak, so this dispatch touched no reader. Still owed on cs463-212 and Trash Detector:
    • mqtt-rpcctl reboot: check the status reads offline (reboot), the reader comes back with no lookupFromJars, and the daemon reconnects within ~7 min.
    • mqtt-rpcctl shutdown, then an on-site power-cycle: check it comes back healthy.
    • With a GPO on: a reboot should drop it at the daemon's stop.
    • A second reboot within 2 s should be refused.

Refs TRA-1316

🤖 Generated with Claude Code

Mike Stankavich and others added 5 commits November 8, 2026 22:06
A clean reboot is the known fix for a wedged CS463, and customer readers
have no SSH. The daemon now takes Reader.Reboot and a new Reader.Shutdown
over the broker. It replies at once, waits 2 s so the reply gets out,
publishes the retained status offline with reason "reboot" or "shutdown",
then runs sync and systemctl reboot or poweroff. Never a power reset.

A second request while one is under way, or while an automatic recovery
reboot is announced, is refused. If systemd refuses, the reader is still
up, so the status goes back online and a retry is accepted. Each request
is logged with requested_by, the reply topic and asked_at.

GetCapabilities advertises both when the daemon has a host. The CS463
adapter stops listing Reader.Reboot as unsupported: it is the daemon's.
The backend's copy of rpc.go is kept identical.

Refs: TRA-1316

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nced offline

The health monitor announces the reboot on the retained status, then asks
systemd. When systemd refused, the daemon stayed up but its status kept
saying offline: SetHealth republishes offline once a reason is set, so
every later health change repeated it, for as long as the daemon ran. The
monitor now calls AbortGoingDown on that failure, which clears the reason
and republishes online. It retries after the next window, as before.

Found while adding Reader.Reboot, which needs the same take-back.

Refs: TRA-1316

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator commands for Reader.Reboot and Reader.Shutdown. Both take the
reader offline, so both refuse without -yes, and the refusal names the
cost: minutes off the broker for a reboot, and an on-site power-cycle for a
shutdown. -by goes to the reader's log as requested_by and defaults to
$USER@hostname. status already shows a planned outage as "offline
(reboot)" or "offline (shutdown)"; tests now pin both.

Refs: TRA-1316

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The method list said Reader.Reboot was reserved and unsupported on the
CS463. It now lists both methods with the daemon's own, and a new
section gives the operator commands, the reply-then-act sequence, the
planned-outage reasons on the status, and what happens when systemd
refuses. The recovery section notes that a refused automatic reboot
republishes the reader online.

Refs: TRA-1316

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend's hand-kept copy of the contract still said Status was the
status-topic payload. The daemon's copy was corrected when StatusDoc
arrived; this one was not. Comment only: config.go is now identical in
both copies again, as rpc.go and daemon.go are.

Found while mirroring the Reader.Shutdown contract change.

Refs: TRA-1316

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment Update

✅ This PR has been successfully merged into the preview branch.

The preview environment will update shortly at: https://app.preview.trakrf.id

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant