Repository navigation
Configuration
This page documents the settings that matter for getting Egroo running and explains where each one is used.
Egroo follows normal ASP.NET Core configuration precedence:
src/Egroo.Server/appsettings.jsonsrc/Egroo.Server/appsettings.{Environment}.json- environment variables
The web client also has one build-time constant in src/Egroo.UI/Constants/Source.cs.
Required key:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Port=5432;User Id=postgres;Password=yourpassword;Database=egroodb;"
}
}Environment variable form:
ConnectionStrings__DefaultConnection=Server=localhost;Port=5432;User Id=postgres;Password=yourpassword;Database=egroodb;The API will fail on startup if DefaultConnection is missing.
Required key:
{
"Secrets": {
"Jwt": "your-secret-key-here-not-less-than-32-characters"
}
}Environment variable form:
Secrets__Jwt=your-secret-key-here-not-less-than-32-charactersThe API uses this to sign and validate JWTs for both REST and SignalR authentication.
Required keys:
{
"Encryption": {
"Key": "12345678901234567890123456789012",
"IV": "1234567890123456"
}
}Rules:
-
Keymust be exactly 32 characters -
IVmust be exactly 16 characters
These values are used by the server-side EncryptionService for protected encrypted records and transport-related encrypted storage.
Important distinction:
- these settings are not the client private keys used for end-to-end message decryption
- end-to-end message keys are generated per device in the client and only the public key is published back to the server
Egroo's message delivery can use a device-scoped end-to-end encryption flow:
- the client generates a key pair locally
- the client publishes
EncryptionPublicKeyandEncryptionKeyIdfor the signed-in user - message payloads are encrypted per recipient using the published public key
- the recipient device decrypts locally with its private key
Operational notes:
- the private key is not meant to live in server configuration
- clearing browser storage can remove the local private key for that device
- if a device key is missing or stale, the client may need to regenerate and republish it from the app settings
Voice calling works without extra configuration for local experimentation, but production deployments should provide relay-capable ICE servers.
Example configuration:
{
"VoiceCall": {
"CloudflareTurn": {
"TokenId": "your-cloudflare-turn-token-id",
"ApiToken": "your-cloudflare-turn-api-token"
},
"IceServers": [
{
"Urls": [
"stun:stun.cloudflare.com:3478",
"turn:turn.example.com:3478?transport=udp",
"turns:turn.example.com:5349?transport=tcp"
],
"Username": "voice-user",
"Credential": "voice-password"
}
]
}
}Environment variable form:
VoiceCall__CloudflareTurn__TokenId=your-cloudflare-turn-token-id
VoiceCall__CloudflareTurn__ApiToken=your-cloudflare-turn-api-token
VoiceCall__IceServers__0__Urls__0=stun:stun.cloudflare.com:3478
VoiceCall__IceServers__0__Urls__1=turn:turn.example.com:3478?transport=udp
VoiceCall__IceServers__0__Urls__2=turns:turn.example.com:5349?transport=tcp
VoiceCall__IceServers__0__Username=voice-user
VoiceCall__IceServers__0__Credential=voice-passwordRuntime behavior:
- the client requests
/api/v1/Voice/configbefore joining a voice call - the server first tries to generate temporary Cloudflare TURN credentials when
VoiceCall:CloudflareTurnis configured - if Cloudflare TURN is unavailable, the server falls back to
VoiceCall:IceServers - if nothing is configured, the server returns a default STUN server so local development still works
- on production-like hosts, when TURN is available, the browser forces relay transport instead of trying direct candidates first
- without TURN, calls may work on simple networks but can fail across NAT or firewall boundaries
Optional production setting:
{
"Api": {
"AllowedOrigins": [
"https://chat.example.com",
"https://app.example.com"
]
}
}Environment variable form:
Api__AllowedOrigins__0=https://chat.example.com
Api__AllowedOrigins__1=https://app.example.comImportant behavior:
- in debug builds,
Program.csexplicitly disables origin restrictions and allows any origin - this means
AllowedOriginsmatters most outside debug builds
Base logging is configured in appsettings.json through ASP.NET Core logging.
Minimal example:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}src/Egroo.Server/appsettings.Development.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Port=5432;User Id=egroo_dev_user;Password=dev_password;Database=egroo_dev;"
},
"Secrets": {
"Jwt": "development-jwt-secret-at-least-32-characters"
},
"Encryption": {
"Key": "12345678901234567890123456789012",
"IV": "1234567890123456"
}
}The web client uses src/Egroo.UI/Constants/Source.cs:
#if DEBUG
public const string ConnectionURL = "http://localhost:5175/";
#else
public const string ConnectionURL = "https://api.egroo.org/";
#endifWhat this means:
- local development already points to the local API
- a custom production deployment usually needs the release value changed before build and publish
The SignalR hub URL is derived from the base URL plus the hub name:
public const string HubName = "chathub";
public const string HubConnectionURL = ConnectionURL + HubName;- JWT is accepted in the
Authorizationheader for REST endpoints - the SignalR hub also accepts JWT through the
access_tokenquery string -
GET /api/v1/Voice/configis anonymous because the browser needs ICE server details before call setup - rate limiting is applied under the
Apipolicy at 100 requests per minute - the API automatically applies pending EF Core migrations on startup
- the client release build still uses the API base URL compiled into
src/Egroo.UI/Constants/Source.cs
{
"ConnectionStrings": {
"DefaultConnection": "Server=db;Port=5432;User Id=egroo;Password=strong-password;Database=egroo;"
},
"Secrets": {
"Jwt": "replace-with-a-real-secret-at-least-32-characters"
},
"Encryption": {
"Key": "replace-with-32-char-secret-value",
"IV": "replace-with-16-char"
},
"VoiceCall": {
"CloudflareTurn": {
"TokenId": "replace-with-your-turn-token-id",
"ApiToken": "replace-with-your-turn-api-token"
}
},
"Api": {
"AllowedOrigins": [
"https://chat.example.com"
]
}
}Before blaming the code, confirm these first:
-
DefaultConnectionpoints to a reachable PostgreSQL instance. -
Secrets:Jwtexists and is long enough. -
Encryption:Keyis 32 characters andEncryption:IVis 16 characters. - The client is pointing at the correct API base URL for the environment you are running.
- Production origins are explicitly set if the API is not running in debug.
- If encrypted messages cannot be read, verify the device has the current private key and published key id.
- For production voice calling, verify
VoiceCallreturns at least one relay-capable TURN URL.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Api": {
"AllowedOrigins": [
"https://staging.yourchat.example.com"
]
}
}{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Api": {
"AllowedOrigins": [
"https://yourchat.example.com"
]
}
}Validate your configuration:
-
Start the API Server (database migrations run automatically on startup):
cd src/Egroo.Server dotnet run -
Verify the API is running (Swagger UI available in development):
http://localhost:5175/swagger -
Test authentication:
curl -X POST http://localhost:5175/api/v1/Auth/signup \ -H "Content-Type: application/json" \ -d '{"username":"testuser","password":"TestPass123!"}'
-
Check CORS settings (verify allowed origins accept requests from your client origin).
Common configuration issues and solutions can be found in the Troubleshooting Guide.