diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0df3f29..5c8d311 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,20 @@ jobs: make tidy git diff --exit-code -- go.mod go.sum + platforms: + name: builds for every platform + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + # The same script the release runs, so a tag is never the first time a + # target is built. + - run: ./scripts/build-release.sh dev dist + - name: the checksums match the files + run: cd dist && sha256sum -c SHA256SUMS + race: name: race detector runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e30b56..c32a94c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -127,3 +127,41 @@ jobs: fi gh release create "$TAG" --verify-tag --title "$TAG" "${notes[@]}" "${flags[@]}" + + binaries: + name: publish binaries + # Archives for every platform, for hosts with no Docker and no Go: Windows + # servers, 32-bit ARM, FreeBSD. After the release page exists, because they + # are attached to it. + needs: release + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build every platform + env: + TAG: ${{ github.ref_name }} + run: ./scripts/build-release.sh "${TAG#v}" dist + + # A binary that does not report the tag was not stamped with it, and + # would be published as a release it is not. + - name: The Linux build reports this release + env: + TAG: ${{ github.ref_name }} + run: | + tar -xzf "dist/certpilot-agent_${TAG#v}_linux_amd64.tar.gz" -C "$RUNNER_TEMP" + got=$("$RUNNER_TEMP/certpilot-agent_${TAG#v}_linux_amd64/certpilot-agent" version) + [ "$got" = "${TAG#v}" ] || { echo "binary says $got, tag is $TAG"; exit 1; } + + - name: Attach them to the release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + # --clobber so re-running a tag's workflow replaces its files. + run: gh release upload "$TAG" dist/* --clobber diff --git a/README.md b/README.md index 86f66e5..e9dfad2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ the key does not, and there is no code path that would let it. go install github.com/certpilot/certpilot-agent/cmd@latest ``` +No Go or Docker on the host? Each [release](https://github.com/certpilot/certpilot-agent/releases) +has a static binary for Linux (amd64, arm64, armv7, 386), Windows (amd64, arm64), +macOS and FreeBSD, with a `SHA256SUMS` file to check it against. + ``` docker run --rm -v certpilot-agent:/var/lib/certpilot-agent \ ghcr.io/certpilot/agent:latest --help diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100755 index 0000000..8358983 --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# Build the agent for every platform it is released for. +# +# ./scripts/build-release.sh 0.2.4 dist stamp 0.2.4, write archives to dist/ +# ./scripts/build-release.sh dev dist unstamped, as CI builds it +# +# One script for CI and for the release, so a tag is never the first time a +# target is built. Each target is a static binary (CGO off) in an archive with +# the licence and README, and SHA256SUMS lists them all. +# +set -euo pipefail + +cd "$(dirname "$0")/.." + +version="${1:?usage: build-release.sh VERSION OUTDIR}" +out="${2:?usage: build-release.sh VERSION OUTDIR}" + +# os/arch[/goarm] +targets=( + linux/amd64 linux/arm64 linux/arm/7 linux/386 + windows/amd64 windows/arm64 + darwin/amd64 darwin/arm64 + freebsd/amd64 +) + +# An unstamped build keeps the -dev default compiled into the source, rather +# than claiming a release number it is not. +ldflags="-s -w" +if [ "$version" != dev ]; then + ldflags="$ldflags -X github.com/certpilot/certpilot-agent.Version=$version" +fi + +rm -rf "$out" +mkdir -p "$out" +out="$(cd "$out" && pwd)" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +for target in "${targets[@]}"; do + IFS=/ read -r goos goarch goarm <<<"$target" + name="certpilot-agent_${version}_${goos}_${goarch}${goarm:+v$goarm}" + bin=certpilot-agent + [ "$goos" = windows ] && bin=certpilot-agent.exe + + dir="$work/$name" + mkdir -p "$dir" + CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" GOARM="${goarm:-}" \ + go build -trimpath -ldflags="$ldflags" -o "$dir/$bin" ./cmd/ + cp LICENSE README.md "$dir/" + + if [ "$goos" = windows ]; then + (cd "$work" && zip -qr "$out/$name.zip" "$name") + else + tar -C "$work" -czf "$out/$name.tar.gz" "$name" + fi + echo "built $name" +done + +sum() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; } +(cd "$out" && sum certpilot-agent_* > SHA256SUMS) +echo "wrote $(ls "$out" | wc -l | tr -d ' ') files to $out"