This document provides guidance for AI coding agents (GitHub Copilot, Codex, Claude, etc.) working in this repository.
This repository uses a Nix flake (flake.nix) to provide all required
tools and packages (Go, Node.js, task, golangci-lint, etc.).
Always run bash commands inside the Nix dev shell:
nix develop --command bash -c "<command>"Examples:
nix develop --command bash -c "task fe:build"
nix develop --command bash -c "go test ./..."
nix develop --command bash -c "task be:lint"Do not assume system-installed versions of go, node, npm, task, or
any other tool — they may be absent or wrong. All tools must be invoked through
nix develop.
Kleister API is a backend and embedded Vue frontend for managing Minecraft mod packs and related metadata such as users, groups, mods, packs, versions, and game loader/build information.
The system currently builds a single binary:
| Binary | Entry point | Role |
|---|---|---|
kleister-api |
cmd/kleister-api |
REST API + embedded web UI |
The Vue 3 single-page application is compiled and embedded directly into the binary; no separate web server is needed.
cmd/
kleister-api/ Main binary entry point
config/ Example YAML/JSON config files
frontend/ Vue 3 SPA source
client/ Auto-generated TypeScript API client (do not edit)
components/ Reusable UI components
composables/ Vue composables
feature/ Feature-based page/logic modules
router/ Vue Router configuration
hack/
compose/ Docker Compose files for local development
openapi/
v1.yaml OpenAPI v1 specification (source of truth for the API)
pkg/
api/ API request/response types and handlers
authn/ Authentication (OIDC, local, token)
command/ Cobra sub-command implementations
config/ Configuration structs (mapped from env / file)
frontend/ Embeds the compiled frontend assets into the binary
handler/ HTTP handler wiring
identicon/ Avatar generation helpers
internal/ Remote client integrations for Minecraft ecosystems
manifest/ Manifest and metadata helpers
metrics/ Prometheus metrics
middleware/ HTTP middleware (auth, logging, etc.)
migrations/ Bun ORM database migrations (timestamped Go files)
model/ Domain model structs (bun models)
router/ HTTP router setup (go-chi)
scim/ SCIM 2.0 provisioning endpoint
secret/ Secret encryption helpers
store/ Database access layer (thin wrappers around bun)
templates/ Server-side HTML templates
token/ JWT token issuance and validation
upload/ File upload drivers (local filesystem + S3)
validate/ Input validation helpers (ozzo-validation)
version/ Build-time version, revision, date
storage/ Runtime data (SQLite file, local uploads) — not committed
- Go ≥ 1.25, module
github.com/kleister/kleister-api - HTTP router:
go-chi/chi/v5 - ORM:
uptrace/bun(supports SQLite, MySQL/MariaDB, PostgreSQL) - API layer: generated from
openapi/v1.yamlviaoapi-codegen - Auth: OIDC (
coreos/go-oidc), JWT (golang-jwt/jwt) - Config:
spf13/viper+spf13/cobra - Mocks:
golang/mock(mockgen — files namedmock.go) - Logging:
log/slogwithjeffry-luqman/zlog - Metrics:
prometheus/client_golang - S3:
aws/aws-sdk-go-v2
- Framework: Vue 3 (Composition API +
<script setup>) - Build: Vite 8, TypeScript 5.9,
vue-tsc - State: Pinia
- Routing: Vue Router 5
- UI: Reka UI / Radix Vue, TailwindCSS 4, Lucide icons, FontAwesome
- Tables: TanStack Vue Table
- Forms: vee-validate + Zod
- API client: auto-generated by
@hey-api/openapi-tsfromopenapi/v1.yaml
All commands are driven by go-task (Taskfile.yml).
# Full build (requires frontend to be built first)
task fe:install fe:build be:build
# Individual steps
task fe:install # npm install --ci
task fe:build # vue-tsc && vite build
task be:build # builds the Go binary into bin/
# Code generation (run after changing openapi/v1.yaml or adding //go:generate)
task generate # runs both be:generate and fe:generate
# Linting
task be:fmt # gofmt -s -w
task be:vet # go vet
task be:lint # revive (revive.toml)
task be:golangci # golangci-lint
task fe:lint # eslint ./frontend
task fe:fmt # eslint --fix + prettier
# Testing
task be:test # go test -coverprofile coverage.out ./...
task fe:test # vitest run (single pass)
# Development (hot-reload, run each in its own terminal)
task watch:server # rebuild + run kleister on source changes
task watch:frontend # npm run serve (Vite dev server on :5173)All Go binaries are built with:
CGO_ENABLED=0-tags netgo-ldflags '-s -w -extldflags "-static"'- Version/revision injected via ldflags into
pkg/version
Two generation pipelines exist; always regenerate after changing their inputs.
task be:generate # runs go generate ./...The //go:generate directive in pkg/api/ calls oapi-codegen against
openapi/v1.yaml to produce Go server interfaces and types.
task fe:generate # npm run openapiRuns openapi-ts against openapi/v1.yaml, writing output to
frontend/client/. Do not hand-edit files in frontend/client/.
Configured in config/auth.yml. Supported provider types:
| Provider | Type |
|---|---|
| Microsoft EntraID | OIDC |
| OAuth2 | |
| GitHub | OAuth2 |
| Gitea | OAuth2 |
| GitLab | OAuth2 |
| Keycloak | OIDC |
| Authentik | OIDC |
| Kanidm | OIDC |
- ORM: bun (
uptrace/bun) - Supported drivers:
sqlite3,mysql/mariadb,postgres/postgresql - Migrations live in
pkg/migrations/as timestamped Go files (e.g.,20250116073247_create_templates.go). - To add a migration: create a new
YYYYMMDDHHMMSS_description.gofile inpkg/migrations/and register it inmigrations.go. - Models live in
pkg/model/as bun-tagged structs.
Configuration is loaded by Viper and can be provided via environment variables,
a config file, or CLI flags. Config files live in the config/ directory:
config/config.yml(or.json) — main server settingsconfig/auth.yml(or.json) — OAuth/OIDC provider definitions
Environment variables follow the pattern KLEISTER_API_<SECTION>_<KEY> (uppercased).
Key config sections (pkg/config/config.go):
| Section | Env prefix | Purpose |
|---|---|---|
server |
KLEISTER_API_SERVER_* |
HTTP listen address, TLS, frontend path |
database |
KLEISTER_API_DATABASE_* |
Driver, host, credentials |
upload |
KLEISTER_API_UPLOAD_* |
File upload (local or S3) |
token |
KLEISTER_API_TOKEN_* |
JWT secret and expiry |
encrypt |
KLEISTER_API_ENCRYPT_* |
Encryption passphrase |
log |
KLEISTER_API_LOG_* |
Log level, pretty printing |
scim |
KLEISTER_API_SCIM_* |
SCIM provisioning |
admin |
KLEISTER_API_ADMIN_* |
Initial admin user bootstrap |
metrics |
KLEISTER_API_METRICS_* |
Prometheus metrics endpoint |
runner |
KLEISTER_API_RUNNER_* |
Runner → server connection |
cleanup |
KLEISTER_API_CLEANUP_* |
Background cleanup job |
Secret values support two special prefixes:
file:///path/to/file— read value from filebase64://base64encodedvalue— decode from base64
Minimal development environment variables (see Taskfile.yml watch tasks):
KLEISTER_API_LOG_LEVEL=debug
KLEISTER_API_TOKEN_SECRET=<32-char random string>
KLEISTER_API_ENCRYPT_PASSPHRASE=<32-char random string>task be:test
# or directly:
go test -coverprofile coverage.out ./...- Tests use
stretchr/testify - Mocks are generated with
mockgenand placed in files namedmock.go - In-memory SQLite is used for store tests
Runner: Vitest with the happy-dom environment.
Component utilities: @vue/test-utils
Config: vitest.config.ts at the repo root (separate from vite.config.ts).
task fe:test
# or directly:
npm run test # vitest run (single pass)
npx vitest # watch mode during developmentTests live co-located with the source file they test, using a .test.ts
suffix:
frontend/lib/utils.test.ts
frontend/composables/useBreadcrumbs.test.ts
frontend/composables/useNavigationLinks.test.ts
frontend/feature/auth/store/auth.test.ts
frontend/feature/not-found/views/NotFound.test.ts
Conventions:
- Import test helpers explicitly from
vitest(describe,it,expect,vi,beforeEach, …) — do not rely on globals. - Use
setActivePinia(createPinia())inbeforeEachfor every Pinia store test. - Mock
vue-routerwithvi.mock('vue-router', ...)when testing stores or composables that calluseRouter(). - Mock the generated API client (
frontend/client/) withvi.mock— never make real HTTP calls from tests. - Use
@vue/test-utilsmountfor component tests; assert onwrapper.text()and DOM queries rather than internal component state.
Run before committing:
task be:fmt # gofmt -s -w on all non-mock .go files
task be:vet # go vet
task be:lint # revive with revive.toml
task be:golangci # golangci-lint (comprehensive)revive.toml enforces standard Go conventions. Notable rules:
exported— all exported symbols must have doc commentspackage-comments— disabledvar-naming— skip-package-name-collision-with-go-std = true
task fe:fmt # eslint --fix + prettier
task fe:lint # eslint ./frontendThe OpenAPI spec (openapi/v1.yaml) is the single source of truth.
- Edit
openapi/v1.yaml - Run
task generateto regenerate both Go stubs and the TypeScript client - Implement the new handler in
pkg/handler/orpkg/api/ - Add routes in
pkg/router/ - The frontend
frontend/client/is now updated automatically — use the new typed client methods in your Vue feature modules
- DCO sign-off required on every commit:
Use
Signed-off-by: Your Name <your@email.com>git commit -sto add it automatically. - PRs should be squashed before merge.
- CI must pass (gofmt, golangci-lint, tests) before merging.
- Security issues → email
kleister@webhippie.debefore opening a public issue.
| Workflow | Trigger | Purpose |
|---|---|---|
general.yml |
push / PR | lint, vet, test (Go + TS) |
binaries.yml |
tag / push | goreleaser binary builds |
docker.yml |
tag / push | container image publishing (GHCR) |
openapi.yml |
push | OpenAPI validation |
flake.yml |
push | Nix flake check |
tools.yml |
schedule | dependency updates |
automerge.yml |
PR | auto-merge Dependabot PRs |
cd hack/compose
docker compose -f base.yml -f local.yml upCompose files in hack/compose/:
base.yml— service definitionslocal.yml— local overrides (ports, volumes)build.yml— build-time overridesimage.yml— published image overrides
-
Store layer (
pkg/store/): each domain (users, projects, templates, …) has its own file. Methods receive acontext.Contextas the first argument. Uses.client.handle(bun DB) for queries. -
Models (
pkg/model/): plain structs withbun:andjson:struct tags. IDs are strings (ULIDs). -
Handlers (
pkg/handler/): thin HTTP glue that calls the store and returns JSON. Input validation viapkg/validate/using ozzo-validation. -
Config secrets: never hardcode secrets. Use
config.Value()which handlesfile://andbase64://prefixes transparently. -
Mock files: generated mocks are always in files named
mock.go. Regenerate withtask be:generate. -
Frontend features: follow the
frontend/feature/structure. Use the generatedfrontend/client/for all API calls — never hand-rollfetch. -
Frontend tests — mandatory: every piece of frontend code you generate must be accompanied by a Vitest test file. This applies to:
- Utility functions (
frontend/lib/) → test all exported functions - Pinia stores (
frontend/feature/*/store/) → test all actions and key computed properties; mockvue-routerand the API client - Composables (
frontend/composables/) → test returned values and reactivity; mock any store dependencies withvi.mock - Vue components (
frontend/feature/*/views/,frontend/components/) → mount with@vue/test-utilsand assert on rendered text/DOM
Place the test file next to the source file with a
.test.tssuffix and follow the conventions in the Testing → Frontend section above. Runnpm run testto verify all tests pass before considering a task done. - Utility functions (