LabKit is a single-course ICS lab leaderboard platform with a Go API, Go CLI, Go worker, Vue web app, PostgreSQL, and a real Docker Compose local stack.
apps/api: HTTP API for device authorization, labs, submissions, leaderboard, history, keys, and admin endpoints.apps/worker: background worker for evaluator jobs. The default worker path now runs the real Docker evaluator runtime; the old dev-fake path remains only as an explicit opt-in debug mode.apps/cli: Cobra-based CLI forauth,submit,board,history,nick,track,keys, andrevoke.apps/web: Vue 3 + Vite UI for lab list, leaderboard, auth confirmation, key inventory, and admin lab/queue views.packages/go/*: shared Go packages for manifests, auth/signing, DB access, job queue primitives, evaluator parsing, and domain types.db/: PostgreSQL migrations and sqlc query definitions.deploy/: Docker Compose deployment stack, image build files, migration helper, and Caddy reverse-proxy config.scripts/: local dev helpers, smoke tests, and the happy-path e2e script.
- Go
1.26with toolchaingo1.26.1 - PostgreSQL
18 - Vue
3, Vite6, TypeScript, Pinia, Vitest - Docker Compose + Caddy for local orchestration
- Bash,
curl, andpython3for smoke/e2e scripts
Prerequisites:
- Go
1.26.1 - Node
22+andnpm - Docker with Compose support
bash,curl, andpython3
Start the local stack:
cp deploy/.env.example deploy/.env
cd apps/web && npm ci && cd ../..
bash scripts/dev-up.shDefault local entry points:
- Web:
http://localhost:8080/ - Admin:
http://localhost:8080/admin?token=dev-admin-token - Health:
http://localhost:8080/healthz
The default examples above follow deploy/.env.example. If your local deploy/.env uses a different LABKIT_HTTP_PORT such as 8083, use that port consistently for web, admin, CLI, and auth commands.
Stop the stack:
bash scripts/dev-down.shFor a real domain and school OAuth callback, start from deploy/.env.prod.example:
cp deploy/.env.prod.example deploy/.envThen set at least:
LABKIT_SITE_ADDRESSto your public host, for examplelab.ics.astralis.icuLABKIT_HTTP_PORT=80LABKIT_HTTPS_PORT=443LABKIT_AUTH_PROVIDER=cas_rucLABKIT_OAUTH_CLIENT_IDandLABKIT_OAUTH_CLIENT_SECRETto the values issued by the schoolLABKIT_OAUTH_REDIRECT_URL=https://<your-host>/api/device/verifyLABKIT_OAUTH_DEVICE_AUTH_TTL=15m
The current production auth wiring is provider-based:
cas_rucuses:- authorize URL:
https://cas.ruc.edu.cn/cas/oauth2.0/authorize - token URL:
https://cas.ruc.edu.cn/cas/oauth2.0/accessToken - profile URL:
https://cas.ruc.edu.cn/cas/oauth2.0/user/profiles
- authorize URL:
school_devcenteruses the standard authorize/token pair plus:- user URL:
https://<auth-host>/apis/oauth2/v1/user - profile URL:
https://<auth-host>/apis/oauth2/v1/profile - scope: usually
profileorall
- user URL:
The school-side OAuth application must register the same HTTPS callback URL. Caddy now supports both HTTP and HTTPS in Compose and will terminate TLS directly when LABKIT_SITE_ADDRESS is set to your real domain.
- Open
http://localhost:8080/admin?token=dev-admin-token - Paste a lab manifest into the manifest editor
- Use
Register labto create a new lab - Open
Queuefor that lab to inspect jobs, export grades, or trigger re-evaluation
The admin SPA now covers:
- lab registration
- lab manifest updates
- queue inspection
- grade export
- re-evaluation
Build or run the CLI directly:
go run ./apps/cli/cmd/labkit --helpBuild portable binaries:
bash scripts/build-cli.shOr build a narrower target set:
bash scripts/build-cli.sh linux/amd64 darwin/arm64Common commands:
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 auth --no-encrypt
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 keys
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 revoke 1
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 --lab sorting board
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 --lab sorting submit main.c README.md
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 --lab sorting history
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 --lab sorting nick alice
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 --lab sorting track runtime_msCurrent submit ergonomics:
submitnow does a signed precheck before upload. If the archive hash matches your latest submission, meaning it is the same as your latest submission, interactive TTY mode shows an in-place soft confirm:Entercontinues,nornocancels.- Non-interactive
submitdoes not block on duplicate content. It prints a short warning and continues. submit, authenticatedboard, andhistorynow surface a personal quota summary, for exampleQuota 2 left today · 1/3 used.quota.dailyis enforced against the API's configured quota timezone viaLABKIT_QUOTA_TIMEZONE, which defaults toAsia/Shanghai.- New submissions reserve quota immediately as
pending; evaluator results later settle them tochargedorfree.erroris always free, andquota.freecovers verdicts such asbuild_failedwhen declared by the lab manifest.
CLI config now uses TOML:
- global config:
~/.config/labkit/config.toml - project config: nearest
.labkit/config.toml, searched upward from the current working directory
Global auth state is stored as a per-server keyring:
# ~/.config/labkit/config.toml
default_server_url = "http://localhost:8080"
[servers."http://localhost:8080"]
key_path = "/home/user/.config/labkit/id_ed25519"
key_fingerprint = "SHA256:..."
encrypted = falseRecommended local project config:
# .labkit/config.toml
server_url = "http://localhost:8080"
lab = "sorting"With that file in the repo, you can run the lab-scoped commands without repeating --server-url and --lab:
go run ./apps/cli/cmd/labkit auth
go run ./apps/cli/cmd/labkit board
go run ./apps/cli/cmd/labkit submit main.c README.md
go run ./apps/cli/cmd/labkit history
go run ./apps/cli/cmd/labkit nick alice
go run ./apps/cli/cmd/labkit track runtime_msOverride priority is:
- CLI flags
- environment variables
- local
.labkit/config.toml - global
~/.config/labkit/config.toml - built-in defaults
labkit auth now:
- reuses the existing local key for the same server by default
- rotates that server key when you pass
--rotate-key - asks whether to encrypt the private key in an interactive terminal
- requires
--encryptor--no-encryptin non-interactive usage
Signed CLI requests now use X-LabKit-Key-Fingerprint instead of X-LabKit-Key-ID.
Important local auth note:
labkit authexpects the configured OAuth provider's device flow.- For pure local development without a real OAuth provider, the repo still relies on the dev-only
/api/dev/device/bindshortcut used by tests and e2e. - To expose that shortcut, set
LABKIT_DEV_MODE=trueindeploy/.envbefore starting the stack.
Current local auth workaround:
# terminal A
go run ./apps/cli/cmd/labkit --server-url http://localhost:8080 auth --no-encrypt
# terminal B: fetch the latest device_code created by terminal A
docker compose -f deploy/docker-compose.yml --env-file deploy/.env exec -T postgres \
psql -U labkit -d labkit -Atc \
"select device_code from device_auth_requests order by created_at desc limit 1;"
# terminal B: approve that device through the dev bind endpoint
curl -X POST http://localhost:8080/api/dev/device/bind \
-H 'Content-Type: application/json' \
-d '{"device_code":"<device_code>","student_id":"2026001","device_name":"local-dev"}'After the bind succeeds, the labkit auth process in terminal A will finish and persist the per-server keyring entry.
Further references:
go test ./...cd apps/web && npm testcd apps/web && npm run buildbash db/migrations/schema_smoke_test.shbash scripts/deploy_smoke_test.shbash scripts/e2e-happy-path.sh
- The API is the integration point for browser and CLI clients.
deploy/docker-compose.ymlnow builds and runs the real API, worker, web, migration, PostgreSQL, and Caddy services.- The worker default path uses the real Docker evaluator runtime;
LABKIT_WORKER_DEV_FAKE_EVALUATION=trueremains available only for explicit debugging. - The frontend is intentionally thin and currently focuses on public board visibility, auth confirmation, and admin surfaces rather than full student self-service.
Current baseline:
-
go test ./... -
cd apps/web && npm test -
cd apps/web && npm run build -
bash db/migrations/schema_smoke_test.sh -
bash scripts/deploy_smoke_test.sh -
bash scripts/e2e-happy-path.sh