Files
roro9stack/docs/milestones/OTA.md
T
twislaandClaude Opus 5.5 4e439410cd Stop IRC by hand; remove mDNS
IrcService::disconnect() stops the session from any state: QUIT if
connected, then no more retries. /quit goes through it (before, it only
stopped a connected session; while waiting for Wi-Fi or retrying it did
nothing), and so does the new `irc stop` command. Stopped by hand,
opening the IRC App no longer reconnects; typing a line does.

mDNS is gone: it never crossed the dev box's routed network, and it
cost about 7.5 KB of RAM. Pushes go to the IP shown in Settings ->
Firmware, which drops its Name row. OTA Q54 records the change.

On the device, Debug Build: 105.6 KB free with Wi-Fi (was 98); with IRC
on TLS 54 KB free (was 46); after `irc stop`, back to 99 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 23:17:48 +02:00

2.8 KiB

OTA — Firmware Updates over Wi-Fi and from the SD card

Goal: install new firmware without a USB cable. Push it from the PC over Wi-Fi, or drop it on the SD card. Unsigned images are refused, and a broken update rolls back by itself.

Decisions (design round 2026-10-03)

# Decision
Q52 Two sources: push over Wi-Fi from the PC, and from the SD card. Pulling from Gitea releases is deferred.
Q53 Signed Update Files (ECDSA P-256 over SHA-256). The private key stays in ~/.config/roro9stack/, and the firmware embeds the public key (ADR 0003).
Q54 The device always listens for pushes on the LAN while Wi-Fi is Connected. Revised in M2: it was announced over mDNS as roro9stack-<id>.local; mDNS was removed to save RAM (it never crossed the dev box's routed network anyway). Pushes go to the IP shown in Settings → Firmware.
Q55 New firmware runs on Probation. It's confirmed once booted, UI drawn, Services started, 30 s without a crash, and Wi-Fi connected (if configured). Otherwise Rollback. A Toast reports either outcome.
Q56 Downgrades are allowed, with "older than the installed version" shown.
Q57 A valid push installs right away: progress screen, then reboot. The reboot waits for Text Entry to end, 60 s at most.

Done when

  • scripts/ota_keygen.sh creates the key pair once. The public key is committed; the private key never is.
  • scripts/flash.sh --ota builds, signs and pushes to roro9stack-<id>.local. The device shows progress, reboots, and a Toast confirms the new version.
  • An Update File with a bad signature, a truncated or corrupted image, or no signature is refused, and the device keeps running.
  • Settings → About → Update from SD lists the .ota files in /updates and installs one.
  • A firmware that crashes during Probation rolls back to the previous version, and says so after the reboot.

Work breakdown

  1. Update File format (host-tested): header (magic, format, version, image size, SHA-256), signature, image. A streaming parser that hashes as it goes and decides accept / refuse / downgrade. The signature verifier sits behind an interface, so tests can inject one.
  2. PC side: key generation, make_ota.py (wraps firmware.bin into a signed .ota), and the push client. flash.sh --ota ties them together.
  3. Device: the Update Service.
    • A listener on TCP 3232 plus mDNS.
    • Writes the image to the inactive app slot, with the ECDSA check through mbedTLS.
    • A progress screen, and a reboot that waits out Text Entry.
  4. Probation and Rollback: the health checks, confirming the image, and detecting a rollback after reboot to report it.
  5. Update from SD: the same parser, fed from the Storage Service's task (all card access stays there).