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).
This commit is contained in:
+18
-10
@@ -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<br/>home WiFi + "Tools Server" host:port
|
||||
Frame->>Frame: Save config to NVS, reboot
|
||||
|
||||
Note over Frame: Every wake (deep sleep timer, next-photo button,<br/>or any other reboot)
|
||||
Note over Frame: Every wake (deep sleep timer, next/back-photo button,<br/>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),<br/>reset interval clock
|
||||
else normal wake
|
||||
Frame->>Server: GET /frame/image
|
||||
Server->>Server: Advance only if refresh_interval_s has elapsed<br/>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
|
||||
|
||||
+24
-11
@@ -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 <host>:443 -showcerts </dev/null
|
||||
```
|
||||
|
||||
Pick whichever certificate in the printed chain you want as the trust
|
||||
anchor (typically the root) and rebuild. A private/self-signed cert
|
||||
works exactly the same way -- there's no requirement that it chain to
|
||||
a public CA at all, since the device trusts this one file directly.
|
||||
|
||||
The device does perform normal hostname verification (it's not
|
||||
skipped), so the Tools Server field's hostname has to match what the
|
||||
|
||||
@@ -10,9 +10,11 @@ services:
|
||||
- CONFIG_PATH=/data/config.json
|
||||
- IMMICH_URL=http://your-immich-host:2283
|
||||
- IMMICH_API_KEY=your-immich-api-key-here
|
||||
# Optional: gates the web UI (/, /api/*) behind this shared secret --
|
||||
# leave unset to keep it open on a trusted LAN, same as before. Paste
|
||||
# the same value into the ESP32's captive portal setup form (Access
|
||||
# Token field) so its manage-menu QR code embeds it automatically.
|
||||
# Optional: gates the entire server -- the web UI (/, /api/*) AND
|
||||
# every device-facing /frame/* endpoint -- behind this shared secret.
|
||||
# Leave unset to keep it all open on a trusted LAN, same as before.
|
||||
# Paste the same value into the ESP32's captive portal setup form
|
||||
# (Access Token field) so it's sent on every device request and gets
|
||||
# embedded automatically in the manage-menu/share QR codes.
|
||||
- MANAGEMENT_TOKEN=changeme
|
||||
restart: unless-stopped
|
||||
|
||||
Reference in New Issue
Block a user