Development setup
Toolchain
Section titled “Toolchain”- Go, the version in
go.work(three modules: the hub inapps/kuben, the agent inapps/kuben-agentand what both share inpackages/api; the pinned tools are the module intools/) - Bun 1.4 or later: package manager, JS runtime and test runner. Node is not needed.
- Turborepo runs every task in both languages from one graph (installed by
bun install). - kind for the end-to-end tests, Helm for the chart.
-
Clone and set up.
setupchecks for Go and Bun, installs the JS dependencies and downloads the Go modules.Terminal window git clone https://github.com/Teamtem-dev/kuben.gitcd kubenbun run setup -
Start a PostgreSQL. Kuben keeps its data only there, in dev mode too, and has no default database:
Terminal window docker run -d --name kuben-postgres -e POSTGRES_PASSWORD=kuben -p 5432:5432 postgres:17-alpineexport KUBEN_DATABASE__URL=postgres://postgres:kuben@localhost:5432/postgres -
Run the API and the console. The API listens on
:8080in dev mode (pretty debug logs, insecure cookie) and uses the database inKUBEN_DATABASE__URL; Vite serves the console on:5173and proxies/apito it.Terminal window bun run devThe first start opens the setup page, where you create the admin account (no token is needed on
localhost). -
Before pushing, run what CI runs: gofumpt,
go vetand the static checks ofscripts/go-check.sh(exhaustive switches, NilAway),go test -race, govulncheck, the TypeScript checks, Biome, the generated-file drift check and the web build with its size budget. Tests that need PostgreSQL (KUBEN_TEST_PG_URL) or a Kubernetes API server (envtest) skip locally without them; CI runs them.Terminal window bun run ci -
If you touched controllers or the API, also run the end-to-end suite against a kind cluster:
Terminal window kind create cluster --config deploy/kind.yamlbun run e2e
Repository layout
Section titled “Repository layout”Directoryapps/
Directoryconsole/ React 19 + TanStack, Tailwind v4 (embedded into the binary)
- …
Directorysite/ this website: Astro + Starlight
- …
Directorykuben/ the hub, a Go module: the server and the
kubenCLIDirectorycmd/kuben/ its main package
- …
Directoryinternal/ every hub package (ARCHITECTURE.md lists them and the dependency rules)
Directorycli/ the CLI: flags and output
- …
Directoryserver/
kuben serve: wiring, background work, shutdown- …
Directoryhttpapi/ HTTP routes (generated from openapi.json by ogen), auth, the event stream, embedded console
- …
Directorykube/ controllers, materializer, projections, leader election, rendering
- …
Directoryagentlink/ the hub’s end of the hub–agent link
- …
Directorystore/ PostgreSQL repositories and the migrations
- …
Directorycore/ config, ids, authz, errors and the other pure domain packages
- …
- CONVENTIONS.md (the Go rules every package follows) and history/ (the record of the Rust → Go port)
Directorykuben-agent/ the agent that runs inside member clusters, a Go module
- …
Directorypackages/
Directoryapi/ what the hub and the agent share, a Go module: the kuben.dev CRD types and the AgentLink protocol
- …
Directoryapi-client/ openapi.json (the frozen API contract) and the generated TypeScript types
- …
Directorytypescript-config/ the shared TypeScript settings
- …
Directorytools/ pinned Go tools (gofumpt, ogen, govulncheck, NilAway, …), outside the Go workspace
- …
Directorycharts/kuben/ the Helm chart and the frozen CRD manifest
- …
Directorydeploy/ Dockerfiles and the kind configuration
- …
Directoryscripts/ ci.sh, e2e.sh, budgets, scans, change detection
- …
- go.work the Go workspace
- turbo.json the task graph
Everyday commands
Section titled “Everyday commands”| Command | Does |
|---|---|
bun run build |
the hub with the console embedded (apps/kuben/bin/kuben), the agent (apps/kuben-agent/bin/kuben-agent) and the web bundles |
bun run check |
tsc, astro check and go vet |
bun run lint |
scripts/go-check.sh in each Go module, and Biome |
bun run format |
gofumpt and Biome fixes |
bun run test |
go test -race and bun test |
bun run gen |
regenerate schema.d.ts and the Go API server from the frozen openapi.json, and the CRD types’ DeepCopy |
bun run drift |
regenerate and fail on any difference (what CI does) |
bun turbo run test --filter=kuben |
only the hub’s Go tests (kuben-agent and api are the other Go packages) |
bun turbo run size --filter=kuben |
the hub with the embedded console, checked against the binary budget |
The console
Section titled “The console”- shadcn/ui components.
src/components/ui/holds the components as the shadcn CLI generated them (stylenew-york, on Radix and Base UI), kept byte-comparable with upstream. Changes to them are listed insrc/components/ui/PATCHES.md; aftershadcn add <name> --overwrite, runbun scripts/rtl-codemod.tsand re-apply those patches. Kuben’s own shared pieces are insrc/components/(kit.tsx,data-table.tsx). - Tokens, not colours. Colours, radius and chart colours are CSS variables in
src/styles/theme.css, one block for light and one for dark, used through the semantic utilities (bg-background,text-muted-foreground,bg-primary,text-destructive,border-border, …). A raw palette colour (text-slate-400) would look wrong in one of the themes. - Both directions. Persian is right to left: use logical utilities (
ms-,pe-,start-,border-e) and mark code, commands and addressesdir="ltr". The RTL codemod rewrites the generated components, andsrc/lib/rtl.test.tsfails when one of them still has physical classes. Interface text lives insrc/lib/messages.tsandsrc/lib/messages/; the Persian table has the English table’s type, so a missing translation does not compile. - A strict content security policy. Kuben serves the console with
script-src 'self'; style-src 'self', nothing inline. Libraries that inject<style>elements are handled at build time (vite-plugins/csp-styles.ts), andreact-style-singletonis replaced bysrc/lib/style-singleton.ts, which uses constructed stylesheets.src/lib/csp.test.tsfails on inline scripts, style elements or injected HTML, and the browser tests check for CSP violations on every page. - Self-hosted fonts only. Inter’s Latin subsets ship in
src/assets/fontswith their licence; nothing is loaded from another origin (font-src 'self').
- Dependencies. Go versions live in each module’s
go.mod(the pinned tools intools/go.mod); JS versions only in the rootpackage.jsoncatalog (packages writecatalog:). A new Go dependency needs a reason in the pull request, and govulncheck must stay clean. - The contracts are frozen (ADR-033).
packages/api-client/openapi.json,charts/kuben/crds/kuben.dev_all.yamland the SQL schema are the ones 1.2 shipped. The Go server is generated from the spec (bun turbo run gen --filter=kuben), and the Go tests pin the embedded CRDs to the manifest. Changing a contract means editing that file on purpose, thenbun run genfor the TypeScript types; CI fails on drift, and on a breaking API change without thebreakinglabel. - Every operation has a unique
operationIdin the spec; the TypeScript types, the Go handlers and the audit log are keyed by it. - CRD types stay structural: no internally tagged unions; use one-of structs with optional fields (
Source { image, git }). A test guards this. - Budgets are gates: binary ≤ 45 MiB, image (Kuben and its agent) ≤ 50 MiB, console initial JS ≤ 200 kB, every lazily loaded chunk ≤ 80 kB and all CSS ≤ 25 kB (brotli). Raising a budget needs a written reason in the pull request.
- Commits follow Conventional Commits (
feat(api): …,fix(controller): …). Label pull requests (feature,bug,security,breaking); release notes are grouped by label. - Migrations are PostgreSQL only (ADR-025), in
apps/kuben/internal/store/migrations/. 2.0 adds none, so going back to 1.2 is an image change; the next migration (0035) comes with 2.1.
The review checklist
Section titled “The review checklist”CONTRIBUTING.md lists 18 invariants, each closing a class of bug found in the system Kuben replaces: opaque sessions and no JWT in the browser, Argon2id only, no injection through user strings, no global mutable cluster context, informers instead of polling, readiness only after sync, embedded migrations under a lock, one source of truth per datum, server-side apply with conditions, and more. Reviewers check the ones a change touches. Read it before opening a pull request.