KRINT Developer Setup
This is what a fresh checkout needs to be productive locally.
Prerequisites
- .NET 10 SDK (the API targets
net10.0) - Docker + Docker Compose (for Postgres; Keycloak only if you test the OIDC path)
- Node.js 20+ and Bun 1.3+ (the frontend uses bun as its package manager)
- Apache Maven (only if you want to rebuild the Keycloak theme JAR, see Notes)
- Java 11+ (only for
bun run apigen, which runs the OpenAPI generator; the generated client is committed, so most changes never need it) - dotnet-ef global tool (only if you'll add EF migrations)
1. Backend secrets
Secrets live in dotnet user-secrets: they are never committed and never written to appsettings.json. The KRINT.API project has a UserSecretsId configured.
Set them once. With no Oidc:Authority the API runs in local login mode, so three values are enough:
# DB connection
dotnet user-secrets --project src/KRINT.API set "ConnectionStrings:KrintDatabase" "Host=localhost;Port=5434;Database=krint-dev;Username=postgres;Password=d4vpas8w0rd13!!!"
# CORS: the dev SPA on :4200 calls the API on :5165
dotnet user-secrets --project src/KRINT.API set "Cors:AllowedOrigins:0" "http://localhost:4200"
# Vault master key, used by SecretsVaultService to AES-GCM encrypt stored secrets at rest.
# 32 raw bytes, base64-encoded. Generate fresh on bash via `openssl rand -base64 32`.
# Optional: without it the API generates one into src/KRINT.API/data/vault.key (git-ignored).
dotnet user-secrets --project src/KRINT.API set "Vault:MasterKey" "$(openssl rand -base64 32)"
# Optional: a known password for the first local account (otherwise it is printed once in the API log).
dotnet user-secrets --project src/KRINT.API set "LocalLogin:AdminPassword" "dev-admin-password"Verify with dotnet user-secrets list --project src/KRINT.API.
To develop against the OIDC path instead, start Keycloak (docker compose -f compose.dev.yml --profile oidc up -d) and add:
dotnet user-secrets --project src/KRINT.API set "Oidc:Authority" "http://localhost:8080/realms/krint"
dotnet user-secrets --project src/KRINT.API set "Oidc:RequireHttpsMetadata" "false"
dotnet user-secrets --project src/KRINT.API set "Oidc:ClientId" "krint"
dotnet user-secrets --project src/KRINT.API set "Oidc:RedirectUri" "http://localhost:4200/"
dotnet user-secrets --project src/KRINT.API set "Oidc:PostLogoutRedirectUri" "http://localhost:4200/"Remove Oidc:Authority again to go back to local login.
INFO
appsettings.json and appsettings.Development.json only carry ASP.NET framework defaults (logging, allowed hosts). Application config goes in user-secrets.
WARNING
Don't rotate Vault:MasterKey while you have encrypted secrets in the DB: anything written with the old key becomes unreadable. There's no key-rotation flow yet.
2. Application config: krint.yaml
Non-secret app config lives in krint.yaml at the repo root. The API loads it via services.AddKrintConfig(env) (see src/KRINT.API/Extensions/KrintConfigExtensions.cs), which walks up from the content root to find the file. Override the path with the KRINT_CONFIG environment variable.
It declares the host port range each engine may bind (one entry per engine; the repo's krint.yaml lists all of them), the storage mode for instance data, declared nodes and an optional instances_file:
krint:
storage:
mode: Volume
port_ranges:
postgres: 30000-30099
mysql: 30200-30399
# ... one range per engine, see the file at the repo rootBind into a handler via IOptions<KrintOptions> (in KRINT.Application/Options/). The file is read once at startup; restart the API after editing it.
3. Dev infrastructure (Postgres, optionally Keycloak)
docker compose -f compose.dev.yml up -d # Postgres only
docker compose -f compose.dev.yml --profile oidc up -d # Postgres + Keycloak, for the OIDC path- Postgres →
localhost:5434, dbkrint-dev, userpostgres, passwordd4vpas8w0rd13!!! - Keycloak (profile
oidc) →http://localhost:8080, adminadmin/admin. Thekrintrealm is auto-imported on first start fromkeycloak/krint-realm.json.
Stop with docker compose -f compose.dev.yml down. Volumes (postgres-data-dev, keycloak-data-dev) persist across restarts; drop them with -v if you want a clean slate.
4. Backend
dotnet run --project src/KRINT.API --launch-profile httpThe API binds to http://localhost:5165. In Development:
- OpenAPI document:
http://localhost:5165/openapi/v1.json(AllowAnonymous) - Scalar API reference UI:
http://localhost:5165/scalar/v1. In local login mode, get a token withPOST /auth/login({"identifier":"admin","password":"…"}) and paste it into Authenticate; with Keycloak configured, Authenticate redirects there (authorization code + PKCE) and Scalar attaches the bearer token to every request.
On startup the API:
- Applies any pending EF migrations to the dev DB (
ApplyMigrations()). - Runs seeders (
ApplySeedsAsync(): currently a no-op placeholder). - Talks to the local Docker socket via
DockerService(Windows named pipe / Unix socket auto-detected by Docker.DotNet).
5. Frontend: first run
cd src/KRINT.Frontend
bun installThe typed API client under src/app/api/ is committed, so a fresh checkout builds without generating anything. Regenerate it only after changing the backend's contract (a controller, a DTO), with the backend running:
bun run apigenThis reads openapitools.json, fetches http://localhost:5165/openapi/v1.json, and writes the typescript-angular client into src/app/api/. It needs a Java runtime on PATH.
Then start the dev server:
bun startFrontend on http://localhost:4200.
6. Tests
dotnet run --project src/KRINT.TestsTUnit 1.x test runner. Coverage:
- Unit: services, commands and queries under
Commands/,Queries/andServices/. They useMicrosoft.EntityFrameworkCore.InMemory: no Postgres or Docker required. - Integration: API-level tests in
src/KRINT.Tests/Integration/. The suite boots its own ephemeral stack each session by drivingintegration/compose.ymlthrough Ductus.FluentDocker (Postgres + the bundled KRINT image built from this repo's Dockerfile, with local password login). Tests sign in throughPOST /auth/session/loginand call the REST API and SignalR hubs with the bearer token; no browser is involved. Spin the stack up by hand for debugging withdocker compose -f integration/compose.yml up -d --build.
Running the integration suite
Prereq: Docker running on the host. Then:
dotnet run --project src/KRINT.TestsOnly the integration tests:
dotnet run --project src/KRINT.Tests -- --treenode-filter "/*/KRINT.Tests.Integration/*/*"Only the unit tests (no Docker): set KRINT_SKIP_INTEGRATION=1 (the older KRINT_SKIP_E2E=1 still works) and the integration tests skip themselves.
What happens on first run:
- Image build (~2 min): compose builds
src/KRINT.API/Dockerfile(frontend viabun build+ .NET API publish) into a single Azure Linux image. - Stack boot (~30 s): Postgres 18 on the compose network, KRINT on
localhost:18081with the admin account seeded fromLocalLogin__AdminUserName/LocalLogin__AdminPasswordin the compose file. Change the port in bothintegration/compose.ymlandKrintStack.csif it conflicts. - Tests: each test provisions its own instances through
POST /api/Database/provisionand deletes them again, so runs are repeatable.
Test files (src/KRINT.Tests/Integration/):
KrintStack.cs,KrintSessionHooks.cs: stack lifecycle and the skip switchKrintApi.cs: authenticated client, provision helper (deletes the instance on dispose), query and backup helpersDockerCli.cs,Postgres.cs,HubClient.cs: docker CLI, psql inside an instance, SignalR hubsAuthTests,InstanceLifecycleTests,InnerDatabaseAndUserTests,BrowseTests,BackupTests,ActivityAndDashboardTests,EngineTests,MigrationTestsDataIntegrityTests: Postgres backup/restore, visibility change and 17 to 18 upgrade keep rows, extra database and user; Redis keys survive restore and a container restart; SQL Server restore brings back a dropped table.
7. EF migrations
KRINT can store its own data in SQLite (default) or PostgreSQL, selected with Database__Provider (Sqlite or Postgres). The connection string is ConnectionStrings__KrintDatabase; for SQLite it defaults to Data Source=krint.db.
Because EF Core can't keep two providers' migrations in one assembly, there are two migration sets, both auto-applied at API startup via ApplyMigrations() for whichever provider is active:
- Postgres →
src/KRINT.Infrastructure/Migrations/ - SQLite →
src/KRINT.Infrastructure.Migrations.Sqlite/Migrations/
After changing entities, add the migration to both sets (the EF tooling reads the target provider from Database__Provider):
# Postgres
$env:Database__Provider="Postgres"
dotnet ef migrations add <Name> -p src/KRINT.Infrastructure -s src/KRINT.API
# SQLite
$env:Database__Provider="Sqlite"
dotnet ef migrations add <Name> -p src/KRINT.Infrastructure.Migrations.Sqlite -s src/KRINT.APIdotnet ef database update is not needed for local dev. dotnet run does it on startup. Run it manually only if you want to apply migrations without booting the API.
Notes
- Keycloak theme:
keycloak/krint-realm.jsonreferencesloginTheme: krint. The theme source lives inkeycloak/keycloakify/. Build the JAR withbun run build-keycloak-themeinsidekeycloak/keycloakify/(Apache Maven must be onPATH); the dev compose mounts the JAR into Keycloak. Until you build it, dev Keycloak falls back to the default theme. - API exploration: use the Scalar UI at
/scalar/v1, the generatedtypescript-angularclient, or any spec-aware tool against/openapi/v1.json. No Swagger UI is mounted. - Scalar auth wiring:
OAuth2SecuritySchemeTransformerdeclares an OAuth2 authorization-code scheme on the OpenAPI doc (derived fromOidc:Authority);MapScalarApiReferencepre-fillsClientId+ PKCE. ThekrintKeycloak client whitelistshttp://localhost:5165/scalar/v1/oauth2-redirect.htmlas a valid redirect URI. If you add another API origin or path, updatekeycloak/krint-realm.jsonand re-import the realm (drop thekeycloak-data-devvolume). - Docker socket:
DockerServiceconnects to the local Docker daemon. On Windows this is the Docker Desktop named pipenpipe://./pipe/docker_engine; on Linux it'sunix:///var/run/docker.sock. When the API runs inside a container (prod compose), the host's socket must be mounted into it (/var/run/docker.sock:/var/run/docker.sock). - Architecture quick map:
KRINT.Domain: entities (Secret,BaseEntity), no dependenciesKRINT.Application: Mediator queries/commands + DTOsKRINT.Infrastructure: EF DbContext + migrations +Services/(DockerService, SecretGeneratorService, SecretsVaultService) +Interfaces/KRINT.API: ASP.NET Core entry point, JWT bearer auth, CORS, OpenAPI, controllers, DI wiring viaAddDocker()/AddSecrets()/ etc.KRINT.Frontend: Angular 21 + Tailwind + Spartan UI, OIDC viaangular-auth-oidc-client, typed API client generated by openapi-generator-cli.