check / check (pull_request) Failing after 1s
The release workflow installed Go via actions/setup-go, which pins the action but not the toolchain tarball it downloads at runtime -- the compiler that produces the published binaries was the last external input in the release path verified against nothing in the repo, against REPO_POLICIES.md's hash-pin rule. New script/install-go, modelled on script/install-goreleaser, downloads the exact go.dev archive for go.mod's `go` directive and refuses it unless its sha256 matches a value committed in the script. release.yml calls it instead of setup-go and sets GOTOOLCHAIN=local so that exact compiler builds the release. The version is not duplicated: go.mod owns it and install-go fails when its committed GO_VERSION disagrees, so bumping Go edits go.mod, the checksum, and the Dockerfile golang digest together. Model: opus-4-8
162 lines
5.4 KiB
Bash
Executable File
162 lines
5.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/install-go: install the Go toolchain pinned by go.mod into the
|
|
# repo-local tool directory, verified against a committed sha256. Our
|
|
# own extension to scripts-to-rule-them-all. Idempotent: exits at once
|
|
# when the pinned toolchain is already installed.
|
|
#
|
|
# Only .gitea/workflows/release.yml calls this. goreleaser is not a
|
|
# compiler: it shells out to `go` for the `before:` hook and for every
|
|
# one of the four cross-compiles, so the release runner needs a Go
|
|
# toolchain on PATH. check.yml never does -- it builds inside the
|
|
# digest-pinned Dockerfile images -- so this is the release path's only
|
|
# host Go, and per REPO_POLICIES.md it must be pinned by hash.
|
|
# actions/setup-go exposes no checksum input, so Go is installed the way
|
|
# script/install-goreleaser installs goreleaser: download the exact
|
|
# archive from go.dev and refuse it unless its sha256 matches the value
|
|
# committed below.
|
|
#
|
|
# The version is go.mod's `go` directive, the single source of truth for
|
|
# the toolchain. GO_VERSION below MUST equal it, and this script fails
|
|
# when they disagree -- so bumping Go is one reviewed change touching
|
|
# go.mod, the checksum here, and the Dockerfile golang digest together.
|
|
#
|
|
# Linux only, because that is what the release runner is. A darwin dev
|
|
# building a snapshot uses their own Go; supporting an OS means adding
|
|
# its checksums.
|
|
set -eu
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
|
|
# Go 1.26.1, 2026-09-21. Checksums are the sha256 values go.dev publishes
|
|
# for each archive at https://go.dev/dl/ (also in its ?mode=json
|
|
# manifest).
|
|
GO_VERSION="1.26.1"
|
|
SHA256_LINUX_AMD64="031f088e5d955bab8657ede27ad4e3bc5b7c1ba281f05f245bcc304f327c987a"
|
|
SHA256_LINUX_ARM64="a290581cfe4fe28ddd737dde3095f3dbeb7f2e4065cab4eae44dfc53b760c2f7"
|
|
|
|
GOROOT_DIR="$ROOT/.tool/go"
|
|
GOCMD="$GOROOT_DIR/bin/go"
|
|
|
|
# The `go` directive in go.mod, e.g. "1.26.1" from `go 1.26.1`.
|
|
gomod_go_version() {
|
|
sed -n 's/^go \([0-9][0-9.]*\).*/\1/p' "$ROOT/go.mod" | head -n 1
|
|
}
|
|
|
|
# Print the version of the go at $1 as "1.26.1", or nothing if it is not
|
|
# usable. `go version` prints "go version go1.26.1 linux/amd64".
|
|
go_version() {
|
|
[ -x "$1" ] || return 0
|
|
"$1" version 2>/dev/null |
|
|
sed -n 's/^go version go\([0-9][0-9.]*\) .*/\1/p' |
|
|
head -n 1
|
|
}
|
|
|
|
verify_sha256() {
|
|
file="$1"
|
|
want="$2"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
got="$(sha256sum "$file" | cut -d' ' -f1)"
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
got="$(shasum -a 256 "$file" | cut -d' ' -f1)"
|
|
else
|
|
echo "install-go: no sha256sum or shasum available" >&2
|
|
return 1
|
|
fi
|
|
if [ "$got" != "$want" ]; then
|
|
echo "install-go: checksum mismatch for $file" >&2
|
|
echo " expected: $want" >&2
|
|
echo " actual: $got" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# On a Gitea/GitHub Actions runner, put the toolchain on PATH for the
|
|
# steps that follow by appending to the file named by $GITHUB_PATH. A
|
|
# no-op off CI, where the caller manages its own PATH.
|
|
export_ci_path() {
|
|
[ -n "${GITHUB_PATH:-}" ] || return 0
|
|
echo "$GOROOT_DIR/bin" >>"$GITHUB_PATH"
|
|
}
|
|
|
|
main() {
|
|
cd "$ROOT"
|
|
|
|
want="$(gomod_go_version)"
|
|
if [ "$want" != "$GO_VERSION" ]; then
|
|
echo "install-go: go.mod says go $want but this script pins" \
|
|
"$GO_VERSION." >&2
|
|
echo " Update GO_VERSION and the checksums in this script to" \
|
|
"match go.mod." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Already installed from a previous run? Then just fix PATH and stop.
|
|
if [ "$(go_version "$GOCMD")" = "$GO_VERSION" ]; then
|
|
echo "go $GO_VERSION already installed in .tool/go"
|
|
export_ci_path
|
|
return 0
|
|
fi
|
|
|
|
os="$(uname -s)"
|
|
arch="$(uname -m)"
|
|
case "$os" in
|
|
Linux) os="linux" ;;
|
|
*)
|
|
echo "install-go: unsupported OS $os (release runner is Linux)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
case "$arch" in
|
|
x86_64 | amd64)
|
|
arch="amd64"
|
|
sum="$SHA256_LINUX_AMD64"
|
|
;;
|
|
arm64 | aarch64)
|
|
arch="arm64"
|
|
sum="$SHA256_LINUX_ARM64"
|
|
;;
|
|
*)
|
|
echo "install-go: no pinned checksum for architecture $arch" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
archive="go${GO_VERSION}.${os}-${arch}.tar.gz"
|
|
url="https://go.dev/dl/${archive}"
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
echo "install-go: curl is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
dl="$(mktemp -d)"
|
|
mkdir -p "$ROOT/.tool"
|
|
stage="$(mktemp -d "$ROOT/.tool/.go-install.XXXXXX")"
|
|
# shellcheck disable=SC2064 # expand the paths now, not at trap time
|
|
trap "rm -rf '$dl' '$stage'" EXIT INT TERM
|
|
|
|
echo "installing go $GO_VERSION for ${os}-${arch}"
|
|
curl -fsSL --retry 3 -o "$dl/$archive" "$url"
|
|
verify_sha256 "$dl/$archive" "$sum"
|
|
|
|
# The archive unpacks to a top-level `go/` directory. Extract it into
|
|
# a staging directory on the same filesystem as the destination, then
|
|
# rename it into place so a concurrent run never observes a
|
|
# half-written toolchain.
|
|
tar -xzf "$dl/$archive" -C "$stage"
|
|
rm -rf "$GOROOT_DIR"
|
|
mv "$stage/go" "$GOROOT_DIR"
|
|
|
|
installed="$(go_version "$GOCMD")"
|
|
if [ "$installed" != "$GO_VERSION" ]; then
|
|
echo "install-go: installed toolchain reports '$installed'," \
|
|
"expected '$GO_VERSION'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "go $GO_VERSION installed to .tool/go"
|
|
export_ci_path
|
|
}
|
|
|
|
main "$@"
|