# Configuration Configuration is built on the standard `Microsoft.Extensions.Configuration` pipeline. Both the WebApp and the Worker use the same override chain. Later providers win: ```text appsettings.json typed defaults, checked in -> .env Docker/default developer stack values -> .env.local host-local overrides, only outside containers -> process environment CI/CD, Docker Compose, shell variables -> Azure Key Vault production secrets, optional -> HarpyxConfigurationComposer derived composite keys, if still empty ``` The wiring happens in `WebApplicationBuilderExtensions.ConfigureHarpyxHost` and `HostApplicationBuilderExtensions.ConfigureHarpyxHost`. See [src/Harpyx.WebApp/Extensions/WebApplicationBuilderExtensions.cs](../src/Harpyx.WebApp/Extensions/WebApplicationBuilderExtensions.cs) and [src/Harpyx.Worker/Extensions/HostApplicationBuilderExtensions.cs](../src/Harpyx.Worker/Extensions/HostApplicationBuilderExtensions.cs). ## Naming Convention All layers target the same logical key path as `appsettings.json`: | Layer | Separator | Example | | --------------------- | --------- | ----------------------------------- | | `appsettings.json` | `:` | `Database:Host`, `EntraId:TenantId` | | Environment variables | `__` | `Database__Host`, `EntraId__TenantId` | | Azure Key Vault | `--` | `Database--Host`, `EntraId--TenantId` | There are no `${...}` placeholders in `appsettings.json`. Environment values directly override the same appsettings key through the native `__` to `:` mapping performed by `AddEnvironmentVariables()`. Azure Key Vault uses the native `--` to `:` mapping. `IConfiguration` lookups are case-insensitive, but Harpyx standardizes on PascalCase environment names so they visually mirror the JSON structure. ### Why `--` and `__`? - Azure Key Vault secret names can contain dashes but not underscores, so the official convention maps `:` to `--`. - POSIX environment variables can contain underscores but not dashes, so .NET maps `:` to `__`. ## Appsettings `appsettings.json` contains structure and safe typed defaults. Environment-specific addresses and secrets are intentionally blank there and supplied by `.env`, `.env.local`, process environment variables, or Key Vault. This keeps appsettings portable: ```json "Database": { "Host": "", "Port": 0, "Name": "", "Username": "", "Password": "", "Encrypt": true, "TrustServerCertificate": true } ``` The actual values live in environment-shaped files: ```dotenv Database__Host=sqlserver Database__Port=1433 Database__Encrypt=true ``` `Database:ApplyMigrationsOnStartup` is intentionally omitted from `appsettings.json`. When omitted, the WebApp applies migrations automatically in `Development` and leaves them disabled in other environments. Production deployments should still set `Database__ApplyMigrationsOnStartup=false` explicitly and apply migrations out-of-band. ## Seed Options `SeedOptions` controls bootstrap data that must exist before an administrator can use the UI. Admin users are configured as an array under `SeedOptions:AdminUsers`: ```json "SeedOptions": { "AdminUsers": [ { "Provider": "EntraId", "UniqueId": "8f8c9e0d-56af-4fef-aef6-0867b95900a3", "Email": "azure@ryadel.com" }, { "Provider": "Google", "UniqueId": "google-sub-value", "Email": "admin@example.com" } ] } ``` The environment-variable form uses normal .NET array binding: ```dotenv SeedOptions__AdminUsers__0__Provider=EntraId SeedOptions__AdminUsers__0__UniqueId=8f8c9e0d-56af-4fef-aef6-0867b95900a3 SeedOptions__AdminUsers__0__Email=azure@ryadel.com SeedOptions__AdminUsers__1__Provider=Google SeedOptions__AdminUsers__1__UniqueId=google-sub-value SeedOptions__AdminUsers__1__Email=admin@example.com ``` Each admin entry has three fields: | Field | Purpose | | ---------- | --------------------------------------------------------------------------------------------- | | `Provider` | Identity provider family. `EntraId` maps `UniqueId` to `User.ObjectId`; `Google` maps it to `User.SubjectId`. | | `UniqueId` | Stable provider-specific identifier. For Entra ID this is the object id; for Google this is the `sub` claim. | | `Email` | Human-readable address and fallback lookup key. It is not the primary identity anchor. | At WebApp startup, `InitializeHarpyxDatabaseAndSeedAsync` reads `SeedOptions:AdminUsers`, creates missing users, and ensures configured seed users are active platform admins. The database still stores provider-specific identifiers separately as `ObjectId` and `SubjectId`; `UniqueId` is only the provider-neutral configuration name. Legacy `SeedAdmin:ObjectId` / `SeedAdmin:Email` values are still accepted as an Entra ID admin entry, but new configuration should use `SeedOptions:AdminUsers`. ## Composite Values Two keys are derived at startup from simpler components by `HarpyxConfigurationComposer`: | Composite key | Derived from | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `ConnectionStrings:Harpyx` | `Database:Host`, `Database:Port`, `Database:Name`, `Database:Username`, `Database:Password`, `Database:Encrypt`, `Database:TrustServerCertificate` | | `EntraId:Authority` | `EntraId:Instance`, `EntraId:TenantId` | `HarpyxConfigurationComposer.ComposeDerivedValues` runs after environment variables and Key Vault have been added, and writes a composite key only when that key is still empty. Administrators can bypass composition by supplying a full value such as `ConnectionStrings__Harpyx`. `Database:Encrypt` defaults to `true`, so SQL Server connections are encrypted whenever the composed connection string is used. Development stacks can keep `Database:TrustServerCertificate=true` for the self-signed SQL Server container certificate. Production deployments should use a valid SQL Server TLS certificate and set `Database__TrustServerCertificate=false`. See [Data Encryption](https://github.com/Ryadel/Harpyx/wiki/DataEncryption). Runtime configuration controls how the Harpyx services start and connect to infrastructure. Settings changed from the application Admin UI, such as hosted models, platform limits, health checks, and user/tenant administration, are documented in [Admin Interface](https://github.com/Ryadel/Harpyx/wiki/AdminInterface). ## `.env` and `.env.local` Copy [.env.example](../.env.example) to `.env`. This is the Docker/default developer stack configuration. Docker Compose reads it automatically, so `docker compose up` works without extra `--env-file` arguments. Copy [.env.local.example](../.env.local.example) to `.env.local` when running the WebApp or Worker from the IDE / `dotnet run` while dependencies run in Docker. `.env.local` contains only host-local overrides, such as `localhost` endpoints and published ports: ```dotenv # .env COMPOSE_PROJECT_NAME=harpyx DOTNET_ENVIRONMENT=Development ASPNETCORE_URLS=http://+:8080 # .env.local Database__Host=localhost Database__Port=14330 Minio__Endpoint=localhost:9000 RabbitMQ__HostName=localhost Redis__ConnectionString=localhost:6379 OpenSearch__Endpoint=https://localhost:9200 MalwareScan__Host=localhost ``` `HarpyxEnvironmentFileLoader` always loads `.env` first. When `DOTNET_RUNNING_IN_CONTAINER` is not `true`, it then loads `.env.local` as an optional overlay. Containers never load `.env.local`. Internally, `HarpyxEnvironmentFileLoader` parses those files with `DotNetEnv` before `AddEnvironmentVariables()` runs. Values from the files enter the normal .NET environment-variable configuration path, so `__` maps to `:` exactly like process environment variables. ## Docker Compose [docker-compose.yml](../docker-compose.yml) reads `.env` for interpolation and forwards the app-level variables into each service's `environment:` block. Docker-specific host ports and container knobs are also configured through `.env` using `Docker__...` variables: ```dotenv COMPOSE_PROJECT_NAME=harpyx DOTNET_ENVIRONMENT=Development ASPNETCORE_URLS=http://+:8080 ``` `COMPOSE_PROJECT_NAME` fixes the Docker Compose project name, so generated container, network, and volume names stay stable even if the repository folder is renamed. `DOTNET_ENVIRONMENT` selects the runtime environment for both WebApp and Worker, and `ASPNETCORE_URLS` binds Kestrel to the WebApp container port. ```dotenv Docker__WebAppPort=8080 Docker__SqlServerPublicPort=14330 Docker__SqlServerContainerPort=1433 ``` Container images sometimes require fixed uppercase variable names, such as `MSSQL_SA_PASSWORD` or `OPENSEARCH_INITIAL_ADMIN_PASSWORD`. Those names remain uppercase in `docker-compose.yml` because they are imposed by upstream images, but their values are still drawn from `.env`. ## Azure Key Vault To move production secrets to Azure Key Vault, add the provider after `AddEnvironmentVariables()` and before `ComposeDerivedValues`: ```csharp builder.Configuration.AddAzureKeyVault( new Uri(builder.Configuration["KeyVault:Uri"]!), new DefaultAzureCredential()); ``` Store secrets using the `--` separator: | Key Vault secret name | Maps to config key | | --------------------- | ----------------------- | | `Database--Password` | `Database:Password` | | `EntraId--ClientSecret` | `EntraId:ClientSecret` | | `Minio--SecretKey` | `Minio:SecretKey` | | `Encryption--MasterKey` | `Encryption:MasterKey` | Because appsettings, env files, process variables, and Key Vault all target the same section path, application code is identical in every environment. ## Key Sections | Section | Purpose | | --------------- | --------------------------------------------------------- | | `Database` | SQL Server connection components and migration toggle | | `EntraId` | Microsoft Entra ID OIDC parameters | | `Google` | Google OAuth client id/secret/callback | | `SeedOptions` | Bootstrap data, including configured platform admin users | | `Minio` | Object storage endpoint and credentials | | `RabbitMQ` | Job queue broker | | `Redis` | Idempotency/cache connection string | | `OpenSearch` | Chunk index host, credentials, index name, and timeouts | | `Rag` | Chunking and embedding parameters | | `MalwareScan` | ClamAV host/port and fail-closed policy | | `UploadSecurity` | Allowed extensions and MIME types | | `ReverseProxy` | Forwarded headers configuration | | `DataProtection` | ASP.NET Data Protection keys location | | `Encryption` | AES-256-GCM master key for user LLM API keys | | `Email` | SMTP settings | | `OpenTelemetry` | OTLP exporter endpoint | | `Serilog` | Structured logging levels |