Public Access
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
34 lines
2.8 KiB
Markdown
34 lines
2.8 KiB
Markdown
# 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).
|