From d5de882b1e8016fd0e5d887c7032579e010e8e71 Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Sun, 19 Jul 2026 15:20:12 -0400 Subject: [PATCH] Fix stale documentation found by a doc-accuracy audit firmware/README.md's HTTP vs HTTPS section still described the public-CA-bundle trust approach that was tried and abandoned in favor of pinning one specific certificate -- rewritten to match what's actually there. docs/architecture.md was missing the back-photo button entirely (sequence diagram and boot-flow bullets only covered next) and still said the system talks "over plain HTTP" despite HTTPS support. docker-compose.yml.example's MANAGEMENT_TOKEN comment understated its scope (said "the web UI", omitting that every /frame/* endpoint is gated too). --- docs/architecture.md | 28 ++++++++++++++++--------- firmware/README.md | 35 +++++++++++++++++++++---------- server/docker-compose.yml.example | 10 +++++---- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index ebe7622..67452c4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,8 +1,10 @@ # Architecture -Two independent pieces talk over plain HTTP on the local network: the -ESP32-C6 firmware, and a small FastAPI server that sits between it and -Immich. +Two independent pieces talk over HTTP or HTTPS (the server itself always +speaks plain HTTP; HTTPS means a reverse proxy in front of it, see +[`firmware/README.md`](../firmware/README.md#http-vs-https)) on the local +network: the ESP32-C6 firmware, and a small FastAPI server that sits +between it and Immich. ```mermaid sequenceDiagram @@ -16,11 +18,14 @@ sequenceDiagram Note over Frame: User scans WiFi QR, then config QR -> fills in
home WiFi + "Tools Server" host:port Frame->>Frame: Save config to NVS, reboot - Note over Frame: Every wake (deep sleep timer, next-photo button,
or any other reboot) + Note over Frame: Every wake (deep sleep timer, next/back-photo button,
or any other reboot) Frame->>Frame: Connect to home WiFi alt next-photo button pressed Frame->>Server: POST /frame/advance Server->>Server: Force-advance to next queued photo, reset interval clock + else back-photo button pressed + Frame->>Server: POST /frame/back + Server->>Server: Return to previously-current photo (bounded history),
reset interval clock else normal wake Frame->>Server: GET /frame/image Server->>Server: Advance only if refresh_interval_s has elapsed
since the current photo was set -- otherwise a no-op @@ -52,12 +57,15 @@ sequenceDiagram 2. **Stored config exists**: connect to the saved WiFi network (a few retries before falling back to provisioning if it fails), then run the fetch cycle in `frame_client.c`: - - Check the next-photo button (`next_button_check()`) -- if it was - what woke the device (checked via the latched - `esp_sleep_get_gpio_wakeup_status()`, not a live pin read, since a - quick tap can release before boot gets around to polling it) or is - currently held, the fetch below hits `POST /frame/advance` instead - of `GET /frame/image`, forcing the server to skip ahead immediately. + - Check the next-photo and back-photo buttons (`next_button_check()`, + `back_button_check()`) -- if either was what woke the device + (checked via the latched `esp_sleep_get_gpio_wakeup_status()`, not + a live pin read, since a quick tap can release before boot gets + around to polling it) or is currently held, the fetch below hits + `POST /frame/advance` or `POST /frame/back` instead of + `GET /frame/image`, forcing the server to move in that direction + immediately (next takes priority if somehow both read pressed at + once). - Fetch the frame and write it into the panel's SPI buffer (`epd_write_frame()`), computing a CRC32 as it streams -- never buffering the full ~192KB frame in RAM. The panel driver refuses to diff --git a/firmware/README.md b/firmware/README.md index b6bfb1e..40771af 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -96,17 +96,30 @@ The Tools Server field accepts either: manage-menu overlay data, the QR codes' own links) uses whichever scheme you enter. -The firmware trusts the standard public CA bundle ESP-IDF ships -(`esp_crt_bundle_attach`, the same root store a browser trusts) -- so -any reverse proxy with a normal publicly-trusted certificate just -works: Let's Encrypt, a Cloudflare-proxied hostname (Cloudflare's own -edge certificate, issued by Google Trust Services or similar -- **not** -Cloudflare's Origin CA cert, which only ever sits on the Cloudflare-to- -origin leg and is never presented to a public client, ESP32 or browser -alike), or any other public CA. If your proxy uses a private/self-signed -cert instead (no public CA in the chain at all), the public bundle won't -trust it -- that's not supported today, would need switching back to -embedding that specific cert. +The firmware does **not** use ESP-IDF's general public CA bundle -- +it pins one specific certificate, embedded at build time from +[`main/certs/tools_server_ca.pem`](main/certs/tools_server_ca.pem) and +trusted directly via `cert_pem` on every request. (The public bundle +was tried first and rejected: it does an exact byte-level match against +its compiled-in table, and a real-world root that's been re-issued +under a new serial/signature but the same name and key -- as Google did +for GTS Root R4 -- doesn't match it, confirmed on hardware.) + +This means a normal publicly-trusted certificate (Let's Encrypt, a +Cloudflare-proxied hostname, etc.) does **not** automatically work -- +only whichever certificate is actually embedded in +`certs/tools_server_ca.pem` is trusted. To point the device at a +different reverse proxy, extract that proxy's actual certificate and +replace the file's contents: + +``` +openssl s_client -connect :443 -showcerts