From 9269b896a6abaec13f25961f4118fa7235003b7d Mon Sep 17 00:00:00 2001 From: Ryan L'Italien Date: Thu, 3 Sep 2026 21:25:40 -0400 Subject: [PATCH] ci: tag-driven release workflow, VERSION build arg, minimal AGENTS.md Push a v* tag to publish linux/darwin/windows archives with SHA256SUMS on the Releases page and the container image to ghcr.io/butterstack/ butterstack-connector. The Dockerfile takes a VERSION build arg so the image reports the same version as the binaries. AGENTS.md gives AI coding agents the layout, commands, and the security rules that must not move. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/release.yml | 88 +++++++++++++++++++++++++++++++++++ AGENTS.md | 32 +++++++++++++ Dockerfile | 3 +- 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100644 AGENTS.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4154fc5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,88 @@ +# Cut a release by pushing a tag: `git tag v0.1.0 && git push origin v0.1.0`. +# Produces: one archive per OS/arch on the GitHub Releases page with a +# SHA256SUMS file, and the container image at ghcr.io/butterstack/butterstack-connector +# tagged with the version and `latest`. Version is stamped into the binary via +# -X main.Version (the same flag the Makefile uses). +name: release +on: + push: + tags: ["v*"] +permissions: + contents: write + packages: write +jobs: + binaries: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + matrix: + include: + - { goos: linux, goarch: amd64 } + - { goos: linux, goarch: arm64 } + - { goos: darwin, goarch: arm64 } + - { goos: darwin, goarch: amd64 } + - { goos: windows, goarch: amd64 } + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version: "1.25" + - name: Build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + run: | + VERSION="${GITHUB_REF_NAME}" + NAME="butterstack-connector_${VERSION}_${GOOS}_${GOARCH}" + BIN="butterstack-connector"; [ "$GOOS" = windows ] && BIN="butterstack-connector.exe" + mkdir -p "dist/$NAME" + go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}" -o "dist/$NAME/$BIN" ./cmd/butterstack-connector + cp README.md LICENSE connector.example.yml "dist/$NAME/" + cd dist + if [ "$GOOS" = windows ]; then zip -qr "$NAME.zip" "$NAME"; else tar -czf "$NAME.tar.gz" "$NAME"; fi + rm -rf "$NAME" + - uses: actions/upload-artifact@v4 + with: + name: dist-${{ matrix.goos }}-${{ matrix.goarch }} + path: dist/* + retention-days: 1 + release: + needs: binaries + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Checksums + run: cd dist && sha256sum * > SHA256SUMS && cat SHA256SUMS + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "${GITHUB_REF_NAME}" dist/* \ + --title "${GITHUB_REF_NAME}" \ + --generate-notes \ + --verify-tag + image: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v6 + with: + context: . + push: true + build-args: VERSION=${{ github.ref_name }} + tags: | + ghcr.io/butterstack/butterstack-connector:${{ github.ref_name }} + ghcr.io/butterstack/butterstack-connector:latest diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dabfdb2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# AGENTS.md + +Guidance for AI coding agents working in this repository. Humans: see README.md. + +## What this is + +The ButterStack Connector: an outbound-only Go daemon a game studio runs inside its own network so ButterStack can reach a private Perforce, TeamCity, Jenkins, GitHub Enterprise Server, or Horde. One outbound TLS connection, a typed command allowlist, no tunnel, no shell, credentials never leave the studio's disk. PROTOCOL.md is the wire contract and wins over any code comment. + +## Layout + +- `cmd/butterstack-connector/`: the binary's entry point. `main.Version` is stamped at build time. +- `internal/config`: config file parsing and validation (permissions, `*_file` secrets, scope rules). +- `internal/vocab`: the command vocabulary, one typed verb per entry with its argument constraints. Adding a verb means adding it here, in PROTOCOL.md, and in the drills. +- `internal/tools`: per-backend executors (Perforce, TeamCity). +- `internal/wsclient`: the single outbound WebSocket connection and liveness. +- `test/`: the drill harness (`drills.rb`), the mock broker, and backend stubs. + +## Commands + +- `make build`: builds `build/butterstack-connector` with the version stamped. +- `make test`: `go vet` and `go test ./...`. +- `make drills`: runs the seven recovery and denial drills against the in-process mock broker. +- `make check`: test then drills. CI runs vet, test, and build on every push; a `v*` tag runs the release workflow. + +## Rules + +- Never widen the allowlist implicitly. Every verb has fixed argument names and patterns; `bannedArgNames` and `depot_scope` checks are security boundaries, not conveniences. +- No secret may be accepted from a flag, an environment variable, or the wire. Only the config file and the `*_file` paths it names. +- Do not add inbound listeners, remote configuration, or shell execution of any kind. +- Keep `connector.example.yml` in sync with `internal/config`, and PROTOCOL.md in sync with `internal/vocab`. +- Plain prose in docs: no em dashes, one paragraph per line. +- Commit messages and PR bodies are written to a file and passed with `-F` / `--body-file`, never inline. diff --git a/Dockerfile b/Dockerfile index b007250..9b13614 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,8 @@ COPY . . # -trimpath drops build-machine file paths from the binary; -s -w strips the # symbol table and DWARF debug info. CGO_ENABLED=0 keeps the binary static, so # the runtime stage needs no libc compatibility shim. -RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/butterstack-connector ./cmd/butterstack-connector +ARG VERSION=dev +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}" -o /out/butterstack-connector ./cmd/butterstack-connector FROM ruby:3.3-alpine