#!/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. # # .gitea/workflows/release.yml calls this, and so does script/bootstrap # when the host has no Go, as on the check runner. 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. The check runner compiles nothing on the host; it # uses this Go only for bootstrap's `go mod download` and for gofmt in # script/fmt-check. Per REPO_POLICIES.md a host Go is 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 checksums here, and the Dockerfile's two golang digests # together. # # Linux and macOS, each on amd64 and arm64: the four archives whose # checksums are committed below. 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" SHA256_DARWIN_AMD64="65773dab2f8cc4cd23d93ba6d0a805de150ca0b78378879292be0b903b8cdd08" SHA256_DARWIN_ARM64="353df43a7811ce284c8938b5f3c7df40b7bfb6f56cb165b150bc40b5e2dd541f" 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" ;; Darwin) os="darwin" ;; *) echo "install-go: unsupported OS $os" >&2 exit 1 ;; esac case "$arch" in x86_64 | amd64) arch="amd64" ;; arm64 | aarch64) arch="arm64" ;; *) echo "install-go: unsupported architecture $arch" >&2 exit 1 ;; esac case "${os}-${arch}" in linux-amd64) sum="$SHA256_LINUX_AMD64" ;; linux-arm64) sum="$SHA256_LINUX_ARM64" ;; darwin-amd64) sum="$SHA256_DARWIN_AMD64" ;; darwin-arm64) sum="$SHA256_DARWIN_ARM64" ;; 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 "$@"