Files
firmware/docs
Oleksandr ShyshatskyiandClaude Opus 5 734a04db45 Make the apk feed buildable by CI, and signable by a release
PR #10 was green on this machine and red on both CI jobs. Three
separate faults, none of which a local signed build could show.

apk-tools is cloned from gitlab.alpinelinux.org, which answers a
GitHub runner with HTTP 418 or with nothing until the connection
times out. It clones from the GitHub mirror now. Which host serves
it cannot change what is fetched: the pin is a commit sha, and git
names a commit by the hash of its content, so a mirror answering
with this sha is serving this tree or is not answering. get_git also
sets the remote url on every run, so a checkout made before this
commit does not keep fetching from the host that refuses.

anvil-core's cache key hashed the feed's public key as

    sha256sum "${APK_SIGN_KEY:-/dev/null}.pub" 2>/dev/null | cut ...

With no key that file is absent, sha256sum is non-zero, pipefail
makes the pipeline non-zero, and build-packages.sh -- which sources
every pkg.conf under `set -euo pipefail` -- died before emitting a
single package. The 2>/dev/null ate the reason, so `make packages`
exited 1 having said only that it had sealed a cache entry. Every
signed build passed. Every CI build is unsigned. It is now
pkg_signkey_hash, which answers `unsigned` and succeeds.

The build container mounts the checkout and nothing else, so a key
at the documented ~/.anvil/anvil.rsa was visible in the developer's
shell and invisible to apk. The Makefile reads APK_SIGN_KEY out of
$(CONFIG_ENV) and bind-mounts its directory read-only. This survived
review because the key used while writing the migration sat under
work/, inside the one directory already mounted.

Gates for all three, each verified against its own bug by reverting
the fix under it. The recipe-reads-unsigned test runs pkg_conf under
the same flags build-packages.sh uses -- the existing _conf helper
does not, and would have stayed green throughout. Its companion
asserts the recipe SET does not change with the key, because
pkg_recipes evaluates PKG_WHEN in a subshell and a recipe that fails
to source is dropped rather than reported: broken only when
unsigned, a package would leave the feed with nothing turning red.
Both are scoped to what pkg_recipes lists, so busybox -- gated off
whenever there is no stock firmware to take one from -- is not
asked a question it cannot answer.

release.yml takes the private key PEM from secrets.APK_SIGN_KEY,
writes it to $RUNNER_TEMP outside the checkout where no later step
can archive it into a release, and derives the public half rather
than storing a second secret that could drift out of step with the
first. Unset, the release builds unsigned with a warning: a fork has
no such secret, and firmware nobody else can build is worse than a
feed untrusted on a stick the printer was handed by hand.

Verified from a cold work/host and work/packages, three ways: no key
(the CI case), a key inside the tree, and a key outside it at the
documented location. Each gives 47 packages plus the index, a
payload, 286 static and 135 replica tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013rbKEyLXrzSE6wm3iNApVp
2026-08-31 15:15:29 +03:00
..