From 341a0e546305459e3c6ff577493c9649b59141e2 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Mon, 2 Nov 2026 11:46:59 -0500 Subject: [PATCH 1/3] Run the release workflow only on a confirmed manual dispatch Go releases are git only until the owner decides otherwise for this project. A tag push no longer starts GoReleaser; a release is dispatched on a v[0-9]* tag with -f confirm=release-go, and the first step refuses anything else before a single artifact is built. TestReleaseRunsOnlyOnAConfirmedDispatch fails if release.yml regains an automatic trigger or loses the confirmation; it was seen failing with the old tag-push trigger restored. docs/release.md describes the new steps. --- .github/workflows/release.yml | 31 +++++++++++++++++++++++++++++-- CHANGELOG.md | 6 ++++++ docs/release.md | 25 +++++++++++++++++++++---- release_workflow_test.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 release_workflow_test.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e51f7e..394d1fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,9 +6,23 @@ name: release # GitHub Release (body from the tagged CHANGELOG section), and updates the # Homebrew/Scoop manifests. A build-provenance attestation is then signed for # every artifact via the checksums file. +# +# Go releases are git only until the owner says otherwise for this project, so +# nothing here starts on a tag push: pushing a vX.Y.Z tag records a version and +# publishes nothing. A release is dispatched by hand against the tag, with an +# explicit confirmation, and refuses to run without both: +# +# gh workflow run release.yml --repo simtabi/probaci --ref vX.Y.Z -f confirm=release-go +# +# It stays tag-driven: GoReleaser reads the version from the ref it is run +# against, and the first step refuses a ref that is not a v[0-9]* tag. on: - push: - tags: ["v*"] + workflow_dispatch: + inputs: + confirm: + description: Type release-go to publish binaries for the tag this run is dispatched on + required: true + type: string # A newer tag must never abort an in-flight publish. concurrency: @@ -29,6 +43,19 @@ jobs: attestations: write # write the build-provenance attestation packages: write # push the container image steps: + - name: Refuse without confirmation or a release tag + env: + CONFIRM: ${{ inputs.confirm }} + run: | + if [ "${CONFIRM}" != "release-go" ]; then + echo "::error::confirm must be release-go; nothing was published" + exit 1 + fi + case "${GITHUB_REF}" in + refs/tags/v[0-9]*) ;; + *) echo "::error::dispatch this on a vX.Y.Z tag (--ref vX.Y.Z), not ${GITHUB_REF}"; exit 1 ;; + esac + - uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index b37745c..f045194 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- The release workflow no longer starts on a tag push. Binary releases are on hold, and a release + runs only when dispatched by hand on a `v[0-9]*` tag with `-f confirm=release-go`; a test fails + if an automatic trigger returns. + ## [0.1.0] - 2026-06-18 ### Added diff --git a/docs/release.md b/docs/release.md index 30b5a5e..35be87f 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,7 +1,12 @@ # Release -Releases are tag-driven and built by GoReleaser. The CI GoReleaser version is -pinned (`~> v2.4`) so the config schema and tool stay in sync. +Releases are tag-driven and built by GoReleaser, and binary releases are on +hold. A `vX.Y.Z` tag records a version and publishes nothing; a binary release is +built from a tag only when dispatched by hand with a confirmation (see +[Cutting a release](#cutting-a-release)). Until binary releases resume, users +build from source ([installation](installation.md#build-from-source)). The CI +GoReleaser version is pinned (`~> v2.4`) so the config schema and tool stay in +sync. ## Local deliverables (no publishing) @@ -52,7 +57,18 @@ regenerated `digests.json`. git tag v0.1.0 git push origin v0.1.0 ``` -3. The `release` workflow runs GoReleaser, which builds the matrix, signs with + Pushing a tag publishes nothing: binary releases are on hold, and probaci is + built from source until the owner decides otherwise. +3. Only when a binary release has been approved for this version, dispatch the + `release` workflow on the tag: + ```sh + gh workflow run release.yml --repo simtabi/probaci --ref vX.Y.Z -f confirm=release-go + ``` + `vX.Y.Z` must be a tag that is already on GitHub. This publishes for real, so + run it only for a version the owner approved. The workflow's first step + refuses to run without `confirm=release-go`, or on anything but a `v[0-9]*` + tag, before anything is built. +4. The `release` workflow runs GoReleaser, which builds the matrix, signs with cosign (keyless via OIDC), attaches an SBOM and SLSA provenance, and publishes the GitHub Release, container image, deb/rpm, and Homebrew/Scoop manifests. @@ -68,7 +84,8 @@ Pure Go, `CGO_ENABLED=0`. long-lived registry credentials). 3. Add the write deploy keys `HOMEBREW_TAP_DEPLOY_KEY` (on `simtabi/homebrew-tap`) and `SCOOP_BUCKET_DEPLOY_KEY` (on `simtabi/scoop-bucket`), each a repository secret that reaches its one repository and nothing else (`TAP_GITHUB_TOKEN` is only a fallback, and the Winget pull request's token), plus any registry tokens as needed. -4. Cut `v0.1.0`. +4. Tag `v0.1.0`, then dispatch the release on it as in + [Cutting a release](#cutting-a-release). The full org-wide gate is `/opensource/shipping-checklist.md` (local; not in any repo). Run it before every release. diff --git a/release_workflow_test.go b/release_workflow_test.go new file mode 100644 index 0000000..0dbcd0d --- /dev/null +++ b/release_workflow_test.go @@ -0,0 +1,28 @@ +package probaci + +import ( + "os" + "path/filepath" + "regexp" + "strings" + "testing" +) + +// Go releases are git only until the owner says otherwise, so release.yml must +// never start on its own: no push trigger of any kind, and a dispatch that +// refuses to publish without confirm=release-go. A tag push that started +// GoReleaser is how an unwanted release began elsewhere on 2026-11-02. +func TestReleaseRunsOnlyOnAConfirmedDispatch(t *testing.T) { + wf, err := os.ReadFile(filepath.Join(".github", "workflows", "release.yml")) + if err != nil { + t.Fatal(err) + } + if regexp.MustCompile(`(?m)^[ \t]*(push|schedule|pull_request|release)\s*:`).Match(wf) { + t.Error("release.yml has an automatic trigger; it must run on workflow_dispatch only") + } + for _, want := range []string{"workflow_dispatch:", "confirm:", `"${CONFIRM}" != "release-go"`, "refs/tags/v[0-9]*"} { + if !strings.Contains(string(wf), want) { + t.Errorf("release.yml lacks %q: a release needs an explicit, confirmed dispatch on a tag", want) + } + } +} From e291f1787ec201a4bf06a4fdc602271b6c650e95 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Mon, 2 Nov 2026 11:46:59 -0500 Subject: [PATCH 2/3] Document building from source as the only install path Binary releases are on hold, so the README and installation guide no longer offer the install scripts, Homebrew, Scoop, Linux packages, the container image, release downloads or go install. They give a clone, make build and probaci install, how to pin a tag and update, and note that no binary release has been published yet. The release tooling and install scripts stay in the tree. --- CHANGELOG.md | 2 + README.md | 24 ++++------ docs/installation.md | 102 +++++++++++++++++++------------------------ 3 files changed, 57 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f045194..6568911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - The release workflow no longer starts on a tag push. Binary releases are on hold, and a release runs only when dispatched by hand on a `v[0-9]*` tag with `-f confirm=release-go`; a test fails if an automatic trigger returns. +- The README and installation guide build probaci from source only: no download, install-script, + package-manager or `go install` path is offered while binary releases are on hold. ## [0.1.0] - 2026-06-18 diff --git a/README.md b/README.md index 688b01f..89782a4 100644 --- a/README.md +++ b/README.md @@ -38,25 +38,19 @@ probaci is built to catch the common causes automatically: ## Install -Most users install the binary (you only need the source to change probaci): +probaci is built from source: binary releases are on hold, so there is no +download, install-script, package-manager or `go install` channel to use. You need +Go (the version `go.mod` names), `git` and `make`. ```sh -# macOS / Linux — Homebrew -brew install simtabi/tap/probaci - -# macOS / Linux — install script (downloads + checksum-verifies the right binary) -curl -fsSL https://raw.githubusercontent.com/simtabi/probaci/main/scripts/install.sh | bash - -# Windows — Scoop, or the install script -scoop bucket add simtabi https://github.com/simtabi/scoop-bucket; scoop install probaci -irm https://raw.githubusercontent.com/simtabi/probaci/main/scripts/install.ps1 | iex - -# Go toolchain -go install github.com/simtabi/probaci/cmd/probaci@latest +git clone https://github.com/simtabi/probaci ~/src/probaci +make -C ~/src/probaci build # -> bin/probaci +~/src/probaci/bin/probaci install # copies itself onto PATH (~/.local/bin) +probaci doctor ``` -deb/rpm packages and a container image are published per release. Full details, -PATH guidance, and building from source: [docs/installation.md](docs/installation.md). +Pinning a version, updating, Windows, and PATH guidance are in +[docs/installation.md](docs/installation.md). ## Quick start guide and usage diff --git a/docs/installation.md b/docs/installation.md index b212292..80d282f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,46 +1,63 @@ # Installation -probaci ships as a single static binary. Most users should install the binary — -you only need the source to make changes. The only hard runtime dependencies are -a container runtime (Docker, rootless Docker, or Podman) and your VCS. +probaci is a single static binary, built from source. Binary releases are on +hold until the maintainer decides otherwise, so this page offers no download, +install-script, package-manager or `go install` path. The only hard runtime +dependencies are a container runtime (Docker, rootless Docker, or Podman) and +your VCS. -## Quick install (recommended) +## Build from source -**macOS / Linux — Homebrew:** +You need Go (the version `go.mod` names), `git`, and `make` (or +[go-task](https://taskfile.dev)). ```sh -brew install simtabi/tap/probaci +git clone https://github.com/simtabi/probaci ~/src/probaci +make -C ~/src/probaci build # -> bin/probaci +~/src/probaci/bin/probaci install # copies itself to ~/.local/bin (Windows: %LOCALAPPDATA%\Programs\probaci) +probaci doctor ``` -**Windows — Scoop:** +`probaci install --system` installs machine-wide (`/usr/local/bin`, +`%ProgramFiles%`, needs elevation), `--dir DIR` picks an explicit directory, and +`probaci uninstall [--purge]` removes it again. Copying `bin/probaci` onto your +`PATH` by hand works just as well. -```powershell -scoop bucket add simtabi https://github.com/simtabi/scoop-bucket -scoop install probaci -``` +Without `make`, the dependency-free primitive is +`go -C ~/src/probaci build -o bin/probaci ./cmd/probaci`. On Windows, run the same +`git clone` and that `go build` (with `-o bin\probaci.exe`) from PowerShell, then +run `bin\probaci.exe install`. + +### Pinning a version -**macOS / Linux — install script** (downloads the right binary for your OS/arch, -verifies its checksum, installs it on your PATH): +A version is a tag: check one out to build exactly that release (`git -C ~/src/probaci tag` +lists them). ```sh -curl -fsSL https://raw.githubusercontent.com/simtabi/probaci/main/scripts/install.sh | bash +git -C ~/src/probaci fetch --tags +git -C ~/src/probaci checkout vX.Y.Z +make -C ~/src/probaci build ``` -**Windows — install script:** +### Updating -```powershell -irm https://raw.githubusercontent.com/simtabi/probaci/main/scripts/install.ps1 | iex +```sh +git -C ~/src/probaci pull --ff-only +make -C ~/src/probaci build +~/src/probaci/bin/probaci install ``` -The script installs to `/usr/local/bin` by default, requesting `sudo` only if -that directory isn't writable (Windows: `%LOCALAPPDATA%\Programs\probaci`). -Override the directory with `PROBACI_INSTALL_DIR`, and pin a version by passing -it as an argument: +`probaci version` reports the version the build was stamped with (`git describe`), +so a build from a branch is still identifiable. -```sh -PROBACI_INSTALL_DIR="$HOME/bin" \ - curl -fsSL https://raw.githubusercontent.com/simtabi/probaci/main/scripts/install.sh | bash -s v0.1.0 -``` +### Binary releases + +No binary release has been published. The release tooling stays in the tree: +the GoReleaser config, and `scripts/install.sh` / `scripts/install.ps1`, which +download release archives. The release workflow would also publish Homebrew and +Scoop manifests, deb/rpm packages and a `ghcr.io/simtabi/probaci` container image. +None of those channels is offered while binary releases are on hold; see +[release.md](release.md). ## Run it globally @@ -48,7 +65,7 @@ Once the install directory is on your `PATH`, run `probaci` from anywhere — it auto-discovers the repository root by walking up from the current directory (ddev-style), so `probaci run` works from any subdirectory. -If the installer warns that the directory isn't on your `PATH`, add it: +If `probaci install` warns that the directory isn't on your `PATH`, add it: | Shell / OS | Add to PATH | |---|---| @@ -58,34 +75,7 @@ If the installer warns that the directory isn't on your `PATH`, add it: | Windows (PowerShell) | `[Environment]::SetEnvironmentVariable('Path', "$dir;$env:Path", 'User')` | `/usr/local/bin` is already on `PATH` for most systems — prefer it for a -machine-wide install (it may require `sudo` or `PROBACI_INSTALL_DIR=/usr/local/bin` -with elevation). - -## go install (Go toolchain) - -```sh -go install github.com/simtabi/probaci/cmd/probaci@latest -``` - -## Manual download - -Download the archive for your OS/arch from -[GitHub Releases](https://github.com/simtabi/probaci/releases), verify it against -`checksums.txt` (and the cosign signature), extract, and either place `probaci` -on your `PATH` or let probaci place itself: - -```sh -probaci install # per-user (~/.local/bin, %LOCALAPPDATA%\Programs\probaci) -probaci install --system # machine-wide (/usr/local/bin, %ProgramFiles%) — needs elevation -probaci install --dir DIR # explicit directory -probaci uninstall [--purge] -``` - -## Container image - -```sh -docker run --rm -v "$PWD:/workspace" ghcr.io/simtabi/probaci:latest run -``` +machine-wide install (`probaci install --system`, which needs elevation). ## Supported platforms and architectures @@ -99,9 +89,9 @@ docker run --rm -v "$PWD:/workspace" ghcr.io/simtabi/probaci:latest run > Windows configurations, runtime availability varies — probaci's `--no-docker` > mode runs the non-container checks and skips the rest. -## Build from source (contributors only) +## Development builds -You only need this to modify probaci. One task definition, three entrypoints: +One task definition, three entrypoints: ```sh task build # go-task (Taskfile.yml) From 0b4e0a403c133c4b854406d95e1826dbd0a8cff4 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Mon, 2 Nov 2026 12:59:56 -0500 Subject: [PATCH 3/3] Pin gosec to v2.29.0 so the job installs on Go 1.25 gosec@latest resolved to v2.30.0, whose go.mod requires go >= 1.26. The job runs Go 1.25 with GOTOOLCHAIN=local, so go install refused it and the job failed before scanning anything. v2.29.0 is the newest release that accepts 1.25, matching the go-version every other job and go.mod use. It scans the module clean with the existing excludes. --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5876d76..d685dd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,10 @@ jobs: # user-named files (G304/G306) with conventional perms (G301/G302) by # design, and G104 duplicates errcheck. The high-severity taint rules # (G702/G703) stay on, with #nosec on the few benign sites. - - run: go install github.com/securego/gosec/v2/cmd/gosec@latest + # Pinned, not @latest: gosec v2.30.0 requires go >= 1.26 and fails to + # install on this job's Go 1.25 (GOTOOLCHAIN=local). v2.29.0 is the + # newest release whose go.mod accepts 1.25; bump with the go-version. + - run: go install github.com/securego/gosec/v2/cmd/gosec@v2.29.0 - run: | "$(go env GOPATH)/bin/gosec" -exclude=G104,G204,G301,G302,G304,G306 ./...