From 241d392b46aa2e6c14ef016b631111ce7e7ba278 Mon Sep 17 00:00:00 2001 From: ConYel Date: Wed, 1 Jul 2026 10:50:50 +0200 Subject: [PATCH 1/2] ci: switch to StageX reproducible builds without QEMU --- .github/Dockerfile | 65 ++++++++++++++++++++------ .github/workflows/ci.yml | 99 ++++++++++++++++++++++++++-------------- 2 files changed, 117 insertions(+), 47 deletions(-) diff --git a/.github/Dockerfile b/.github/Dockerfile index 872f7e00..956afce3 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -1,16 +1,53 @@ -# build stage -FROM golang:alpine AS build -RUN apk update && apk add git -ADD . /src -WORKDIR /src +ARG GOARCH=amd64 +ARG GOARM= +ARG GOMIPS=hardfloat +ARG GO386= + +# All build stages explicitly use BUILDPLATFORM so RUN commands execute +# on the host architecture (amd64) even when cross-compiling for other targets. +# This eliminates the need for QEMU entirely — Go's native GOARCH cross-compilation +# handles all target architectures from a single amd64 build host. + +FROM --platform=$BUILDPLATFORM docker.io/stagex/pallet-go@sha256:4b7f9fe27d84dd9109fe89c4dab1cefdd66bf2f670817ef95ffd335b84fdf2cb AS deps +ARG CHISEL_VERSION +WORKDIR /app +COPY . . +RUN GO111MODULE=on go mod vendor +RUN test -d vendor && echo "vendor/ created" + +FROM --platform=$BUILDPLATFORM docker.io/stagex/pallet-go@sha256:4b7f9fe27d84dd9109fe89c4dab1cefdd66bf2f670817ef95ffd335b84fdf2cb AS check +ARG CHISEL_VERSION +WORKDIR /app ENV CGO_ENABLED=0 -RUN go build \ - -ldflags "-X github.com/jpillora/chisel/share.BuildVersion=$(git describe --abbrev=0 --tags)" \ - -o /tmp/bin -# run stage -FROM scratch -LABEL maintainer="dev@jpillora.com" -COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=deps /app/ . +RUN go vet -mod=vendor ./... + +FROM --platform=$BUILDPLATFORM docker.io/stagex/pallet-go@sha256:4b7f9fe27d84dd9109fe89c4dab1cefdd66bf2f670817ef95ffd335b84fdf2cb AS test +ARG CHISEL_VERSION WORKDIR /app -COPY --from=build /tmp/bin /app/bin -ENTRYPOINT ["/app/bin"] \ No newline at end of file +ENV CGO_ENABLED=0 +COPY --from=check /app/ . +RUN go test -mod=vendor -v ./... + +FROM --platform=$BUILDPLATFORM docker.io/stagex/pallet-go@sha256:4b7f9fe27d84dd9109fe89c4dab1cefdd66bf2f670817ef95ffd335b84fdf2cb AS build +ARG CHISEL_VERSION +ARG GOARCH=amd64 +ARG GOARM= +ARG GOMIPS=hardfloat +ARG GO386= +WORKDIR /app +ENV CGO_ENABLED=0 +ENV GOARCH=${GOARCH} +ENV GOARM=${GOARM} +ENV GOMIPS=${GOMIPS} +ENV GO386=${GO386} +COPY --from=test /app/ . +RUN --network=none \ + go build -mod=vendor -trimpath \ + -ldflags="-s -w -X github.com/jpillora/chisel/share.BuildVersion=${CHISEL_VERSION#v}" \ + -o /chisel . + +FROM scratch +ARG CHISEL_VERSION +COPY --from=build /chisel /chisel +ENTRYPOINT ["/chisel"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0036cc89..bf9fcba1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,6 @@ on: push: {} permissions: write-all jobs: - # ================ - # BUILD AND TEST JOB - # ================ test: name: Build & Test strategy: @@ -28,9 +25,6 @@ jobs: run: go build -v -o /dev/null . - name: Test run: go test -v ./... - # ================ - # RELEASE BINARIES (on push "v*" tag) - # ================ release_binaries: name: Release Binaries needs: test @@ -50,9 +44,6 @@ jobs: GOTOOLCHAIN: auto with: args: release --config .github/goreleaser.yml - # ================ - # RELEASE DOCKER IMAGES (on push "v*" tag) - # ================ release_docker: name: Release Docker Images needs: test @@ -61,32 +52,74 @@ jobs: steps: - name: Check out code uses: actions/checkout@v5 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - name: Login to DockerHub uses: docker/login-action@v3 with: username: jpillora password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: jpillora/chisel - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - - name: Build and push - uses: docker/build-push-action@v6 - with: - context: . - file: .github/Dockerfile - platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/386,linux/arm/v7,linux/arm/v6 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Build and push all architectures + env: + CHISEL_VER: ${{ github.ref_name }} + run: | + set -e + IMAGE=jpillora/chisel + VERSION="${CHISEL_VER#v}" + + # Architecture matrix: per-platform tag, GOARCH, GOARM + # GOARM is empty for non-ARM architectures (Go uses its default) + ARCHES=( + "amd64 amd64" + "arm64 arm64" + "386 386" + "armv5 arm 5" + "armv6 arm 6" + "armv7 arm 7" + "ppc64le ppc64le" + "ppc64 ppc64" + "mips mips" + "mipsle mipsle" + "mips64 mips64" + "mips64le mips64le" + "s390x s390x" + ) + + ALL_TAGS="" + for entry in "${ARCHES[@]}"; do + read -r tag goarch goarm <<< "$entry" + build_args="--build-arg GOARCH=$goarch --build-arg CHISEL_VERSION=$VERSION" + [ -n "$goarm" ] && build_args="$build_args --build-arg GOARM=$goarm" + echo "=== Building $tag (GOARCH=$goarch GOARM=$goarm) ===" + docker build $build_args --tag $IMAGE:$tag --file .github/Dockerfile . + docker push $IMAGE:$tag + ALL_TAGS="$ALL_TAGS $IMAGE:$tag" + done + + # Create multi-arch manifest for the version tag + echo "=== Creating manifest $IMAGE:$VERSION ===" + docker manifest create $IMAGE:$VERSION $ALL_TAGS + + # Annotate each entry with correct platform metadata + # Without this, all images would be marked linux/amd64 because + # the build ran on an amd64 host regardless of GOARCH. + docker manifest annotate $IMAGE:$VERSION $IMAGE:amd64 --arch amd64 + docker manifest annotate $IMAGE:$VERSION $IMAGE:arm64 --arch arm64 + docker manifest annotate $IMAGE:$VERSION $IMAGE:386 --arch 386 + docker manifest annotate $IMAGE:$VERSION $IMAGE:armv5 --arch arm --variant v5 + docker manifest annotate $IMAGE:$VERSION $IMAGE:armv6 --arch arm --variant v6 + docker manifest annotate $IMAGE:$VERSION $IMAGE:armv7 --arch arm --variant v7 + docker manifest annotate $IMAGE:$VERSION $IMAGE:ppc64le --arch ppc64le + docker manifest annotate $IMAGE:$VERSION $IMAGE:ppc64 --arch ppc64 + docker manifest annotate $IMAGE:$VERSION $IMAGE:mips --arch mips + docker manifest annotate $IMAGE:$VERSION $IMAGE:mipsle --arch mipsle + docker manifest annotate $IMAGE:$VERSION $IMAGE:mips64 --arch mips64 + docker manifest annotate $IMAGE:$VERSION $IMAGE:mips64le --arch mips64le + docker manifest annotate $IMAGE:$VERSION $IMAGE:s390x --arch s390x + + docker manifest push $IMAGE:$VERSION + + # Create additional semver tags (major.minor and major) + for tv in "${VERSION%.*}" "${VERSION%%.*}"; do + echo "=== Creating manifest $IMAGE:$tv ===" + docker manifest create $IMAGE:$tv $ALL_TAGS + docker manifest push $IMAGE:$tv + done From e6b135a5779c54ab5bd023e07a8e5b9f3c0db0ad Mon Sep 17 00:00:00 2001 From: ConYel Date: Wed, 1 Jul 2026 10:59:08 +0200 Subject: [PATCH 2/2] ci: switch to StageX reproducible builds without QEMU -add test --- .github/verify-binary.sh | 59 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 1 + 2 files changed, 60 insertions(+) create mode 100755 .github/verify-binary.sh diff --git a/.github/verify-binary.sh b/.github/verify-binary.sh new file mode 100755 index 00000000..42c74749 --- /dev/null +++ b/.github/verify-binary.sh @@ -0,0 +1,59 @@ +#!/bin/sh +set -eu + +# verify-binary.sh — Verify chisel binary architecture and version +# Usage: verify-binary.sh [] [] +# +# Checks: +# 1. Binary executes (docker run — catches exec format errors) +# 2. ELF Machine field matches expected GOARCH +# 3. Version string matches (if expected-version provided) + +GOARCH="${1:?usage: verify-binary.sh GOARCH [image:tag] [expected-version]}" +IMAGE="${2:-chisel:test}" +EXPECTED_VER="${3:-}" + +case "$GOARCH" in + amd64) EXPECTED_MACHINE="Advanced Micro Devices X86-64" ;; + arm64) EXPECTED_MACHINE="AArch64" ;; + 386) EXPECTED_MACHINE="Intel 80386" ;; + arm|armv5|armv6|armv7) EXPECTED_MACHINE="ARM" ;; + ppc64le|ppc64) EXPECTED_MACHINE="PowerPC64" ;; + mips|mipsle|mips64|mips64le) EXPECTED_MACHINE="MIPS" ;; + s390x) EXPECTED_MACHINE="IBM S/390" ;; + *) echo "error: unknown GOARCH: $GOARCH" >&2; exit 1 ;; +esac + +echo "=== Verify $IMAGE (GOARCH=$GOARCH → $EXPECTED_MACHINE) ===" + +# 1. Binary must execute +echo "--- chisel version ---" +docker run --rm "$IMAGE" chisel version + +# 2. Extract and check ELF Machine field +echo "--- ELF architecture ---" +CID="$(docker create "$IMAGE")" +docker cp "$CID:/chisel" /tmp/chisel-verify-binary 2>/dev/null +docker rm "$CID" >/dev/null + +MACHINE="$(readelf -h /tmp/chisel-verify-binary 2>/dev/null | sed -n 's/^[[:space:]]*Machine:[[:space:]]*//p')" +rm -f /tmp/chisel-verify-binary + +if [ "$MACHINE" != "$EXPECTED_MACHINE" ]; then + echo "FAIL: Machine mismatch (expected '$EXPECTED_MACHINE', got '$MACHINE')" >&2 + exit 1 +fi +echo " Machine: $MACHINE ✓" + +# 3. Optional version check +if [ -n "$EXPECTED_VER" ]; then + echo "--- version string ---" + VER="$(docker run --rm "$IMAGE" chisel version 2>/dev/null | sed -n 's/^.*Version: *//p' | awk '{print $1}')" + if [ "$VER" != "$EXPECTED_VER" ]; then + echo "FAIL: Version mismatch (expected '$EXPECTED_VER', got '$VER')" >&2 + exit 1 + fi + echo " Version: $VER ✓" +fi + +echo "PASS: $IMAGE" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf9fcba1..c932ab49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,7 @@ jobs: [ -n "$goarm" ] && build_args="$build_args --build-arg GOARM=$goarm" echo "=== Building $tag (GOARCH=$goarch GOARM=$goarm) ===" docker build $build_args --tag $IMAGE:$tag --file .github/Dockerfile . + .github/verify-binary.sh "$goarch" "$IMAGE:$tag" "$VERSION" docker push $IMAGE:$tag ALL_TAGS="$ALL_TAGS $IMAGE:$tag" done