Skip to content

OTA and update UX: nightly installs, update now, banners, honest outcomes, clock trust, board report - #3

Merged
IbraTech04 merged 11 commits into
developfrom
claude/awesome-turing-bdgpfd
Sep 25, 2026
Merged

IbraTech04 merged 11 commits into
developfrom
claude/awesome-turing-bdgpfd

Conversation

@IbraTech04

@IbraTech04 IbraTech04 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Nightly OTA updates. Release channel updates wait in /var/lib/musallahboard/updates/ and install as one batch in the install window: update_time (default 23:00, in the content's time zone) for 4 hours.

  • A board with no app installed gets its first app straight away.
  • /api/local/status has updates (now including autoUpdate, lastCheckAt and lastCheckError), and SSE sends an updates event, which drives the ticker line.

Install now. sudo musallahboard-agent update now and the update.install_now remote command check the channels and install at once. The command's deadline covers only the download. After a failed check the command no longer says "up to date".

Plain outcomes, screen or banner. Every batch ends in one notice (internal/notice): a tone, a headline, up to three plain lines, and an optional footer. File names never appear; technical reasons go in a result's new detail.

  • The update screen appears only when something is about to be installed. Otherwise a person at the board sees a banner over the running board (the new notice SSE event).
  • Older content, app or agent is outdated, not rejected.
  • The update screen's result state takes a tone, lines that wrap, and a drawn ring with an exclamation mark for problems.

USB feedback.

  • The helper unmounts the stick before handing its files over, so every USB outcome ends with "You can remove the USB stick".
  • It also announces "Reading USB stick", "Can't read this USB stick" (with the reason), "No updates on this USB stick" and "USB updates are turned off".

Truthful agent updates (internal/agentupdate):

  • The new agent shows the outcome when it starts.
  • After a rollback, the previous agent explains what happened.
  • A refusal by the root updater is detected and the inbox resumes (previously "Restarting" stayed on screen forever).
  • Each outcome is shown once, and a batch queued behind an agent update shares its screen.

Upload page.

  • An agent update is reported as "Restarting to finish the update", with the queued items listed. The page follows the restart until the new agent answers, or reports a rollback, a refusal, or a board that never came back.
  • Uploads take up to 16 packages.

Clock trust, board report, fades.

  • clock in the status gives the time's source and whether it can be trusted.
  • Every heartbeat carries telemetry.board, including the offline routes and the update-check status.
  • Switching to and from the update screen dips to black.

Pre-pilot audit fixes

  • Crash loops:
    • The agent unit never gives up restarting; it backs off from 5 s to 2 minutes.
    • The kiosk opens a local starting.html that waits for the agent (/_mb/alive.gif) before moving to the board, so Chromium's "refused to connect" page no longer shows. Checked in real Chromium.
  • Offline routes: the no-app page and the waiting screens offer only the routes the board accepts (the status has servicePort and usbImport).
  • enroll: plain output, requires root, refuses to re-enroll without --force, and says what happens next.
  • status: shows what the screen is showing, the LensBridge connection, the offline routes, update-check failures, and readable times.
  • setup.sh:
    • Prompts read from /dev/tty, so curl | bash asks its questions.
    • No personal default for the admin user; the admin user and SSH key are validated.
    • The normal setup prepares the service port (left off), so service-port on works without a local copy of setup.sh.
    • The README matches all of this.
  • Enrollment splash:
    • One embedded copy (musallahboard-agent splash install, refreshed after each self-update), in the board's navy and gold.
    • It shows the agent version and the enroll step.
  • Readability: update screen text is sized for reading across a room.

Test plan

  • go test ./..., go test -race on the new packages, and go vet ./... all pass.
  • New tests cover updates, notice, agentupdate, the importer (banner vs screen, Summarize, USB notices and footer), usbimport (a notice per failure case), selfupdate (the outcome is written before the restart), clock (Info), localserver (BoardReport, no-app routes) and splash (version push).
  • systemd-analyze verify passes on the new agent unit.
  • The setup.sh prompt helper was tried both with and without a terminal.
  • Rendered the update screen, the banners, the splash and the starting page in headless Chromium at 1920x1080.
  • Not yet run on a Pi.

Companion PRs: LensBridge/MusallahBoard#3, LensBridge/LensBridgeBackend#20, LensBridge/LensBridgeFrontend#10.

Note: curl | bash installs fetch setup.sh from main, so the setup fixes reach new boards once develop is merged into main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy

… screen

Release channel updates no longer install the moment they are downloaded.
The channel check verifies the package, checks it would install, and hands
it to a new update scheduler (internal/updates), which keeps it in
/var/lib/musallahboard/updates and installs everything waiting as one batch
in the board's install window: update_time (default 23:00, in the content's
time zone) for four hours. A board with no app installs at once. Waiting
updates survive restarts and are dropped if they stop applying.

/api/local/status gains "updates" (what waits and when it installs) and the
SSE stream an "updates" event, so the board app can put it on the ticker.

Install now: `sudo musallahboard-agent update now` (a request file the
daemon polls, like the inbox) and the update.install_now remote command
both check the channels and install at once. `status` shows what waits.

The update screen switch is now a dip to black: the agent fades the page on
screen to black over CDP before each navigation, and the update screen
fades in from black on load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
The remote command runs under its deadline, which the admin portal sets to
30 s for most commands. The channel check (the download) still honours it;
the install that follows now always finishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Every batch now ends in one notice (internal/notice): a tone, a headline
and up to three plain lines ("Board app 2.1.0", "The board already has
newer content"), never a file name. Technical reasons move to a result's
new `detail`.

- The update screen goes up only when something is about to install.
  Otherwise a person at the board sees a banner over the running board
  (the new `notice` SSE event): "Checking updates" when a batch starts,
  then "Already up to date", "Update not installed" and so on.
- Older content, app or agent is `outdated`, not `rejected`: an old stick
  at a board that has synced since is not a failure.
- The USB helper tells the board what it is doing through notice files in
  the inbox: "Reading USB stick", "Can't read this USB stick" (and why),
  "No updates on this USB stick", "USB updates are turned off".
- Agent updates (internal/agentupdate): the new agent shows the batch's
  outcome at startup; after a rollback the previous agent shows why; a
  refusal by the root updater, which used to leave "Restarting" up for
  good, is noticed and shown, and the inbox resumes. The updater now writes
  its outcome before restarting the previous agent. Each outcome is shown
  once, and a batch queued behind an agent update shares its screen.
- The update screen's done state takes a tone and wrapping lines, with a
  drawn ring-and-mark for problems; the upload page, `import` and
  `update now` use the same wording. /api/local/status gains
  lastAgentUpdate, which `status` prints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
The clock floor stops the clock going backwards but cannot say it is
right. The keeper now reports where the time comes from: NTP
(timesyncd's synchronized flag), a fitted RTC, a laptop's time since this
boot (a verified upload that set or confirmed it, keyed to the kernel boot
id), the first three minutes after boot, or nothing. `clock` in
/api/local/status carries it, the upload page and `status` show it, and
the board app puts up a note when nothing has confirmed the time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
Heartbeats now carry `telemetry.board` (localserver.BoardReport): the
board app version, the installed content and days left, software waiting
for the install window, the last agent self-update outcome, clock trust
and the last sync error. The backend keeps the latest per device for the
admin portal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
@IbraTech04 IbraTech04 changed the title Nightly OTA updates, "update now", update screen fades OTA and update UX: nightly installs, update now, banners, honest outcomes, clock trust, board report Sep 25, 2026
The splash's footer said "MusallahBoard v2.0" in packaging/waiting.html and
something else in setup.sh's inlined copy, which had already drifted. The
agent now embeds packaging/waiting.html and installs it with
`musallahboard-agent splash install`: setup.sh calls that instead of its
heredoc, and the self-updater re-runs it after each successful update. The
footer shows "MusallahBoard · agent <version>", pushed by the running agent
with the address, so it is right after a self-update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
- The upload page no longer says "Update complete" when an agent update is
  only staged: the response says "Restarting to finish the update", lists
  what installs after the restart (queued results are no longer hidden),
  and the page follows the restart until the new agent answers, or reports
  a rollback, a refusal, or a board that does not come back.
- Uploads take up to 16 packages, like a USB stick, and the page says so.
- The USB helper unmounts the stick before handing its packages to the
  daemon, and every USB outcome carries a footer, "You can remove the USB
  stick", on the update screen and the banner.
- updates.Info reports autoUpdate, lastCheckAt and lastCheckError, so "no
  update waiting" can be told apart from "cannot check".
- `update now` no longer prints "up to date" after a failed check; it says
  nothing was installed and exits 1.
- /api/local/status and the board report carry servicePort and usbImport,
  and the "waiting for the board app" page offers only the routes this
  board accepts. The board report's content carries its time zone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
- Crash loops: the agent's unit never gives up restarting, backing off
  from 5 s to 2 minutes, and the kiosk opens a local "MusallahBoard is
  starting" page (starting.html, embedded and installed with the splash)
  that waits for the agent's new /_mb/alive.gif before moving to the
  board, so Chromium's "refused to connect" page no longer shows.
- enroll: plain output instead of JSON logs, requires root, refuses to
  re-enroll an enrolled board without --force (the old device would be
  orphaned in the portal), and says what happens next.
- status: what the screen is showing (asked over CDP), whether the board
  is connected to LensBridge (the WebSocket client now tracks it), which
  offline routes are on, update-check failures, and readable times.
- The normal setup prepares the ethernet service port (left off), so
  `sudo musallahboard-agent service-port on` works without a local copy of
  setup.sh; the RTC still needs `curl ... | bash -s -- --service-port --rtc`,
  which the messages now give in full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
- Prompts read from /dev/tty, so `curl ... | bash` asks instead of
  silently taking defaults (MB_ASSUME_YES=1 or no terminal still takes
  them).
- No personal default for the admin user: it must be given, and is
  checked to be a valid username; the SSH key is checked to look like
  one, and the messages say why it is needed.
- README: run setup.sh as a user with sudo (it refuses root), the enroll
  command in the form the portal gives, the service port turned on with
  the agent CLI, the RTC re-run spelled out, and USB sticks removed when
  the screen says so rather than at "Update complete".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
…ours

- Update screen: the working caption, the outcome lines and the footer at
  2.6vmin (about 28px at 1080p, up from 19px), and "Please keep
  MusallahBoard plugged in" at 1.8vmin: read from across a room.
- The enrollment splash moves from its old green to the board's navy and
  gold (like the update screen and the starting page), sized in vmin, and
  once it knows its address says what to do next: SSH in and run the
  enroll command the admin portal gives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
TestRefreshTriggersDebouncedSync gave start-up sync, the refresh
WebSocket dial and the 50 ms debounce 400 ms in total, which a busy CI
runner under -race sometimes exceeds ("1 content syncs, want 2"). It now
waits up to 10 s for the second sync, then long enough for an unfolded
third to show, and still fails if the burst is not folded into one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy
@IbraTech04
IbraTech04 merged commit 794d639 into develop Sep 25, 2026
1 check passed
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.

2 participants