setu:: docs
Reference

From code to deploy

The exact path a change takes from your editor to production — branch, PR, CI, merge, and the three things that ship on merge (console + control plane + docs) versus the agent, which ships on a tag. Plus how to run the whole thing locally.

Setu is a Go monorepo (a go.work workspace of five modules) plus a Next.js console. A code change flows through GitHub, gets verified by CI, and — on merge to main — auto-deploys the web + control plane (and docs, when they change). The edge setu runtime is different: it ships from a version tag, not from main.

The pipeline at a glance

Two triggers, two destinations. Push/PR drives verification and the provider-side app deploys· a tag drives the agent release. There is no deploy workflow in the repo for the app — Render and Vercel watch main directly.

Branch → PR → main

Standard trunk flow off main: branch, commit, push, open a PR· CI runs on the PR and again on the merge commit.

a change, start to mergebash
git checkout -b feat/incremental-resume
# … edit code …
git add -A && git commit -m "worker: resume staged loads from the last part"
git push -u origin feat/incremental-resume

gh pr create --fill
# CI (ci.yml) runs three jobs — see below.

gh pr merge --squash    # once checks are green + reviewed

CI — what ci.yml actually gates on

ci.yml runs on every PR and on push to main, with concurrency: cancel-in-progress per ref (a new push cancels the stale run) and least-privilege contents: read. It’s three jobs, not one:

goMatrix over [engine, apiserver, worker, agent, packs]: go build ./..., go vet ./..., go test ./... -race -count=1. fail-fast: false, so one module failing still reports the rest.
golangciA separate matrix running golangci-lint v2.1.6 via install-mode: goinstall — built from source with the job’s Go 1.26 toolchain (the prebuilt binary refuses to lint go 1.26 modules).
webDetects web/package.json, then pnpm 11 / Node 24 with a frozen lockfile: pnpm lint, typecheck, test, build.
ci.yml — the Go + lint matrices (excerpt)yaml
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  go:
    strategy:
      fail-fast: false
      matrix: { module: [engine, apiserver, worker, agent, packs] }
    steps:
      - uses: actions/setup-go@v5
        with: { go-version: "1.26" }
      - run: cd ${{ matrix.module }} && go build ./...
      - run: cd ${{ matrix.module }} && go vet ./...
      - run: cd ${{ matrix.module }} && go test ./... -race -count=1

  golangci:
    strategy:
      matrix: { module: [engine, apiserver, worker, agent, packs] }
    steps:
      - uses: golangci/golangci-lint-action@v7
        with: { version: v2.1.6, install-mode: goinstall, working-directory: "${{ matrix.module }}" }
Red CI blocks the merge
All three jobs must pass. Because the merge also runs CI, a change that only breaks on main (e.g. a flaky race) still surfaces — the same matrix runs on the merge commit.

What runs on merge

App deploys are provider-driven — there is no deploy workflow in the repo for them:

  • Control plane. Render watches main (autoDeploy: truein render.yaml), rebuilds the Docker image’s default combinedtarget (apiserver + worker), and rolls it out. The entrypoint starts the apiserver first — it applies the DB + River migrations — and only starts the worker once /healthzpasses· healthCheckPath: /healthz gates the rollout.
  • Console. Vercel builds and deploys the Next.js app on push to main, baking NEXT_PUBLIC_API_BASE_URL at build time.
  • Docs. docs.yml fires only when docs-site/** (or the workflow itself) changes: next buildpagefind --site out for search → touch out/.nojekyll → upload the Pages artifact· a second job with pages: write + id-token: write deploys it. Third-party actions are pinned to commit SHAs.

Agent releases — tag → GoReleaser → setu-dist

The edge runtime is versioned and released deliberately, not on every merge. Push a semver tag· release.yml runs GoReleaser (pinned to a commit SHA, since tags are mutable), which cross-compiles the setu binary and publishes assets to the public setu-dist repo. The source repo stays private, so curl | sh installs work for anyone.

cut an agent releasebash
git tag v0.2.0
git push origin v0.2.0
# release.yml → goreleaser/goreleaser-action (~> v2):
#   builds ./agent/cmd/setu for {linux, darwin, windows} × {amd64, arm64}
#   ldflags inject the version; windows archives as .zip, others tar
#   uploads binaries + archives + checksums.txt to the GitHub Release
#   on the PUBLIC Abhishekkumar2021/setu-dist repo
GOWORK=offThe agent is a go.work member but is built standalone against its own go.mod (it vendors the engine via replace ../engine) — so it compiles exactly as an external consumer would.
RELEASES_TOKENThe built-in GITHUB_TOKEN is scoped to the source repo· a fine-grained PAT with contents:write on setu-dist is passed as GITHUB_TOKEN to publish cross-repo.
prerelease: autoA tag ships as a prerelease until vetted — it isn’t marked “latest” automatically.
HomebrewThe cask block is present but disabled until the HOMEBREW_TAP_TOKEN secret exists· re-enabled later.
.goreleaser.yaml — the standalone agent build (excerpt)yaml
builds:
  - id: setu
    dir: agent
    main: ./cmd/setu
    binary: setu
    env: [CGO_ENABLED=0, GOWORK=off]
    goos: [linux, darwin, windows]
    goarch: [amd64, arm64]
    ldflags:
      - -s -w
      - -X github.com/Abhishekkumar2021/setu/agent.Version={{ .Version }}

release:
  github: { owner: Abhishekkumar2021, name: setu-dist }   # PUBLIC dist repo
  prerelease: auto

Local development

Everything runs on one machine. The Go modules are stitched by go.work· the console is pnpm.

go.work — the five modulestext
go 1.26.4

use (
  ./agent
  ./apiserver
  ./engine
  ./packs
  ./worker
)

The dev loop

bring the stack upbash
# one-shot: Postgres + blob + apiserver + worker + web
./scripts/dev.sh

# or the self-contained compose box (Postgres + MinIO + everything)
cd deploy/compose
SETU_SECRET_KEY="$(openssl rand -base64 32)" \
  docker compose -f docker-compose.selfhost.yml up --build

Codegen & the console

DB access is generated by sqlc from SQL queries — after editing a .sql query or a migration, regenerate the typed Go before building. The console is a normal pnpm app.

regenerate + verify the way CI doesbash
# regenerate typed queries after touching SQL (sqlc.yaml at repo root)
sqlc generate

# verify a module exactly like the ci.yml matrix
cd apiserver && go build ./... && go vet ./... && go test ./... -race -count=1

# the console
cd web
pnpm install --frozen-lockfile
pnpm dev          # local console against NEXT_PUBLIC_API_BASE_URL
pnpm lint && pnpm typecheck && pnpm test && pnpm build
Test DB footgun
The DB-backed Go tests wipe tables from SETU_TEST_DATABASE_URL. Point it at a throwaway database — never your dev DB.
The whole flow in one breath
Branch off main, PR, and let ci.yml run three jobs — the Go matrix, the golangci matrix, and the web build. Squash-merge to main and Render (control plane, apiserver-then-worker) and Vercel (console) auto-deploy· docs publish to Pages when docs-site/** changes. Ship the edge runtime separately by pushing a v* tag — GoReleaser builds it GOWORK=off and pushes binaries to the public setu-dist repo. Locally, go.work ties the modules together, sqlc generate regenerates DB code, and pnpm runs the console.