diff --git a/site/content/devlog/roro9stack-console/dbg.png b/site/content/devlog/roro9stack-console/dbg.png new file mode 100644 index 0000000..426022d Binary files /dev/null and b/site/content/devlog/roro9stack-console/dbg.png differ diff --git a/site/content/devlog/roro9stack-console/index.md b/site/content/devlog/roro9stack-console/index.md new file mode 100644 index 0000000..6b5ceb4 --- /dev/null +++ b/site/content/devlog/roro9stack-console/index.md @@ -0,0 +1,266 @@ ++++ +title = '''It was off''' +description = '''roro9stack gets a website, and writing down how its Debug Console works shows that only one person could ever use it. So the Debug Build is retired: one firmware, with the console in it, off until its owner switches it on. Then an evening of chasing a console that wouldn't come back and a memory leak, neither of which existed.''' +date = 2026-10-07T00:30:00+02:00 + +[extra] +topics = '''ESP32-S3 · Debugging · Security''' +read_label = '''Read what was off →''' +uid = '''debug: on, token set, client connected''' +dek = "A day of writing for [roro9stack](/devlog/roro9stack/), my firmware for the M5Stack Cardputer: a website, a user guide, and developer docs about the thing I like best in it, a console over Wi-Fi. Documenting it properly made one fact hard to miss: nobody else could have it. Fixing that deleted more than it added, and then cost me an evening looking for two bugs that turned out to be a switch in the Off position and TCP minding its own business." +byline = '''designed by interrogation, round twelve: fourteen questions thrown away, eight kept''' + +[extra.sign] +label = "Tokens leaked by the feature built to protect them" +note = "In a screenshot, taken over the console, of the one page that shows the token." +count = "1" +tone = "red" + +[[extra.cast]] +name = "The Debug Build" +role = "retired, v0.3.0 to v0.11.0" +text = "The same firmware plus a console over Wi-Fi, with its builder's token compiled in. Which is why it could never be published, and why the firmware I tested was never the one I released." + +[[extra.cast]] +name = "Port 3232" +role = "the Update Service, in every build since v0.3.0" +text = "Listens on the network in release builds, guarded by a signature. The counter-example to \"in a release, nothing listens\" that had been sitting there all along." + +[[extra.cast]] +name = "The token" +role = "100 bits, 20 characters" +text = "Made by the device, shown on one page of Settings and nowhere else. Read off a 240-pixel screen by a human, which is where the trouble started." + +[[extra.cast]] +name = "The dialog" +role = "\"Switch it on?\"" +text = "Opens with Cancel selected, as a question about a remote control should. Enter, Enter: still off. Works exactly as designed." + +[[extra.cast]] +name = "TIME_WAIT" +role = "two minutes, per closed connection" +text = "What TCP does with a connection it has closed, in case a late packet turns up. About 270 bytes each. Looks exactly like a leak if you only watch for one minute." ++++ + +## TL;DR + +- **roro9stack has a website:** [roro9stack.net](/), with an Install page that flashes a Cardputer from the browser, a [user guide](/guide/), [how-tos](/howto/), an [FAQ](/faq/), [developer docs](/dev/) generated from the repository, and this devlog, which moved here. +- **The Debug Build is gone.** There is one firmware (**v0.12.0**), and the Debug Console is in it: **off** until you switch it on in Settings, with a token the device makes itself. +- **The token never crosses the network.** The device sends a challenge, the client answers with an HMAC. Five wrong answers close the console for a minute. +- **It costs** 30 KB of flash and 88 bytes of RAM over the old release build. Off, nothing listens and nothing is allocated. +- Then I spent an evening on **a console that wouldn't come back on** (it was off) and **a memory leak of 230 bytes per reopening** (it was TCP). Both produced a real fix on the way, and one design change I had to undo. +- 468 host tests, 12 more than last time. The decision is [ADR 0010](/dev/decisions/0010-debug-console-in-every-build/). + +## The cast + +{{ cast() }} + +## A site, in four phases + +The [last post](/devlog/roro9stack-f1-r1/) ended with a device that installs its own releases. That makes it something another person could use, and another person needs somewhere to start that isn't a Gitea README. So the first half of the day was a website: a home page, an Install page, then a guide to each App, how-tos, an FAQ, and developer docs. + +Three things from that half are worth keeping. + +**Flashing from the browser, without a copy of the firmware.** The Install page uses ESP Web Tools and Web Serial. The obvious way is to copy the firmware image next to the page; then every release needs a rebuild of the site. Instead the page asks Gitea's API for the latest release, in the browser, and hands the flasher a manifest it builds on the spot. That only needed one header: Caddy, in front of Gitea, adds `Access-Control-Allow-Origin: https://roro9stack.net` to the release downloads and to the releases API, which are public anyway. A new release shows up on the page the moment it exists. + +**"Generated from the repository" met Zola.** The developer docs were to be built from `docs/`, the README and the firmware's own `help` text, not copied by hand. Zola refuses to read a file outside its own folder, and it resolves symlinks before deciding, so that door is closed too. So a small script writes those pages, they are committed, and CI fails when one is out of date. The command reference is the part I like: it is parsed from the `kHelp` string in `main.cpp`, so the site can't describe a command the firmware doesn't have. + +**The posts you're reading had `