feat(ci): gate release pins and propose bumps by pull request - #13
Merged
Conversation
The release number is copied out of images/owlab.yaml into every place that starts a router, and a stale copy fails nothing: an old release still builds, it just stops being the one anybody tests. Measured on 2026-09-04 -- ARG BASE_IMAGE and both release literals in ci.yml still said 25.12.4 while the config had been on 25.12.5 since it was written, and every job was green. tools/pins.sh holds the list of files this repository runs a release number out of, once, and both directions over it: `check` refuses a literal the config does not pin, `bump` moves every copy of one release. ci.yml runs `check` in the shell job. Comments, examples/, the READMEs and docs/ stay out of both, because a release number there is an illustration -- the README's sample `owlab releases` output is a pin one release behind on purpose. pins.yml is the consumer the report never had. `owlab releases --json` has carried a `stale` field since 0.2.0 and images.yml pipes the table into a log nobody opens. Daily, this reads it, rewrites the pins when stale is non-zero, validates with `pins.sh check` and `owlab context --list`, and opens a pull request. It never pushes to main: what proves a new release still builds a router is the CI run on that pull request. It then dispatches ci.yml on the pin branch. A pull request opened under GITHUB_TOKEN is authored by app/github-actions, and this repository's approval policy is first_time_contributors, so its pull_request run is created and held in action_required until a person presses a button; workflow_dispatch is GitHub's own documented exception, and check runs bind to a commit rather than to an event. The two lagging copies move to 25.12.5. openwrt/rootfs:x86_64-25.12.5 was not on Docker Hub yet -- upstream tags the download server first -- and that is not a problem: `owlab context` asks the registry and falls back to the rootfs tarball, which is the path the published 25.12.5 images were already built through.
VizzleTF
added a commit
that referenced
this pull request
Sep 4, 2026
`docker compose build` puts every router in one buildkit solve, and buildkit cancels the solve on the first target that fails. The extras step ran under `set -eu` with no guard, so one unusable staged file turned into a failed lab: measured on a three-router stand, an .ipk with an unsatisfiable dependency exited 255 and took `#16 CANCELED`, `#13 CANCELED` and `owlab: build failed` with it. Both release lines did it -- it is `set -eu`, not the package manager: the same stand with a .apk apk answers `unable to select packages` exited 27 and cancelled its neighbour. Neither `docker compose build` nor `docker buildx bake` has a --keep-going, so the tolerance has to live in the RUN. The extras step now installs the set as before and, only if that fails, installs each file on its own in the staged order, so the good ones still land. The successful path is unchanged. Tolerance without a report would be worse than the cancelled builds it replaces, so what still fails is named once per package and recorded in /etc/owlab/extras-failed. `owlab up` reads that back off each running router and prints it under the ready table, exiting non-zero; `owlab test` gains an `extra_packages` step that fails the router before any assertion runs against a box missing what it was told to have. Fixes #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
images/owlab.yamlis the source of truth for which point releases this repositoryruns, and
owlab context --listalready resolves it weekly into theimages.ymlmatrix. What nothing did was notice when a copy of a release literal fell behind
the pin — so
ci.ymlandimages/Dockerfilehave been naming25.12.4since25.12.5shipped, testing a release nobody runs, with nothing going red to say so.The gate
tools/pins.sh checkrequires everyNN.NN.Nliteral in.github/workflows/*.ymlandimages/Dockerfileto be a releaseimages/owlab.yamlpins. Comment lines are skipped:the ones there record measured facts about specific releases, and rewriting those would
be forging a measurement.
Prose is deliberately out of scope —
README*.md,docs/,examples/andaction/action.ymlname a release as illustration, and the README's sampleowlab releasesoutput shows a stale pin on purpose.On
mainthe gate reports exactly the three forgotten copies (ci.yml:155,ci.yml:207,images/Dockerfile:27) and nothing else. They are bumped to25.12.5here.
The proposal
pins.ymlruns on a cron, builds owlab, and readsowlab releases --json— theowlab.releases/v1schema it has always emitted and nobody consumed. Withstale == 0it prints one line and exits, touching nothing. With
stale > 0it derives thepinned -> newest pairs, rewrites them with
tools/pins.sh bump, re-validates withpins.sh checkandowlab context --list, and opens a pull request. It proposes; itdoes not apply — the same shape
check-updates.shhas inowfeed-packages.An already-open proposal for the same release is detected and not duplicated.
Two failure modes it avoids by construction
Every
ghcall passes-R "$GITHUB_REPOSITORY", and results are captured into avariable rather than piped into
grep: a pipeline reports the status of its lastcommand, so a failed
ghreachinggrepas empty output reads as a valid answer.The pull request is authored by
app/github-actions, and this repository'sfork-pr-contributor-approvalpolicy isfirst_time_contributors, so itspull_requestrun would sit inaction_requireduntil a person approves it. The jobtherefore dispatches
ci.ymlon the pin branch —workflow_dispatchis GitHub's owndocumented exception, and check runs bind to a commit, so the same contexts are
reported. A refusal to dispatch prints the manual command instead of failing the job.
Note on 25.12.5
The rootfs tarball exists and answers 200, but the
openwrt/rootfs:x86_64-25.12.5container tag is not on Docker Hub yet. That does not block anything:
owlab contextasks the registry and falls back to the tarball — measured locally, andconfirmed green by the
images.ymlrun foropenwrt-2512-x86-64at25.12.5. TheARGcarries that as a comment.