Files
roro9stack/docs/milestones/R1.md
T
twislaandClaude Opus 5.5 86ddb87f54
CI / build (push) Successful in 1m6s
CI: a push runs the tests, a pull request also builds the firmware
Rebuilding both firmwares on every push was more than anyone looked at.
A push now runs the host tests with their coverage (under two minutes);
a pull request adds the release firmware and the Debug Build, and is how
changes reach main; a tag still does everything before it releases.
Pull requests from forks don't run. scripts/ci.sh takes 'tests' or
'builds' for one half; coverage.sh now fails when a test fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 14:19:56 +02:00

4.4 KiB

R1 — Releases

Status: in progress. CI and signed releases on Gitea (issue #5) are in place since 2026-10-06: every tag from v0.1.0 to v0.10.0 has its release. Next: updates from Gitea (#6), which waited for this, and the Issues App (#4).

Goal: a tag is a release, built the same way every time and published where a device can find it.

CI and releases (issue #5)

Until now the tests, the builds, the signing and the flashing all happened on one machine, through scripts/ci.sh and scripts/flash.sh. Nothing was published.

Decisions (design round 2026-10-06)

# Decision
Q151 A push, to any branch: the host tests (with their coverage). A pull request: the same and both builds; changes reach main through pull requests. A tag v*: all of it, then a release. (First: everything on every push, which rebuilt the firmware far more often than anyone looked at it.)
Q152 CI signs. The signing key is the repository secret OTA_SIGNING_KEY; a tag push makes a complete, signed release with no manual step (ADR 0008).
Q153 The Debug Build is built in CI with a token of the runner's own, to prove it compiles, and isn't published: it would hand everyone its Debug Console token.
Q154 Pull requests from forks don't start a run.
Q155 A release carries roro9stack-<version>.ota (signed), -factory.bin for USB, .elf.gz to decode crashes, and SHA256SUMS.
Q156 Its text is the tag's message, what the files are, and the commits since the tag before.
Q157 No cache service to begin with: measure first.
Q158 Reproducible builds aren't needed for signing any more (Q152); not pursued here.
Q159 The tags from before CI get their releases too, v0.1.0 to v0.10.0, built from each tag's own sources by running the workflow by hand.
Q160 Actions is switched on for the repository.

As built

  • One workflow, .gitea/workflows/ci.yml, one job, on the runner runner0 (label ubuntu). The job asks for a python:3.12-slim container, installs git, a compiler, openssl and PlatformIO, and runs the same scripts as a developer's machine. No Docker inside the job.
  • The cache is a Docker volume, roro9stack-pio, mounted at /pio; the runner's config.yaml allows it under container.valid_volumes. A first run downloads about 1 GB and rebuilds the framework (17 minutes); with the volume filled, tests and both builds take about 6.
  • No JavaScript actions, so the image needs no Node and nothing is fetched from GitHub: the checkout is four git commands.
  • scripts/_docker.sh runs the command in place when RORO_NO_DOCKER is set (a CI job is already in a build container), and in the project's image otherwise. The Debug Build's token is made on the spot in CI and goes with the container.
  • scripts/release_build.sh <checkout> <out> builds a tag's own sources with today's tools, signs, verifies against the public key in those sources, and writes the files and the release's text. scripts/release_publish.py creates the Gitea release or completes it; run twice, it replaces what's there. Both run the same on a developer's machine.
  • scripts/ota_verify.py checks an Update File as a device does, on a PC.
  • The job's own token (secrets.GITEA_TOKEN) is enough to create a release and upload its files.
  • Old tags. v0.1.0 to v0.3.0 are from before the framework was rebuilt with our settings (ADR 0006) and can't link against a rebuilt one left in the cache: the release build puts the stock framework libraries back for them. v0.1.0 to v0.2.1 have no public key in their sources (Firmware Updates came with v0.3.0); their files are checked against today's.

How it went

  • The runner's label took three tries. Registered as ubuntu://docker:ubuntu:resolute and then as ubuntu::docker://..., Gitea took the whole string for the label's name; with the first, jobs ran on the runner's host itself. The first version of the workflow was written for that (plain shell, docker run for the build) and published v0.10.0 that way. ubuntu:docker://docker.gitea.com/runner-images:ubuntu-latest is the form that works.
  • Gitea 1.27's API can't cancel a run that isn't finished, only delete a finished one; switching Actions off and on for the repository doesn't either. Runs queued for a label that no longer exists stay queued until cancelled in the web UI.
  • CI's image isn't byte-identical to a local build of the same tag (same size, different bytes). Not pursued (Q158).