Wiki: Architecture/07-CI-and-Supply-Chain
Architecture/07-CI-and-Supply-Chain
CI and supply chain
Pipeline definition
.gitbay/ci.yml at the pushed commit (internal/ci/ci.go):
| Limit / rule | Value |
|---|---|
| jobs per file | 10 |
| steps per job | 50, each at most 4096 bytes |
| path filters | 50 each for paths and paths-ignore |
| job name | ^[a-z0-9][a-z0-9_-]{0,39}$ |
| image | a restricted reference; it becomes a podman argument, so no whitespace or shell characters (ci.go) |
| triggers | push, merge request, schedule (cron), tags (glob) |
A file that does not parse sets a ci/config failure status on the
commit instead of failing silently.
Build lifecycle
- Queue. The post-receive hook calls
queueJobs(internal/control/build.go). Each job gets aci/<job>status:pendingwhen queued,skippedwhen path filters exclude it, orsuccesscopied from an earlier trusted build of the same tree on the same image (#177, #258). Merge requests from forks are queued against the target repository withtrusted = false. - Claim. A runner calls
runner nextover SSH (build.go). Allowed for arunner-scoped key or an admin; a runner key claims only for repositories it is attached to withrepo runner add. Untrusted builds are claimable only by a runner started with-untrusted(internal/store/builds.go). The claim returns id, repository, job, commit, ref, steps, image, the build's trust, and — for trusted builds only — the repository's secrets (build.go). - Run. The runner clones over SSH into
build-<id>, starts a container and runs each step withpodman exec … sh -c <step>(cmd/gitbay-runner/isolate.go). - Log.
runner log <id>streams stdin into the build row; the server ends the stream if the build is cancelled (build.go). - Result.
runner done <id> success|failure [--step <n>] [--reason <text>]records where a failed build stopped, sets the status, records an event and mails the repository's watchers a log tail on failure (build.go). - Reap. The scheduler fails a running build whose log stream closed
more than 2 minutes ago, or that started more than 90 minutes ago
(
internal/store/builds.go).
Who may do what:
| Action | Requirement |
|---|---|
build list/show/log/jobs |
read on the repository |
build trigger, build cancel |
write on the repository |
repo secret set/remove/list |
admin on the repository |
repo runner add/remove |
admin on the repository |
runner next/log/done |
runner key attached to the repository, or admin |
status set |
write on the repository; ci/* contexts refused (status.go) |
Runner isolation
| Control | Implementation |
|---|---|
| Isolation mode | podman by default; none must be chosen explicitly and logs a warning; an unknown value or missing prerequisites refuse start (isolate.go) |
| Container runtime | rootless podman under the ci-runner user and its subordinate uid range |
| Image | --pull=never; images are built by the operator (deploy/Containerfile.ci) and referenced by tag |
| Workspace | <workdir>/build-<id>, removed after the build; workdir must be 0700 and owned by the runner (main.go) |
| Build home | trusted: <workdir>/trusted-home/<owner>/<name>, one per repository, persistent; untrusted: <workdir>/build-<id>-home, removed with the build (main.go) |
| Secrets | env file 0600 outside the workspace, or --env NAME for multi-line values |
| Resources | per-build cgroup with memory.max and cpu.max written by the runner; unit-level MemoryMax=6G, CPUQuota=300% |
| Network | pasta; a loopback runner's builds run with --no-map-gw (main.go); host limited by user (gitbay-runner-egress.nft) and by build cgroup, trusted or untrusted (gitbay-runner-builds.nft, #260) |
| Shutdown | SIGTERM stops claiming and drains in-flight builds; the unit uses KillMode=mixed |
Integrations
| Integration | Trigger | Security properties |
|---|---|---|
| Webhooks | recorded events | SSRF checks at save and connect time, no redirects, HMAC-SHA256 signature, 5 attempts with exponential backoff, response body capped at 4 KiB (internal/webhook/webhook.go) |
| Mirrors | schedule | address check at save and before each sync, git pinned to the checked addresses, no redirects; token via GIT_ASKPASS script (0700); heads and tags only; 10-minute timeout (internal/mirror/mirror.go) |
| Dependency checks | schedule, opt-in | fixed registry hosts; package names restricted (internal/deps/registry.go) |
The project's own supply chain
| Stage | Control |
|---|---|
| Source | krz/gitbay on the instance itself; signed commits required, fast-forward merges only; require-mr on main |
| Dependencies | 15 direct Go modules (go.mod); pure-Go SQLite (modernc.org/sqlite), no cgo |
| CI | build (build, vet) and test (full suite against real git, ssh, sshd, gpg) on every push; vuln (govulncheck) nightly and before release (.gitbay/ci.yml) |
| Static checks | deploy/audit.sh: vet, govulncheck, short fuzz runs of the pkt-line, commit, signature, PGP key and tokenizer parsers |
| Build | CGO_ENABLED=0 -trimpath -ldflags'-s -w -buildid='= for reproducible binaries; the commit is stamped in (deploy/release.sh, Makefile) |
| Release | binaries gzipped; SHA256SUMS for every archive; a minisign signature of the manifest when the release key is present (optional) |
| Distribution | release assets on the forge; Homebrew formula in krz/homebrew-tap built from the tag; push mirror to GitHub (read-only copy) |
| Deploy | make deploy refuses a dirty tree, then copies, checks config and restarts over operator SSH |
| CI image | built on the host from deploy/Containerfile.ci (golang:1.27-trixie plus git-lfs, gnupg, openssh, python3, sqlite3); tagged, never pulled at build time |