CommandBlock 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 and Keycloak)
- 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)
- 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 CommandBlock.API project has a UserSecretsId configured.
Set them once:
# DB connection
dotnet user-secrets --project src/CommandBlock.API set "ConnectionStrings:CommandBlockDatabase" "Host=localhost;Port=5434;Database=commandblock-dev;Username=postgres;Password=d4vpas8w0rd13!!!"
# OIDC (consumed by /api/App, which the frontend reads at startup to configure auth)
dotnet user-secrets --project src/CommandBlock.API set "Oidc:Authority" "http://localhost:8080/realms/commandblock"
dotnet user-secrets --project src/CommandBlock.API set "Oidc:RequireHttpsMetadata" "false"
dotnet user-secrets --project src/CommandBlock.API set "Oidc:ClientId" "commandblock"
dotnet user-secrets --project src/CommandBlock.API set "Oidc:RedirectUri" "http://localhost:4200/"
dotnet user-secrets --project src/CommandBlock.API set "Oidc:PostLogoutRedirectUri" "http://localhost:4200/"
dotnet user-secrets --project src/CommandBlock.API set "Oidc:Scope" "openid profile email roles"
# CORS: origins allowed to call the API
dotnet user-secrets --project src/CommandBlock.API set "Cors:AllowedOrigins:0" "http://localhost:4200"Verify with dotnet user-secrets list --project src/CommandBlock.API.
INFO
appsettings.json and appsettings.Development.json only carry ASP.NET framework defaults (logging, allowed hosts). Application config goes in user-secrets.
2. Application config: commandblock.yaml
Non-secret app config lives in commandblock.yaml at the repo root. The API loads it via services.AddCommandBlockConfig(env) (see src/CommandBlock.API/Extensions/CommandBlockConfigExtensions.cs), which walks up from the content root to find the file. Override the path with the CommandBlock_CONFIG environment variable.
Currently used to declare where server worlds persist on disk:
commandblock:
storage:
mode: HostFolder # or Volume
host_path: /data/serversBind into a handler via IOptions<CommandBlockOptions> (in CommandBlock.Application/Options/).
3. Dev infrastructure (Postgres + Keycloak)
docker compose -f compose.dev.yml up -d- Postgres →
localhost:5434, dbcommandblock-dev, userpostgres, passwordd4vpas8w0rd13!!! - Keycloak →
http://localhost:8080, adminadmin/admin. Thecommandblockrealm is auto-imported on first start fromkeycloak/commandblock-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/CommandBlock.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: click Authenticate to redirect to Keycloak (authorization code + PKCE against thecommandblockrealm/client). On return, Scalar attaches the bearer token to every request you fire from the UI.
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/CommandBlock.Frontend
bun installWith the backend running, generate the typed API client:
bun run apigenThis reads openapitools.json, fetches http://localhost:5165/openapi/v1.json, and writes the typescript-angular client into src/app/api/. Rerun any time the backend's contract changes.
Then start the dev server:
bun startFrontend on http://localhost:4200.
6. Tests
There is no test project right now - the database-era unit and E2E suites were removed in the Minecraft rewrite. Add one (TUnit works well here) as features grow; server create/route/backup are good first candidates.
7. EF migrations
CommandBlock can store its own data in SQLite (default) or PostgreSQL, selected with Database__Provider (Sqlite or Postgres). The connection string is ConnectionStrings__CommandBlockDatabase; for SQLite it defaults to Data Source=commandblock.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/CommandBlock.Infrastructure/Migrations/ - SQLite →
src/CommandBlock.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/CommandBlock.Infrastructure -s src/CommandBlock.API
# SQLite
$env:Database__Provider="Sqlite"
dotnet ef migrations add <Name> -p src/CommandBlock.Infrastructure.Migrations.Sqlite -s src/CommandBlock.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/commandblock-realm.jsonreferencesloginTheme: commandblock. 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. ThecommandblockKeycloak client whitelistshttp://localhost:5165/scalar/v1/oauth2-redirect.htmlas a valid redirect URI. If you add another API origin or path, updatekeycloak/commandblock-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:
CommandBlock.Domain: entities (ServerInstance,BackupEntry,ActivityEntry,BaseEntity), no dependenciesCommandBlock.Application: Mediator queries/commands + DTOsCommandBlock.Infrastructure: EF DbContext + migrations +Services/(DockerService, S3BackupStorage, ModrinthClient, ActivityLogger) +Interfaces/CommandBlock.API: ASP.NET Core entry point, JWT bearer auth, CORS, OpenAPI, controllers, the Minecraft router, DI wiring viaAddDocker()/AddActivityLog()/ etc.CommandBlock.Frontend: Angular 21 + Tailwind + Spartan UI, OIDC viaangular-auth-oidc-client, typed API client generated by openapi-generator-cli.