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
88 changes: 88 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down