Developing locally
Running Modgud from source for development. For the "just want it running" path, use the Docker quickstart instead — this page is for contributors who edit the code.
Prerequisites
- .NET 10 SDK
- Node.js 22+ and pnpm
- Docker (for PostgreSQL via container)
Bring up the backend
# Start PostgreSQL (one-off)
docker run --name cocoar-postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:17-alpine
# Create master DB (one-off — the backend can do this on boot, but doing it here survives container restarts more cleanly)
docker exec cocoar-postgres psql -U postgres -c "CREATE DATABASE modgud;"
# Build the backend
cd src/dotnet
dotnet build
# Start the backend (port 9099 in dev — see data/configuration.json)
cd Modgud.Api
ASPNETCORE_ENVIRONMENT=Development dotnet run --no-launch-profileOn first start the bootstrap path runs:
- Apply master DB schema (
realms.mt_tenant_databasesis created) - Register the system tenant in the tenancy table
- Apply the system tenant schema
- Seed the system realm document (flagged
IsControlPlane = true) - Seed 5 default OAuth scopes + the Internal login provider into the system tenant DB
- Seed the
modgudandcontrol-planeapps into the system tenant DB - Warm up RealmCache
Then Kestrel starts listening on http://localhost:9099.
Bring up the frontend
In a second terminal:
cd src/frontend-vue
pnpm install
pnpm devThe Vite dev server runs on http://localhost:4300 and proxies /api/*, /connect/*, /.well-known/*, /signalr/* to http://localhost:9099.
Multi-realm dev
The Vite proxy uses changeOrigin: false so the original Host header reaches the backend. A tenant realm with Domains: ["acme.localhost"] is then reachable at http://acme.localhost:4300/ during development. The single-realm fallback in RealmCache ensures the default localhost URL still works on a fresh DB with only the system realm.
Most desktop OSes resolve *.localhost → 127.0.0.1 automatically (Windows since Vista, macOS, glibc-based Linux with nss-myhostname). On Linux distros that don't, add the entries you need to /etc/hosts:
127.0.0.1 acme.localhost beta.localhostThis is purely a dev-loop concern. In a real deployment the tenant hostnames are real DNS names served by the Docker container behind your reverse proxy.
Create the first admin
There is no anonymous setup wizard. Run the recovery CLI in direct mode:
cd src/dotnet/Modgud.Api
dotnet run --no-launch-profile -- recover bootstrap-admin \
--email admin@example.com \
--username admin \
--password 'ABC12abc!'The CLI atomically creates:
- An
ApplicationUserwith the given password (hashed with the configured Identity policy) - The three default
PermissionRoles — System Admin, User Manager, Viewer - A
Group"Administratoren" with you as the only member, the System Admin role attached,BoundTo: ["*"](active in every app)
Sign in at http://localhost:4300/ with admin / ABC12abc!. You hold realm:admin, so the sidebar shows everything.
Same identity, different scenarios
- Local dev: direct mode with a known password (above) is fastest.
- Handing off to a customer: drop
--passwordto use invite mode — the CLI prints a magic-link URL on stdout, the recipient sets their own password. - Provisioning additional tenant realms (once you have an admin in the Control-Plane realm): use
POST /api/admin/realmswith anInitialAdminpayload — see First-time setup for the full decision tree.
Seed demo data (optional)
node scripts/seed-demo.mjsLogs in as admin / ABC12abc! (override with --user= / --password=), then POSTs the demo dataset (extra users, granular roles, auto-membership groups, OAuth clients, scopes, an API, a sample external login provider) through the regular admin API. Idempotent — re-runs only create what's missing. Generated OAuth client secrets are printed at the end.
Run the tests
cd src/dotnet
# All tests (needs Docker for Testcontainers)
dotnet test
# A single test
dotnet test --filter "FullyQualifiedName~AuthenticationTests"The tests use Testcontainers and pull up a PostgreSQL container on demand. Per-test-class DB isolation, four parallel xUnit collections.
E2E tests (Playwright)
cd src/frontend-vue
pnpm test:e2eRequires the backend + frontend to be running. ENV variables for the test credentials:
E2E_ADMIN_USER=admin
E2E_ADMIN_PASSWORD=ABC12abc!Wolverine codegen
Wolverine generates handler code on boot. With the default config (TypeLoadMode.Auto), the code is written into an Internal/Generated/ folder on first start and loaded directly on the next boot — no Roslyn compilation at runtime.
If you change handlers or aggregates, delete the Generated folder and restart, or have Wolverine pre-generate:
cd src/dotnet/Modgud.Api
dotnet run --no-launch-profile -- codegen writeRecovery CLI
When all admin accounts are locked out or a projection is corrupted:
cd src/dotnet/Modgud.Api
dotnet run --no-launch-profile -- recover list
dotnet run --no-launch-profile -- recover reset-2fa <username>
dotnet run --no-launch-profile -- recover set-email <username> <email>
dotnet run --no-launch-profile -- recover magic-link <username>
dotnet run --no-launch-profile -- recover rebuild-projectionsIn the container: docker exec modgud dotnet Modgud.Api.dll recover list.
Dev endpoints
In development mode, additional endpoints are mounted under /api/dev/* (see Modgud.Api.Features.Dev):
- Email inspector (shows sent mails without SMTP)
- MFA reset for test users
- General test helpers for E2E
In production they are not mounted.