Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...

Expand Down
31 changes: 29 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ 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.
- 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

### Added
Expand Down
24 changes: 9 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
102 changes: 46 additions & 56 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,71 @@
# 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

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 |
|---|---|
Expand All @@ -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

Expand All @@ -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)
Expand Down
25 changes: 21 additions & 4 deletions docs/release.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand Down
28 changes: 28 additions & 0 deletions release_workflow_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Loading