CI workflow (.github/workflows/ci.yml):
- Triggers on PRs (and main pushes for redundant fast feedback).
- Two parallel jobs: build the firmware (with placeholder wifi.env),
build the host-side publisher tool. No publishing or signing -- that
remains publish.yml's job.
- Per-PR concurrency: a new push to a branch cancels in-flight CI for
the previous commit on that branch.
GHA action bumps (publish.yml + ci.yml both use the new versions):
- actions/checkout v4 -> v6
- astral-sh/setup-uv v3 -> v8
- esp-rs/xtensa-toolchain v1.5 -> v1.7
- sigstore/cosign-installer v3 -> v4 (defaults to cosign 3, which
still produces the Sigstore Bundle v0.3 format we verify on-device)
- actions/cache v4 -> v5
- Swatinem/rust-cache stays v2 (already current)
Gets us off the deprecated Node.js 20 actions runtime.
Cargo.lock: now tracked. Both crates (firmware and publisher) are
binaries, and binary Cargo.lock should be committed for reproducible
builds. `cargo update` was a no-op except for a couple transitive
patch bumps in the firmware (generic-array 0.14.7 -> 0.14.9).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub Actions workflow at .github/workflows/publish.yml builds the
firmware on every push to main, pushes it to ghcr.io/imjasonh/esp32,
and cosign-signs it keylessly using the workflow's ambient OIDC token.
Required repo secrets: WIFI_SSID, WIFI_PASS (the GITHUB_TOKEN is
injected automatically).
Trust changes:
- src/trust.rs adds the GHA workflow identity to TRUSTED_IDENTITIES,
alongside the existing email entry. The SAN URI pins the exact
workflow file at refs/heads/main; a malicious commit that adds a
different workflow won't match.
- src/sig.rs's SAN extractor now also handles GeneralName::Uniform-
ResourceIdentifier (workflow URIs), not just Rfc822Name (emails).
Sign-by-digest:
- tools/publisher prints `digest: sha256:...` on stdout (tracing logs
redirected to stderr) so downstream tooling can target the immutable
manifest digest.
- Makefile captures that digest and runs `cosign sign ... $REPO@DIGEST`
instead of `:latest`, eliminating the tag-race window between push
and sign.
Bootstrap: the device's currently-running firmware doesn't know the
GHA identity yet, so the first GHA-signed publish will be rejected.
A manual `make publish` after this lands rolls out the new
TRUSTED_IDENTITIES, after which GHA-signed updates verify cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>