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
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@

## Checklist

- [ ] `go build ./...`, `go vet ./...`, and `go test ./...` pass
- [ ] `go -C src build ./...`, `go -C src vet ./...`, and `go -C src test ./...` pass
- [ ] Added/updated tests for new logic
- [ ] Updated docs / CHANGELOG where relevant
13 changes: 1 addition & 12 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
directory: "/src"
schedule:
interval: weekly
day: monday
Expand All @@ -24,14 +24,3 @@ updates:
github-actions-minor-and-patch:
patterns: ["*"]
update-types: [minor, patch]
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
day: monday
time: "06:00"
timezone: America/New_York
groups:
docker-minor-and-patch:
patterns: ["*"]
update-types: [minor, patch]
62 changes: 53 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ jobs:
with:
go-version: "1.25"
check-latest: true
- run: go build ./...
- run: go vet ./...
- run: go test -race -coverprofile=coverage.txt ./...
cache-dependency-path: src/go.sum
# The module lives in src/: a bare `go test ./...` at the root finds no
# packages and passes, so every command names it.
- run: go -C src build ./...
- run: go -C src vet ./...
- run: go -C src test -race -coverprofile=../coverage.txt ./...

lint:
runs-on: ubuntu-latest
Expand All @@ -62,9 +65,11 @@ jobs:
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
- uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: latest
working-directory: src

govulncheck:
runs-on: ubuntu-latest
Expand All @@ -73,10 +78,11 @@ jobs:
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
# Pinned: govulncheck @latest (v1.4.0) segfaults under the go1.25 toolchain.
- run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
- run: |
"$(go env GOPATH)/bin/govulncheck" ./...
cd src && "$(go env GOPATH)/bin/govulncheck" ./...

gosec:
runs-on: ubuntu-latest
Expand All @@ -85,6 +91,7 @@ jobs:
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
# Build gosec with the runner's Go — the gosec docker action bundles an
# older Go that cannot load a `go 1.24` module.
# probaci is a CI orchestrator: it runs subprocesses (G204) and reads/writes
Expand All @@ -96,17 +103,23 @@ jobs:
# 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 ./...
cd src && "$(go env GOPATH)/bin/gosec" -exclude=G104,G204,G301,G302,G304,G306 ./...

gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
# Run gitleaks directly (the gitleaks-action requires a paid license for
# org repos). Scans the working tree and honors .gitleaks.toml.
# org repos). Scans the working tree and honors .gitleaks.toml. Built from
# source at a named release rather than run from a container image, which
# this repository no longer uses anywhere.
- run: go install github.com/zricethezav/gitleaks/v8@v8.30.1
- run: |
docker run --rm -v "${{ github.workspace }}:/repo" -w /repo \
zricethezav/gitleaks:latest dir . --no-banner --redact --exit-code 1
"$(go env GOPATH)/bin/gitleaks" dir . --no-banner --redact --exit-code 1

# Dogfood: probaci proves itself. Scoped to the platform/security stages that
# aren't already covered by the dedicated jobs above (lint/govulncheck/gosec/
Expand All @@ -118,4 +131,35 @@ jobs:
- uses: actions/setup-go@v7
with:
go-version: "1.25"
- run: go run ./cmd/probaci run --ci --only detect,workflow-lint,versions,secrets
cache-dependency-path: src/go.sum
# go -C src starts probaci inside src/; it walks up to the git root and
# proves the whole repository, as it did from the root.
- run: go -C src run ./cmd/probaci run --ci --only detect,workflow-lint,versions,secrets

# Cross-compile every target into build/<os>/ on every pull request (and on
# demand) and attach the binaries to the run, so a reviewer can try the exact
# binaries before merging. It builds with src/scripts/build-all.sh and
# publishes nothing; no GoReleaser runs here. `make cross` ends with
# `make check-build`, so a stray file in build/ fails the job.
snapshot:
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [test, lint]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
- run: make cross
- run: cd build && sha256sum -c checksums.txt
- uses: actions/upload-artifact@v7
with:
name: "probaci-snapshot-${{ github.event.pull_request.number || github.run_id }}"
if-no-files-found: error
retention-days: 14
path: |
build/*/probaci_*
build/checksums.txt
9 changes: 8 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ jobs:
actions: read
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
# The module lives in src/, which autobuild does not look for: manual mode
# builds it explicitly, so the scan never reports clean on zero code.
- uses: github/codeql-action/init@v4
with:
languages: go
- uses: github/codeql-action/autobuild@v4
build-mode: manual
- run: go -C src build ./...
- uses: github/codeql-action/analyze@v4
44 changes: 30 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: release

# Every release is tag-driven: GoReleaser cross-compiles the full matrix, builds
# the archives/packages and a reproducible source tarball, generates SBOMs and
# checksums, pushes the container image, cosign-signs the checksums, creates the
# checksums, cosign-signs the checksums, creates the
# 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.
Expand Down Expand Up @@ -32,6 +32,12 @@ concurrency:
permissions:
contents: read

# Our own modules are fetched from git, never through proxy.golang.org or
# sum.golang.org, so the build itself is not what feeds the public proxy a tag.
env:
GOPRIVATE: github.com/simtabi/*
GONOSUMDB: github.com/simtabi/*

jobs:
goreleaser:
runs-on: ubuntu-latest
Expand All @@ -41,7 +47,6 @@ jobs:
contents: write # create the GitHub Release, upload assets
id-token: write # cosign keyless signing + provenance via OIDC
attestations: write # write the build-provenance attestation
packages: write # push the container image
steps:
- name: Refuse without confirmation or a release tag
env:
Expand All @@ -62,27 +67,38 @@ jobs:
- uses: actions/setup-go@v7
with:
go-version: "1.25"
cache-dependency-path: src/go.sum
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
# syft powers GoReleaser's SBOM generation.
- uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
# QEMU + buildx build the multi-arch dockers_v2 images; login pushes to ghcr.
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- name: Log in to ghcr.io
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# `go install` needs a tag named after the module's subdirectory: the
# module is github.com/simtabi/probaci/src, so Go resolves src/vX.Y.Z and
# ignores vX.Y.Z. OSS GoReleaser rejects the prefixed tag as non-semver,
# so the release runs off vX.Y.Z and mirrors src/vX.Y.Z onto the same
# commit here, where it cannot be forgotten. This workflow starts on no
# tag push, so pushing src/... starts no second run. A re-run finds the
# tag already there and leaves it alone: a public tag is never moved.
- name: Mirror the tag Go resolves the module through
run: |
alias_tag="src/${GITHUB_REF_NAME}"
if git rev-parse -q --verify "refs/tags/${alias_tag}" >/dev/null; then
echo "${alias_tag} already exists"
else
git tag "${alias_tag}" "${GITHUB_REF_NAME}^{commit}"
git push origin "refs/tags/${alias_tag}"
echo "pushed ${alias_tag}"
fi
# The notes go to $RUNNER_TEMP, never the checkout: an untracked file in
# the working tree makes GoReleaser refuse to release a dirty tree.
- name: Extract CHANGELOG section for this tag
run: ./scripts/extract-changelog.sh "${GITHUB_REF_NAME}" > release_notes.md
run: ./src/scripts/extract-changelog.sh "${GITHUB_REF_NAME}" > "$RUNNER_TEMP/release_notes.md"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@a4f02ab5355a589de60d917a659ce617ed19b3de # v7.3.0
with:
# Pin the GoReleaser series so the config schema and the tool stay in
# sync (avoids `latest` deprecating config keys out from under a release).
version: "~> v2.4"
args: release --clean --release-notes=release_notes.md
args: release --clean --config src/.goreleaser.yaml --release-notes=${{ runner.temp }}/release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# PAT with write access to the Homebrew tap + Scoop bucket repos.
Expand All @@ -97,4 +113,4 @@ jobs:
- name: Attest build provenance
uses: actions/attest-build-provenance@v4
with:
subject-checksums: build/dist/checksums.txt
subject-checksums: .cache/goreleaser/checksums.txt
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
/probaci
/bin/
/dist/
/build/dist/
# Generated build output. build/ keeps its tracked inputs (targets.txt, and
# nfpm.yaml if one is added); everything else in it is generated, one folder
# per OS (see the Makefile).
/build/*
!/build/targets.txt
!/build/nfpm.yaml
# GoReleaser's scratch dist (src/.goreleaser.yaml `dist:`).
/.cache/
*.exe
*.test
*.out
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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.
- The Go module moved into `src/`, and its path is now `github.com/simtabi/probaci/src`. Importers
of `pkg/probaci` use `github.com/simtabi/probaci/src/pkg/probaci`. A release tags `src/vX.Y.Z`
beside `vX.Y.Z` on the same commit, since that is the tag Go resolves the module through.
- `make build` writes `build/<os>/probaci` (macOS as `macos`) instead of `bin/probaci`.
`make cross` and `make dist` write one folder per OS under `build/`, with
`build/checksums.txt` over `{os}/{file}` paths and archives only in `build/archives/`;
`build/dist/` is gone, and GoReleaser's scratch output moved to `.cache/goreleaser/`.
- The Makefile is the one task definition, and every Taskfile task forwards to it.
- `probaci docs` reads a copy of `README.md` and `docs/` kept in `src/embedded/` (`make sync-docs`),
because the module in `src/` cannot embed files above it; a test fails when the copy drifts.
- The CI gitleaks scan runs a gitleaks v8.30.1 binary instead of the `zricethezav/gitleaks` image.

### Added

- Tests that fail when `build/` holds anything but OS folders, inputs and checksums, when a
container image or docker step returns, and when the docs offer `go install`. The release
trigger test now also requires the refusal to be the workflow's first step.
- A CI `snapshot` job that runs `make cross` on every pull request and uploads the binaries.

### Removed

- The container image. The `Dockerfile`, the GoReleaser `dockers_v2` block
(`ghcr.io/simtabi/probaci`, linux/amd64 and linux/arm64), the release workflow's QEMU, buildx
and ghcr.io login steps and its `packages: write` permission, and Dependabot's docker ecosystem
are gone. No image was ever published, so nothing on ghcr.io is affected.

## [0.1.0] - 2026-06-18

Expand Down
34 changes: 24 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,39 @@ Thanks for your interest in improving probaci.
```sh
git clone https://github.com/simtabi/probaci
cd probaci
go build ./... # or: task build / make build (version-stamped)
go test ./...
go vet ./...
golangci-lint run # or: task lint
make build # build/<host-os>/probaci, version-stamped (or: task build)
make test # go -C src test -race ./...
make vet
make lint # golangci-lint, run in src/
```

The Go module lives in `src/`, so a bare `go test ./...` at the repository root
finds no packages and passes; name the module with `go -C src …`. `build/` holds
one folder per OS and nothing else, and `make check-build` fails on anything
else.

probaci dogfoods itself — once built, run it on the repo:

```sh
go run ./cmd/probaci run --skip workflow-run
go -C src run ./cmd/probaci run --skip workflow-run
```

Build local release deliverables and validate the release config:
Cross-compile and validate the release config (none of these publishes):

```sh
task bundle # dist/ archives + checksums (no publish)
task release-check # goreleaser check (uses the pinned GoReleaser series)
make cross # build/<os>/ binaries + build/checksums.txt
make dist # the same, plus build/archives/
make release-check # goreleaser check --config src/.goreleaser.yaml
```

`probaci docs` renders an embedded copy of `README.md` and `docs/` from
`src/embedded/`. After editing either, run `make sync-docs`; a test fails when
the copy drifts.

## Project layout

Everything Go lives in `src/`:

- `cmd/probaci` — entrypoint
- `internal/cli` — Cobra commands (thin; parse flags, drive the engine)
- `internal/stage` — the pipeline engine and stage implementations
Expand All @@ -40,8 +52,10 @@ task release-check # goreleaser check (uses the pinned GoReleaser series)
- `internal/config`, `paths`, `logging`, `secret`, `ui`, `report`, `tui`,
`run` (RunID), `discover` (repo-root walk-up)
- `pkg/probaci` — the public, embeddable API
- `install.sh` / `install.ps1` — end-user installers (keep asset names in sync
with the GoReleaser archive `name_template`)
- `internal/release` — tests guarding the release trigger, the `build/` layout,
the absence of a container image, and the docs
- `scripts/install.sh` / `scripts/install.ps1` — end-user installers (keep asset
names in sync with the GoReleaser archive `name_template`)

## Adding things

Expand Down
7 changes: 0 additions & 7 deletions Dockerfile

This file was deleted.

Loading
Loading