1
0
Fork 0
mirror of https://github.com/imjasonh/esp32 synced 2026-07-06 23:52:24 +00:00
No description
Find a file
Jason Hall 1264ca7da1 cloud_log: real sender — JWT, RSA-SHA256, OAuth2, POST to logging.googleapis.com
Replaces the stub eprintln sender with the real implementation:

- mint_access_token: builds + signs a service-account JWT (RS256 over
  base64url(header).base64url(claims)), POSTs to oauth2.googleapis.com
  with the standard grant_type=...jwt-bearer form body, parses the
  access_token + expires_in.
- CachedToken: held in the sender thread's local state. Refreshes
  300 seconds before expiry (Google issues 1h tokens; we re-mint at
  T+55min).
- post_batch: serializes the WriteEntriesRequest with logName,
  resource (generic_node + project + MAC node_id), and entries
  (severity + jsonPayload + optional timestamp), POSTs to
  logging.googleapis.com/v2/entries:write with the bearer.
- Backoff on failure: sleep doubles on each consecutive failure
  (capped at 5 min). Batches dropped on POST failure rather than
  re-enqueued; loss is surfaced via dropped_before on the next entry.

Crates added: rsa 0.9 (with sha2 feature for AssociatedOid; PKCS#1
v1.5 needs the DigestInfo prefix), time 0.3 (RFC3339 formatting).

main.rs: SNTP startup is now gated on gcp.is_some(). The JWT auth
needs a real wall-clock for / (Google rejects ~5min skew),
so devices with cloud logging pay a one-time ~few-second sync at
boot. Devices without [gcp] skip it. Log entry timestamps themselves
are still optional — when omitted, GCP server-side assigns them.

Cloud Logging severity mapping: tracing TRACE/DEBUG -> DEBUG,
INFO -> INFO, WARN -> WARNING, ERROR -> ERROR (matches GCP's
LogSeverity enum).

device_mac() reads via esp_efuse_mac_get_default and uses the lower
6 bytes as the node_id resource label, so multi-device logs can be
filtered by MAC.

Firmware size: 1.60 MB -> 1.71 MB (+rsa, +time, +sender code).
Still under the 1.94 MB slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:13:03 -04:00
.cargo initial commit 2026-05-02 11:43:41 -04:00
.github Fix Dependabot on firmware crate; fold provisioning-plan.md into ota.md 2026-05-02 16:34:47 -04:00
src cloud_log: real sender — JWT, RSA-SHA256, OAuth2, POST to logging.googleapis.com 2026-05-02 17:13:03 -04:00
tools cloud_log: NVS-loaded config, tracing layer, queue, stub sender 2026-05-02 17:06:00 -04:00
trust Add cosign signature verification (OTA phase 4a) 2026-05-02 13:50:03 -04:00
.gitignore Provision Wi-Fi creds + trust roots via NVS instead of compile-time embed 2026-05-02 16:14:26 -04:00
build.rs Provision Wi-Fi creds + trust roots via NVS instead of compile-time embed 2026-05-02 16:14:26 -04:00
Cargo.lock cloud_log: real sender — JWT, RSA-SHA256, OAuth2, POST to logging.googleapis.com 2026-05-02 17:13:03 -04:00
Cargo.toml cloud_log: real sender — JWT, RSA-SHA256, OAuth2, POST to logging.googleapis.com 2026-05-02 17:13:03 -04:00
CLAUDE.md Fix Dependabot on firmware crate; fold provisioning-plan.md into ota.md 2026-05-02 16:34:47 -04:00
eink-plan.md initial commit 2026-05-02 11:43:41 -04:00
LICENSE Initial commit 2026-05-02 11:43:06 -04:00
logs-plan.md cloud_log: NVS-loaded config, tracing layer, queue, stub sender 2026-05-02 17:06:00 -04:00
Makefile Fix Dependabot on firmware crate; fold provisioning-plan.md into ota.md 2026-05-02 16:34:47 -04:00
notes.txt Switch to OTA partition layout and document Wi-Fi + HTTPS demo 2026-05-02 12:14:21 -04:00
ota.md cloud_log: NVS-loaded config, tracing layer, queue, stub sender 2026-05-02 17:06:00 -04:00
partitions.csv Aggressive release-profile flags + grow OTA slots to 1.94 MB 2026-05-02 15:39:33 -04:00
provisioning.toml.example cloud_log: NVS-loaded config, tracing layer, queue, stub sender 2026-05-02 17:06:00 -04:00
README.md Fix Dependabot on firmware crate; fold provisioning-plan.md into ota.md 2026-05-02 16:34:47 -04:00
sdkconfig.defaults.in Add device-side OTA loop (OTA phase 2) 2026-05-02 12:53:19 -04:00

ESP32 Rust

CI Publish OTA

Std Rust on an Inland ESP-WROOM-32 dev board with end-to-end OTA over GHCR + cosign keyless signing. E-ink display work coming next (see eink-plan.md).

What it does today

  • Connects to Wi-Fi, fetches public IP and weather over HTTPS.
  • Polls ghcr.io/imjasonh/esp32:latest every ~60s for new firmware.
  • For each new digest, fetches the cosign Sigstore Bundle, verifies the signature, the cert chain to the Sigstore root, and that the signer's identity matches the allowlist provisioned into the device's NVS.
  • On verify-pass, streams the layer to the inactive OTA partition, reboots, and only marks the new image valid after Wi-Fi + registry bringup checks pass. Any post-OTA failure auto-rolls back via the bootloader.

Push to main → CI builds → publish workflow pushes a signed image to GHCR → device picks it up on its next poll. See ota.md for the full design.

Hardware

  • Board: Inland ESP-WROOM-32 (Micro Center SKU 027466). Connects as /dev/cu.usbserial-0001 on macOS via the onboard CP210x USB-UART.
  • No user LED on this variant — only a power LED. Watch logs via make monitor.

Prerequisites (one-time, macOS)

cargo install espup espflash ldproxy
brew install cmake ninja dfu-util cosign
espup install --targets esp32
curl -LsSf https://astral.sh/uv/install.sh | sh   # if you don't have uv

First flash (USB)

make provisioning.toml             # creates from template
$EDITOR provisioning.toml          # fill in wifi creds + trust identities
make bootstrap                     # build, flash everything, write NVS
make monitor                       # watch it boot and connect

The first build clones ESP-IDF v5.2.2 into .embuild/ (510 min). Subsequent builds are fast.

The OTA-distributed firmware contains no secrets — Wi-Fi creds and trust roots live in NVS, written via USB by make provision. See ota.md for the full design.

Day-to-day

make build      Compile firmware
make flash      Build + flash app (use flash-all after partitions change)
make flash-all  Erase + write bootloader, partition table, app
make provision  Write NVS partition from provisioning.toml over USB
make bootstrap  flash-all + provision (new device setup)
make monitor    Open serial monitor; Ctrl+C to exit
make run        Build + flash + monitor
make publish    Build, push OCI artifact to ghcr.io/imjasonh/esp32, cosign sign
make clean      cargo clean

make publish requires gh.env (see ota.md for PAT setup) and a real cosign OIDC flow the first time per ~10min window — a browser pops to authenticate. CI does this automatically via the GitHub Actions workflow's ambient OIDC token.