Skip to content

Updates (OTA)

The device updates itself over-the-air from a signed update bundle the host pushes over the API — no SD card removal, no console access. It uses two system slots (A/B) so a bad update automatically rolls back instead of bricking the device.

How A/B updates work

flowchart TD
    A["Slot A: running<br/>(active)"] -->|"PUT /update/bundle"| B["Bundle verified,<br/>installed to Slot B (inactive)"]
    B -->|"POST /update/apply"| C["Reboot: trial boot<br/>into Slot B"]
    C -->|"Boot succeeds:<br/>waiting page + USB up"| D["displayd marks Slot B good<br/>(rauc mark-good)"]
    D --> E["Slot B is now active.<br/>Slot A kept as fallback"]
    C -->|"Boot fails / never confirms"| F["Firmware falls back to<br/>Slot A on next watchdog reboot"]
    F --> G["update status reports<br/>the rollback"]

The device only ever boots the slot it's currently confirmed-good on, or trials the other slot once; it never gets stuck alternating or stuck unbootable from an update failure by itself.

Check current update status

curl http://10.99.0.1/api/v1/update

Returns the active slot, each slot's installed version, its health, and the result of the last install (including whether it rolled back).

Web UI: System page — update/slot status.

Upload and install an update bundle

curl -X PUT http://10.99.0.1/api/v1/update/bundle \
  --data-binary @mycustomerdisplay-2026.07.raucb \
  -H 'Content-Type: application/octet-stream'

Returns 202 Accepted with an operation resource while the device streams, cryptographically verifies (against a keyring baked into the image — an unsigned or wrongly-signed bundle is rejected), and installs the bundle to the inactive slot. Poll GET /api/v1/operations/{id} or subscribe to operation events (see Events) to track progress; the currently running system is untouched throughout this step, so the device keeps showing whatever it was showing.

Web UI: System page → Upload update bundle.

Apply the update (reboot into the new slot)

Uploading does not reboot the device by itself — that's a separate, explicit step so you control exactly when the device goes offline:

curl -X POST http://10.99.0.1/api/v1/update/apply

This marks the newly-installed slot for the next boot and reboots immediately. The device comes back up on the new slot; once displayd confirms the browser reached the waiting page and the USB link is up, it marks that boot good. If it never reaches that point (crash loop, corrupted install, etc.), the Raspberry Pi firmware automatically falls back to the previous slot on the next watchdog-triggered reboot — no host action needed to recover, though you should re-check GET /update afterwards to see the rollback reported.

Web UI: System page → Apply update & reboot.

Planning an update rollout

  • Push the bundle (PUT /update/bundle) ahead of time — it doesn't affect the running device, so you can stage it whenever convenient (e.g. overnight) and only call apply during a maintenance window.
  • Always check GET /update after apply and a reboot to confirm the new slot is active and healthy before considering the rollout for that device done — don't assume success just because apply returned 200.
  • A power loss between apply and the trial boot completing falls back to the previous slot silently (the tryboot flag doesn't survive a power cut, only a normal reboot); GET /update reports this case distinctly from a successful rollback so you can tell "update never really tried" from "update tried and failed".
  • Because the update is a signed bundle, only bundles built and signed with your release process will install — building and signing the .raucb bundle itself is a build-time, not a device-facing, concern; it's outside the scope of this guide.

See also

  • Configuration — general PATCH /config semantics used elsewhere in the API.
  • Factory reset — wiping the data partition, independent of the OTA slots (a factory reset does not touch which system slot is active).