using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; using ZB.MOM.WW.Health; 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; 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(); 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.AddZbSerilog(o => o.ServiceName = "mxgateway"); builder.Services.AddGatewayConfiguration(builder.Configuration); builder.Services.AddSqliteAuthStore(builder.Configuration); builder.Services.AddGatewayGrpcAuthorization(); builder.Services.AddHealthChecks() .AddTypeActivatedCheck( "auth-store", failureStatus: null, tags: new[] { ZbHealthTags.Ready }); builder.Services.AddSingleton(); builder.AddZbTelemetry(o => { o.ServiceName = "mxgateway"; o.Meters = [GatewayMetrics.MeterName]; // "MxGateway.Server" — name unchanged if (Enum.TryParse(builder.Configuration["MxGateway:Telemetry:Exporter"], ignoreCase: true, out var exporter)) o.Exporter = exporter; var otlp = builder.Configuration["MxGateway:Telemetry:OtlpEndpoint"]; if (!string.IsNullOrWhiteSpace(otlp)) o.OtlpEndpoint = otlp; }); 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.Configuration); 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.MapZbHealth(); endpoints.MapZbMetrics(); 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; } }