using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Serilog; using ZB.MOM.WW.MxGateway.Contracts; using ZB.MOM.WW.MxGateway.Server.Alarms; using ZB.MOM.WW.MxGateway.Server.Configuration; using ZB.MOM.WW.MxGateway.Server.Dashboard; using ZB.MOM.WW.MxGateway.Server.Diagnostics; using ZB.MOM.WW.MxGateway.Server.Galaxy; using ZB.MOM.WW.MxGateway.Server.Grpc; using ZB.MOM.WW.MxGateway.Server.Metrics; using ZB.MOM.WW.MxGateway.Server.Security.Authentication; using ZB.MOM.WW.MxGateway.Server.Security.Authorization; using ZB.MOM.WW.MxGateway.Server.Sessions; using ZB.MOM.WW.MxGateway.Server.Workers; using ZB.MOM.WW.Telemetry.Serilog; namespace ZB.MOM.WW.MxGateway.Server; /// /// Configures and builds the gateway web application. /// public static class GatewayApplication { private const string StaticAssetsManifestFileName = "ZB.MOM.WW.MxGateway.Server.staticwebassets.endpoints.json"; /// /// Builds a configured web application with all gateway services and middleware. /// /// Command-line arguments passed to the application. /// A configured web application ready to run. public static WebApplication Build(string[] args) { WebApplicationBuilder builder = CreateBuilder(args); WebApplication app = builder.Build(); // Push the per-request correlation properties (via Serilog LogContext) before the // request-logging middleware emits its completion event, so those properties appear on it. app.UseGatewayRequestLoggingScope(); app.UseSerilogRequestLogging(); app.UseStaticFiles(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); app.MapGatewayEndpoints(); return app; } /// /// Creates a web application builder configured with gateway services. /// /// Command-line arguments passed to the application. /// A configured web application builder. public static WebApplicationBuilder CreateBuilder(string[] args) { WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = args, ContentRootPath = ResolveContentRootPath(), }); StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration); ConfigureSerilog(builder); builder.Services.AddGatewayConfiguration(); builder.Services.AddSqliteAuthStore(); builder.Services.AddGatewayGrpcAuthorization(); builder.Services.AddHealthChecks(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddWorkerProcessLauncher(); builder.Services.AddGatewaySessions(); builder.Services.AddGatewayAlarms(); builder.Services.AddGatewayDashboard(); builder.Services.AddGalaxyRepository(); return builder; } /// /// Replaces the default Microsoft.Extensions.Logging provider with the shared /// ZB.MOM.WW.Telemetry.Serilog bootstrap (). /// Sinks and minimum level come from the Serilog configuration section; identity /// (SiteId/NodeRole) is read from MxGateway:Telemetry when present. /// Also registers the project's adapter so the shared redaction /// enricher masks gateway secrets on every event. /// /// The web application builder being configured. private static void ConfigureSerilog(WebApplicationBuilder builder) { string? siteId = builder.Configuration["MxGateway:Telemetry:SiteId"]; string? nodeRole = builder.Configuration["MxGateway:Telemetry:NodeRole"]; builder.Services.AddSingleton(); builder.AddZbSerilog(options => { options.ServiceName = "mxgateway"; options.SiteId = string.IsNullOrWhiteSpace(siteId) ? null : siteId; options.NodeRole = string.IsNullOrWhiteSpace(nodeRole) ? null : nodeRole; }); } private static string ResolveContentRootPath() { string? configuredContentRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT"); if (!string.IsNullOrWhiteSpace(configuredContentRootPath) && IsServerContentRoot(configuredContentRootPath)) { return configuredContentRootPath; } string currentDirectory = Environment.CurrentDirectory; if (IsServerContentRoot(currentDirectory)) { return currentDirectory; } string baseDirectory = AppContext.BaseDirectory; if (IsServerContentRoot(baseDirectory)) { return baseDirectory; } string? discoveredContentRootPath = DiscoverServerContentRoot(currentDirectory) ?? DiscoverServerContentRoot(baseDirectory); return discoveredContentRootPath ?? baseDirectory; } private static string? DiscoverServerContentRoot(string startPath) { DirectoryInfo? directory = new(startPath); while (directory is not null) { if (IsServerContentRoot(directory.FullName)) { return directory.FullName; } string serverProjectPath = Path.Combine(directory.FullName, "src", "ZB.MOM.WW.MxGateway.Server"); if (IsServerContentRoot(serverProjectPath)) { return serverProjectPath; } directory = directory.Parent; } return null; } private static bool IsServerContentRoot(string path) { return File.Exists(Path.Combine(path, "appsettings.json")) && Directory.Exists(Path.Combine(path, "wwwroot")); } /// /// Maps gateway endpoints including gRPC services, health checks, and the dashboard. /// /// Endpoint route builder to map endpoints to. /// The same endpoint route builder for chaining. public static IEndpointRouteBuilder MapGatewayEndpoints(this IEndpointRouteBuilder endpoints) { endpoints.MapStaticAssets(ResolveStaticAssetsManifestPath()); endpoints.MapGet( "/health/live", () => Results.Ok(new GatewayHealthReply( Status: "Healthy", DefaultBackend: GatewayContractInfo.DefaultBackendName, WorkerProtocolVersion: GatewayContractInfo.WorkerProtocolVersion))) .WithName("LiveHealth"); endpoints.MapGrpcService(); endpoints.MapGrpcService(); endpoints.MapGatewayDashboard(); return endpoints; } private static string ResolveStaticAssetsManifestPath() { string manifestPath = Path.Combine(AppContext.BaseDirectory, StaticAssetsManifestFileName); return File.Exists(manifestPath) ? manifestPath : StaticAssetsManifestFileName; } }