Skip to content

CI: reproducible arm64 and armv7 release binaries; publish without overwriting signatures - #9602

Merged
nGoline merged 7 commits into
ElementsProject:masterfrom
nGoline:ci/arm-release-binaries
Oct 6, 2026
Merged

nGoline merged 7 commits into
ElementsProject:masterfrom
nGoline:ci/arm-release-binaries

Conversation

@nGoline

@nGoline nGoline commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

The release job now builds arm64 and armv7 tarballs for Ubuntu 22.04, 24.04 and 26.04 alongside the amd64 ones, and it no longer replaces files that are already on the GitHub release.

ARM builds

  • arm64 builds natively on GitHub's ubuntu-24.04-arm runners, using tools/repro-build.arm64.sh (the same pinned package set as the amd64 script, built for arm64).
  • armv7 cannot build natively: protoc-bin-vendored, which cln-grpc's build script uses, has no armv7 binary. tools/repro-build.armv7.sh cross-compiles instead, in the amd64 cl-repro-<dist> image, with Ubuntu's arm-linux-gnueabihf toolchain and armhf libraries from ports.ubuntu.com, all pinned by sha256 like the other scripts. The build runs a few armv7 programs (configure tests, tools/headerversions), so the runner registers qemu-arm first.
  • Each architecture has its own manifest and signature: SHA256SUMS-<version>-arm64 / .asc and SHA256SUMS-<version>-armv7 / .asc, next to the existing amd64 SHA256SUMS-<version>. Output goes to release-arm64/ and release-armv7/, so the manifests never mix; tools/sign-release-arm.sh <version> arm64|armv7 creates and signs them.
  • doc/getting-started/advanced-setup/repro.md explains how to reproduce both.

Publishing

The draft-release step used softprops/action-gh-release, which replaces any asset with the same name. Re-running the job after the release captains had added their signatures to SHA256SUMS-<version>.asc replaced that file with one carrying only the CI signature.

tools/publish-release-assets.sh replaces it:

  • files the release does not have yet are uploaded;
  • files it already has must be byte-identical, otherwise the job stops before touching anything;
  • an existing .asc gets the CI signature appended (armor normalised so gpg reads every block), and one that already carries it is left alone, so re-runs are harmless.

Also fixed

cln-rpc/Makefile named the cln-rpc-getinfo example under target/$(RUST_PROFILE) while DEFAULT_TARGETS lists it under $(RUST_TARGET_DIR), so any build with TARGET set (any cross build) stopped with "No rule to make target". It now uses $(RUST_TARGET_DIR).

Testing

  • arm64 noble tarball built natively (aarch64 host): aarch64 binaries, lightningd --version reports the forced version.
  • armv7 noble tarball cross-built in the amd64 builder image: ARM EABI5 binaries (C and Rust), lightningd --version runs in arm32v7/ubuntu:noble.
  • armv7 package pins generated in the real cl-repro-{jammy,noble,resolute} images.
  • tools/publish-release-assets.sh exercised against a stub gh with throwaway keys: fresh release, a release with two co-signatures (ends with three), a re-run (no-op), a mismatched tarball (aborts, nothing touched), and a co-signed .asc with a malformed armor block (repaired, all signatures verify).
  • make check-source passes.

Changelog-None

Written by Claude (AI assistant) on behalf of Níckolas.

🤖 Generated with Claude Code

@nGoline
nGoline requested a review from cdecker as a code owner October 6, 2026 13:27
@nGoline
nGoline force-pushed the ci/arm-release-binaries branch from db520a5 to a0b9d13 Compare October 6, 2026 15:07
nGoline and others added 7 commits October 6, 2026 12:26
RELEASEDIR is hardcoded to release/, and the zip block runs on the host
before any builder container starts, so an ARM build would write into the
amd64 release directory whichever builder image it used.

That block also removes release/clightning-$VERSION.zip before rebuilding
it from the current HEAD.  For an ARM build there is nothing to rebuild it
from, so the file is simply lost.

Send -arm64 targets to release-arm64/ and -armv7 targets to
release-armv7/, and skip the zip for them:
the zip is the source archive and carries no architecture, so one copy
serves every build.  Targets without a suffix keep their current behaviour.

Suffix arm64 builder images in cl-repro.sh so both architectures can be
present at once, and so bin-Ubuntu-<dist>-arm64 resolves to the right one.
amd64 images keep their existing names.

Changelog-None

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every pinned .deb in repro-build.sh is _amd64, so sha256sum -c fails
outright on an arm64 builder.

Add tools/repro-build.arm64.sh: the same script with the same packages at
the same upstream versions, built for arm64. Keeping it separate leaves the
amd64 path isolated, so work on one architecture cannot disturb the other.

Pass VERSION to the install step as well as the build step. The Makefile
falls back to git describe, which inside the builder reports the commit
that was checked out rather than the release tag, so without this
--force-version names the tarball but leaves the binaries stamped with the
branch version.

Pick the script from the image's own architecture, so one Dockerfile still
serves both. bin-Ubuntu-<dist>-arm64 already resolves to
cl-repro-<dist>-arm64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cln-rpc/Makefile names the example target/$(RUST_PROFILE)/..., while
plugins/Makefile lists the same file under $(RUST_TARGET_DIR) in
DEFAULT_TARGETS. The two only agree when TARGET is unset: with a cross
TARGET, cargo writes to target/<triple>/<profile>/, and make stops with
"No rule to make target 'target/<triple>/release/examples/cln-rpc-getinfo'".

Use $(RUST_TARGET_DIR) like the other Rust targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
armv7 cannot build natively like arm64: cln-grpc's build script takes
protoc from protoc-bin-vendored, which ships no armv7 binary, so the build
panics there.

Add tools/repro-build.armv7.sh, which cross-compiles instead. It runs in
the amd64 cl-repro-<dist> image, adds armhf from ports.ubuntu.com (same
release pocket, updates and security still disabled), and pins the
arm-linux-gnueabihf toolchain and armhf libraries the same way the other
scripts pin theirs. The Rust plugins build for
armv7-unknown-linux-gnueabihf.

The build runs a few armv7 programs (configure tests,
tools/headerversions), so the host needs qemu-arm registered with
binfmt_misc.

bin-Ubuntu-<dist>-armv7 runs the amd64 image with REPRO_ARCH=armv7, and
the tarball lands in release-armv7/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-release.sh's sign target cannot be used for the ARM tarballs: it
hardcodes `cd release/` rather than honouring RELEASEDIR, and globs the
zip, so running it for an ARM build would rewrite the amd64 manifest.

Checksum one architecture's tarballs, from release-arm64/ or
release-armv7/, into SHA256SUMS-<version>-<arch> and sign it. arm64 and
armv7 keep separate manifests: arm64 builds natively and armv7 is
cross-compiled, so they are reproduced and co-signed separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add arm64 and armv7 targets for every Ubuntu release to the release build.
arm64 builds natively on GitHub's ubuntu-24.04-arm runners; armv7
cross-compiles on the amd64 runners, with qemu registered for the few
armv7 programs the build runs.

The arm64 and armv7 tarballs go into their own artifacts, are checksummed
and signed per architecture with tools/sign-release-arm.sh, and are
attached to the draft release with SHA256SUMS-<version>-arm64 and
SHA256SUMS-<version>-armv7 and their .asc files, next to the amd64
manifest.

Document how to reproduce the ARM builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release job uploaded release/* with action-gh-release, which replaces
any asset of the same name. Re-running it after the release captains had
added their signatures to SHA256SUMS-<version>.asc replaced that file with
one carrying only the CI signature, so users verifying the release found
the maintainers' signatures gone.

Upload with tools/publish-release-assets.sh instead:

- files the release does not have yet are uploaded;
- files it already has must be identical, otherwise the job stops before
  touching anything (the builds disagree, and nothing should be signed);
- an .asc it already has gets our signature appended, after normalising
  the armor so gpg reads every block; one that already carries our
  signature is left alone, so re-runs are harmless.

The draft release is created with gh when it does not exist yet, with the
same title and pre-release flag as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nGoline
nGoline force-pushed the ci/arm-release-binaries branch from a0b9d13 to 9a663cf Compare October 6, 2026 15:29
@nGoline
nGoline merged commit f918c23 into ElementsProject:master Oct 6, 2026
165 of 177 checks passed
@nGoline
nGoline deleted the ci/arm-release-binaries branch October 6, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant