Skip to content

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:

powershell
# 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:

powershell
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:

yaml
krint:
  storage:
    mode: Volume
  port_ranges:
    postgres: 30000-30099
    mysql:    30200-30399
    # ... one range per engine, see the file at the repo root

Bind 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) ​

powershell
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, db krint-dev, user postgres, password d4vpas8w0rd13!!!
  • Keycloak (profile oidc) → http://localhost:8080, admin admin / admin. The krint realm is auto-imported on first start from keycloak/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 ​

powershell
dotnet run --project src/KRINT.API --launch-profile http

The 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 with POST /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:

  1. Applies any pending EF migrations to the dev DB (ApplyMigrations()).
  2. Runs seeders (ApplySeedsAsync(): currently a no-op placeholder).
  3. Talks to the local Docker socket via DockerService (Windows named pipe / Unix socket auto-detected by Docker.DotNet).

5. Frontend: first run ​

powershell
cd src/KRINT.Frontend
bun install

The 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:

powershell
bun run apigen

This 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:

powershell
bun start

Frontend on http://localhost:4200.

6. Tests ​

powershell
dotnet run --project src/KRINT.Tests

TUnit 1.x test runner. Coverage:

  • Unit: services, commands and queries under Commands/, Queries/ and Services/. They use Microsoft.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 driving integration/compose.yml through Ductus.FluentDocker (Postgres + the bundled KRINT image built from this repo's Dockerfile, with local password login). Tests sign in through POST /auth/session/login and call the REST API and SignalR hubs with the bearer token; no browser is involved. Spin the stack up by hand for debugging with docker compose -f integration/compose.yml up -d --build.

Running the integration suite ​

Prereq: Docker running on the host. Then:

powershell
dotnet run --project src/KRINT.Tests

Only the integration tests:

powershell
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:

  1. Image build (~2 min): compose builds src/KRINT.API/Dockerfile (frontend via bun build + .NET API publish) into a single Azure Linux image.
  2. Stack boot (~30 s): Postgres 18 on the compose network, KRINT on localhost:18081 with the admin account seeded from LocalLogin__AdminUserName / LocalLogin__AdminPassword in the compose file. Change the port in both integration/compose.yml and KrintStack.cs if it conflicts.
  3. Tests: each test provisions its own instances through POST /api/Database/provision and deletes them again, so runs are repeatable.

Test files (src/KRINT.Tests/Integration/):

  • KrintStack.cs, KrintSessionHooks.cs: stack lifecycle and the skip switch
  • KrintApi.cs: authenticated client, provision helper (deletes the instance on dispose), query and backup helpers
  • DockerCli.cs, Postgres.cs, HubClient.cs: docker CLI, psql inside an instance, SignalR hubs
  • AuthTests, InstanceLifecycleTests, InnerDatabaseAndUserTests, BrowseTests, BackupTests, ActivityAndDashboardTests, EngineTests, MigrationTests
  • DataIntegrityTests: 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):

powershell
# 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.API

dotnet 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.json references loginTheme: krint. The theme source lives in keycloak/keycloakify/. Build the JAR with bun run build-keycloak-theme inside keycloak/keycloakify/ (Apache Maven must be on PATH); 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 generated typescript-angular client, or any spec-aware tool against /openapi/v1.json. No Swagger UI is mounted.
  • Scalar auth wiring: OAuth2SecuritySchemeTransformer declares an OAuth2 authorization-code scheme on the OpenAPI doc (derived from Oidc:Authority); MapScalarApiReference pre-fills ClientId + PKCE. The krint Keycloak client whitelists http://localhost:5165/scalar/v1/oauth2-redirect.html as a valid redirect URI. If you add another API origin or path, update keycloak/krint-realm.json and re-import the realm (drop the keycloak-data-dev volume).
  • Docker socket: DockerService connects to the local Docker daemon. On Windows this is the Docker Desktop named pipe npipe://./pipe/docker_engine; on Linux it's unix:///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 dependencies
    • KRINT.Application: Mediator queries/commands + DTOs
    • KRINT.Infrastructure: EF DbContext + migrations + Services/ (DockerService, SecretGeneratorService, SecretsVaultService) + Interfaces/
    • KRINT.API: ASP.NET Core entry point, JWT bearer auth, CORS, OpenAPI, controllers, DI wiring via AddDocker() / AddSecrets() / etc.
    • KRINT.Frontend: Angular 21 + Tailwind + Spartan UI, OIDC via angular-auth-oidc-client, typed API client generated by openapi-generator-cli.

Made with care by PianoNic.