using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; 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; 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(); app.UseGatewayRequestLoggingScope(); 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); ConfigureSelfSignedTls(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; } private static void ConfigureSelfSignedTls(WebApplicationBuilder builder) { if (!Security.Tls.KestrelTlsInspector.RequiresGeneratedCertificate(builder.Configuration)) { return; } Configuration.TlsOptions tlsOptions = builder.Configuration.GetSection("MxGateway:Tls").Get() ?? new Configuration.TlsOptions(); using ILoggerFactory loggerFactory = LoggerFactory.Create(logging => { logging.AddConfiguration(builder.Configuration.GetSection("Logging")); logging.AddConsole(); }); Security.Tls.SelfSignedCertificateProvider provider = new( tlsOptions, loggerFactory.CreateLogger(), TimeProvider.System); X509Certificate2 certificate = provider.LoadOrCreate(); builder.WebHost.ConfigureKestrel(options => // The certificate is intentionally owned by Kestrel for the application // lifetime; it is not disposed here. options.ConfigureHttpsDefaults(https => https.ServerCertificate = certificate)); } 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; } }