#!/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 "$@"