Contribute
Tobby is developed in the open — GPL-3.0-only, design documents first, every decision in a public ADR. This page gets you from a clone to a merged pull request. The workflow rules live in CONTRIBUTING.md; this page adds the context.
Build from source
Section titled “Build from source”Tooling is pinned in
mise.toml
so every machine — and CI — builds with the same versions: Go 1.25.13,
golangci-lint 2.6.1, gitleaks 8.30.1, and Node 24.18.0 with pnpm 10.33.2
(website build only; the product itself has no Node anywhere):
git clone https://github.com/tobby-fetch/tobby-fetch && cd tobby-fetchmise install # installs the pinned toolchainmise run setup # one-time: activates the git hooks (secret scan on commit)mise run build # produces bin/tobbyOnly Git and the pinned Go are strictly required — mise run build wraps
go build -trimpath -o bin/tobby ./cmd/tobby.
DCO — the sign-off every commit needs
Section titled “DCO — the sign-off every commit needs”This is the canonical explanation; other pages link here.
Every commit must carry a Signed-off-by trailer, certifying under the
Developer Certificate of Origin that
you wrote the change or otherwise have the right to submit it under the
project’s license. Git adds the trailer for you:
git commit -s -m "feat: add per-recipe pre-flight byte estimate"git commit --amend -s --no-edit # forgot on the last commitgit rebase --signoff main # forgot on a whole branchThere is no CLA. The DCO is deliberately lighter: you sign nothing
over and keep the copyright on your contributions
(ADR-0003). Enforcement is
dco.yml —
a plain shell check over git log, no third-party action, checkout pinned
by full SHA like everything else. It blocks the pull request on any
unsigned commit and warns when the sign-off email differs from the commit
author’s. New source files also carry an SPDX header
(GPL-3.0-only) — see CONTRIBUTING.md for the exact lines.
The code, in broad strokes
Section titled “The code, in broad strokes”One module, one binary (cmd/tobby), everything else under internal/ in
small, strictly layered packages — the milestone-4 audit confirmed the
dependency graph has no cycles and interfaces sit on the consumer side.
Rough map:
- Entry and surface —
cli(commands),server(HTTP wiring),ui(server-rendered templates + vendored htmx, no Node),api(/api/v1mirrors),fileserve(FileSet HTTP serving). - Engine —
engine(recipe resolution, synchronization, promotion, cookbook),importer(unit import),blobfetch(streaming transfers, fine-grained resume),relocate(destination naming, ADR-0013),schedule,tasks(persistent queue). - Trust and policy —
sigverify(both cosign layouts, offline),policy(trust roots, scopes, allowlist),auth(accounts, tokens, RBAC),audit,taxonomy(theTBY-*bilingual error catalog). - Substrate —
store(embedded OCI registry storage, GC),config,netx(the single shared outbound transport: proxy, private CAs),logging,metrics,runid,tlsadmin,buildinfo.
Test infrastructure lives in test/ (hermetic topology/ scenarios, the
chromedp browser/ suite, ocilogin/ real-client checks) and crucible/.
Run the pyramid locally
Section titled “Run the pyramid locally”The mise tasks are the same commands CI runs — see tests and proofs for what each gate means:
mise run test # go test -race -count=2 ./... (unit + integration)mise run lint # golangci-lint, strict profile, zero suppressionsmise run coverage # per-package coverage floor (>= 70% per internal package)mise run vuln # govulncheck, reachable known vulnerabilitiesmise run secrets-scan # gitleaks over the full git historymise run test-browser # chromedp suite — uses your installed Chrome, # never downloads onemise run doc # serve this website locallyThe topology scenarios (test/topology/scenario-m*.sh) need Docker and
run anywhere Docker runs. Run mise run lint and mise run test before
opening a pull request; CI enforces both plus the rest of the gates, and
main takes changes only through a reviewed, green pull request.
Replaying the crucible: hardware prerequisites
Section titled “Replaying the crucible: hardware prerequisites”From the crucible README:
- A Linux host with Incus
installed and initialized. Milestone 1–4 scenarios run entirely in
system containers, so any Linux host works, including cloud VMs without
nested virtualization — milestone 1 was validated on a small disposable
cloud instance. Later-milestone scenarios that need per-node kernels
will require KVM (
/dev/kvm). - A ZFS or btrfs storage pool for instant snapshot-based scenario
reset (
dirworks, but resets are slow). - Root on the host — the removable medium is a loop-backed block
device (
losetup), the same privilege bar as Incus administration. - Host tooling from the environment:
go,jq,curl,opensslandhelm. Nothing is downloaded at scenario time, with one deliberate exception:cosignis installed on demand by the m3/m4 scenarios when absent, because real signatures are the point of those scenarios.
macOS and Windows can drive a crucible host remotely over the Incus API,
but never host one. Everything runs in a dedicated Incus project
(tobby-crucible); ./crucible/teardown.sh removes it entirely.