Skip to main content

Overview

Masar Eagle uses a combination of appsettings.json files and environment variables for configuration. This guide covers all configuration options for each service.
Configuration follows the .NET configuration hierarchy: Environment Variables > appsettings..json > appsettings.json

Configuration Structure

Each service has its own configuration in its appsettings.json file:
src/
├── services/
│   ├── Users/Users.Api/appsettings.json
│   ├── Trips/Trips.Api/appsettings.json
│   ├── Notifications/Notifications.Api/appsettings.json
│   ├── Identity/src/Identity.Web/appsettings.json
│   └── Gateway.Api/appsettings.json
└── aspire/AppHost/appsettings.json

Global Configuration

Logging Configuration

All services use consistent logging configuration:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Warning",
      "System.Net.Http.HttpClient": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}
# Override log level
export Logging__LogLevel__Default=Debug
export Logging__LogLevel__Microsoft.AspNetCore=Information

OpenTelemetry Configuration

All services send telemetry to the OpenTelemetry Collector:
# Configured in AppHost.cs for each service
OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_SERVICE_NAME=<service-name>
  • user - Users Service
  • trip - Trips Service
  • notifications - Notifications Service
  • identity - Identity Service
  • gateway - API Gateway
The OpenTelemetry Collector forwards to:

Service-Specific Configuration

Identity Service

{
  "Jwt": {
    "SecretKey": "super-secret-key-for-development-only-change-in-production-masar-eagle-2024",
    "Issuer": "masareagle.identity",
    "Audience": "masar-eagle-api",
    "AccessTokenExpiryMinutes": 60
  },
  "Sms": {
    "Provider": "InMemory",
    "OtpExpiryMinutes": 5,
    "OtpLength": 6,
    "MaxOtpAttempts": 5,
    "MaxResendAttempts": 3,
    "ResendCooldownMinutes": 1,
    "ResendWindowMinutes": 30
  },
  "Taqnyat": {
    "BearerToken": "",
    "SenderName": "",
    "BaseUrl": "https://api.taqnyat.sa/v1"
  }
}
Security Critical: Change the Jwt:SecretKey in production! It must be at least 64 characters for HS512 algorithm.

JWT Configuration Options

SettingDescriptionDefaultRequired
SecretKeyHMAC signing keyDevelopment keyYes
IssuerToken issuermasareagle.identityYes
AudienceToken audiencemasar-eagle-apiYes
AccessTokenExpiryMinutesToken lifetime60Yes

SMS Configuration Options

SettingDescriptionOptions
ProviderSMS providerInMemory, Mock, Taqnyat
OtpExpiryMinutesOTP validity periodDefault: 5
OtpLengthOTP code lengthDefault: 6
MaxOtpAttemptsMax verification attemptsDefault: 5
MaxResendAttemptsMax resend attemptsDefault: 3
ResendCooldownMinutesTime between resendsDefault: 1

Users Service

{
  "Sms": {
    "Provider": "Mock",
    "OtpExpiryMinutes": 5,
    "OtpLength": 6,
    "MaxOtpAttempts": 3,
    "MaxResendAttempts": 3,
    "ResendCooldownMinutes": 1,
    "ResendWindowMinutes": 60
  },
  "FileStorage": {
    "Provider": "Local",
    "UseAbsolutePath": true,
    "MaxFileSizeMB": 2147483647,
    "AllowedExtensions": [".jpg", ".jpeg", ".png", ".webp"],
    "EnableImageResizing": true,
    "MaxImageWidth": 1920,
    "MaxImageHeight": 1080,
    "ThumbnailWidth": 300,
    "ThumbnailHeight": 200
  },
  "AppVersion": {
    "Captain": {
      "Android": { "Current": 26, "Latest": 26 },
      "Ios": { "Current": 26, "Latest": 26 }
    },
    "Passenger": {
      "Android": { "Current": 20, "Latest": 20 },
      "Ios": { "Current": 20, "Latest": 20 }
    }
  },
  "EmailConfiguration": {
    "ApiToken": "re_your_resend_api_token",
    "From": "[email protected]",
    "DisplayName": "مسار إيجل",
    "CompanyPortalUrl": "https://portal.masar-eagle.com",
    "LogoUrl": "https://prod.api.masar-eagle.com/uploads/logos/logo.png"
  }
}

File Storage Options

{
  "FileStorage": {
    "Provider": "Local",
    "UseAbsolutePath": true,
    "BasePath": "/app/uploads"
  }
}
Files are stored on the container filesystem. Use volumes for persistence.

Trips Service

{
  "Hangfire": {
    "DashboardEnabled": true,
    "DashboardPath": "/hangfire"
  },
  "TripReminder": {
    "ReminderTimeWindowMinutes": 60,
    "CronExpression": "*/5 * * * *",
    "BatchSize": 50,
    "EnableReminders": true
  },
  "AutoCancelOverdueTrips": {
    "EnableAutoCancel": true,
    "GracePeriodHours": 5,
    "CronExpression": "*/10 * * * *",
    "BatchSize": 50
  },
  "MoyasarPassenger": {
    "SecretKey": "sk_test_xxx",
    "PublishableKey": "pk_test_xxx",
    "ApiUrl": "https://api.moyasar.com/v1"
  },
  "MoyasarDriver": {
    "SecretKey": "sk_test_xxx",
    "PublishableKey": "pk_test_xxx",
    "ApiUrl": "https://api.moyasar.com/v1"
  },
  "MoyasarCompany": {
    "SecretKey": "sk_test_xxx",
    "PublishableKey": "pk_test_xxx",
    "ApiUrl": "https://api.moyasar.com/v1"
  }
}
Replace test Moyasar keys with production keys before deploying!

Background Job Configuration

JobCron ExpressionDescription
Trip Reminders*/5 * * * *Send reminders 60min before trip
Auto-Cancel*/10 * * * *Cancel trips 5h past scheduled time

Notifications Service

{
  "Firebase": {
    "ProjectId": "masar-eagle-notifications",
    "ServiceAccount": {
      "Type": "service_account",
      "ProjectId": "masar-eagle-notifications",
      "PrivateKeyId": "...",
      "PrivateKey": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
      "ClientEmail": "[email protected]",
      "ClientId": "...",
      "AuthUri": "https://accounts.google.com/o/oauth2/auth",
      "TokenUri": "https://oauth2.googleapis.com/token"
    }
  }
}
Firebase configuration requires a service account JSON from the Firebase Console. Download it from Project Settings > Service Accounts.

Gateway Service

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Yarp": "Information",
      "Gateway.Api": "Information"
    },
    "RequestResponse": {
      "LogRequests": true,
      "LogResponses": true,
      "LogRequestBody": false,
      "LogResponseBody": false,
      "MaxBodySize": 1024,
      "ExcludedPaths": ["/health", "/metrics", "/ready", "/live"],
      "ExcludedHeaders": ["Authorization", "Cookie", "X-Api-Key"],
      "RequestLogLevel": "Information",
      "ResponseLogLevel": "Information",
      "ErrorLogLevel": "Error"
    }
  },
  "ReverseProxy": {
    "Routes": { ... },
    "Clusters": {
      "users-cluster": {
        "Destinations": {
          "destination1": { "Address": "http://user:8080" }
        }
      },
      "trips-cluster": {
        "Destinations": {
          "destination1": { "Address": "http://trip:8080" }
        }
      },
      "notifications-cluster": {
        "Destinations": {
          "destination1": { "Address": "http://notifications:8080" }
        }
      },
      "identity-cluster": {
        "Destinations": {
          "destination1": { "Address": "http://identity:8080" }
        }
      }
    }
  }
}
The gateway uses YARP (Yet Another Reverse Proxy) for routing. Service discovery is automatic when using .NET Aspire.

Database Configuration

Connection Strings

Connection strings are automatically configured by .NET Aspire:
# Users Service
ConnectionStrings__user="Host=postgres;Database=user;Username=postgres;Password=..."

# Trips Service
ConnectionStrings__trip="Host=postgres;Database=trip;Username=postgres;Password=..."

# Notifications Service
ConnectionStrings__notifications="Host=postgres;Database=notifications;Username=postgres;Password=..."

# Identity Service
ConnectionStrings__auth="Host=postgres;Database=auth;Username=postgres;Password=..."
AppHost.cs
var postgres = builder.AddPostgres("postgres")
    .WithEnvironment("POSTGRES_HOST_AUTH_METHOD", "trust")
    .WithDataVolume("masar-postgres-data");

var usersDb = postgres.AddDatabase("user");

builder.AddProject<User>("user")
    .WithReference(usersDb);  // Connection string injected automatically

Migration Configuration

Migrations run automatically on startup. To control migration behavior:
# Disable auto-migration (run manually)
export Database__AutoMigrate=false

# Migration timeout
export Database__MigrationTimeoutSeconds=120

Message Queue Configuration

RabbitMQ Connection

# Automatically configured by Aspire
RabbitMQ__Host=rabbitmq
RabbitMQ__Port=5672
RabbitMQ__Username=guest
RabbitMQ__Password=guest
AppHost.cs
var rabbitmq = builder.AddRabbitMQ("rabbitmq", username, password)
    .WithDataVolume("rabbitmq-data")
    .WithManagementPlugin();

builder.AddProject<User>("user")
    .WithReference(rabbitmq);  // Connection injected

Wolverine Configuration

Masar Eagle uses Wolverine for message handling:
# Wolverine settings (automatically configured)
export Wolverine__RabbitMQ__ConnectionString="amqp://guest:guest@rabbitmq:5672"
export Wolverine__MaxRetries=3
export Wolverine__RetryDelaySeconds=5

Environment-Specific Configuration

Development

export ASPNETCORE_ENVIRONMENT=Development
export DOTNET_ENVIRONMENT=Development

# Use mock services
export Sms__Provider=Mock
export FileStorage__Provider=Local

# Enable detailed logging
export Logging__LogLevel__Default=Debug

# Enable Swagger
export Swagger__Enabled=true

Staging

export ASPNETCORE_ENVIRONMENT=Staging

# Use staging services
export Sms__Provider=Taqnyat
export EmailConfiguration__ApiToken="re_staging_token"

# Moderate logging
export Logging__LogLevel__Default=Information

# Test payment keys
export MoyasarPassenger__SecretKey="sk_test_xxx"

Production

export ASPNETCORE_ENVIRONMENT=Production

# Production services
export Sms__Provider=Taqnyat
export Taqnyat__BearerToken="production-token"

# Production secrets
export Jwt__SecretKey="$(openssl rand -base64 64)"
export MoyasarPassenger__SecretKey="sk_live_xxx"

# Minimal logging
export Logging__LogLevel__Default=Warning

# Disable Swagger
export Swagger__Enabled=false

# Enable HTTPS
export ASPNETCORE_URLS="https://+:443;http://+:80"
Never commit production secrets to version control. Use environment variables, Docker secrets, or a secrets manager.

Secrets Management

Docker Secrets

docker-compose.yml
services:
  identity:
    secrets:
      - jwt_secret
      - db_password
    environment:
      - Jwt__SecretKey=/run/secrets/jwt_secret
      - ConnectionStrings__auth=/run/secrets/db_password

secrets:
  jwt_secret:
    external: true
  db_password:
    external: true

Azure Key Vault

# Install Azure Key Vault package
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets

# Configure in Program.cs
builder.Configuration.AddAzureKeyVault(
    new Uri("https://your-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());

Kubernetes Secrets

apiVersion: v1
kind: Secret
metadata:
  name: masar-eagle-secrets
type: Opaque
data:
  jwt-secret: <base64-encoded-secret>
  db-password: <base64-encoded-password>
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: identity
        env:
        - name: Jwt__SecretKey
          valueFrom:
            secretKeyRef:
              name: masar-eagle-secrets
              key: jwt-secret

Health Checks

All services expose health check endpoints:
# Health check endpoint
curl http://localhost:8080/health

# Readiness check
curl http://localhost:8080/health/ready

# Liveness check
curl http://localhost:8080/health/live

Health Check Configuration

{
  "HealthChecks": {
    "DatabaseHealthCheckTimeout": 5,
    "RabbitMQHealthCheckTimeout": 5,
    "DetailedErrors": false
  }
}

Performance Tuning

Connection Pooling

# PostgreSQL connection pooling
export ConnectionStrings__user="Host=postgres;Database=user;Pooling=true;MinPoolSize=10;MaxPoolSize=100"

# HTTP client pooling
export HttpClient__MaxConnectionsPerServer=50

Caching

# Memory cache
export MemoryCache__SizeLimit=100
export MemoryCache__CompactionPercentage=0.25

# Distributed cache (Redis)
export Redis__Configuration="redis:6379"
export Redis__InstanceName="MasarEagle:"

Validation

Validate your configuration:
#!/bin/bash

# Check required environment variables
REQUIRED_VARS=(
    "ASPNETCORE_ENVIRONMENT"
    "Jwt__SecretKey"
    "ConnectionStrings__user"
    "RabbitMQ__Host"
)

for var in "${REQUIRED_VARS[@]}"; do
    if [ -z "${!var}" ]; then
        echo "❌ Missing: $var"
    else
        echo "✅ Set: $var"
    fi
done

Next Steps

Docker Deployment

Deploy using Docker Compose

Aspire Deployment

Deploy with .NET Aspire

Build docs developers (and LLMs) love