From f570887999a2a3fa3544b3d8cdd7bd4db26815ce Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Wed, 10 Dec 2025 17:01:07 -0600 Subject: [PATCH 01/19] feat: Add Aspire Apphost and ServiceDefault projects --- .aspire/settings.json | 3 + AppHost/AppHost.cs | 59 ++++++++ AppHost/AppHost.csproj | 29 ++++ AppHost/BuilderExtensions.cs | 156 ++++++++++++++++++++ AppHost/Properties/launchSettings.json | 29 ++++ AppHost/appsettings.Development.json | 8 + AppHost/appsettings.json | 9 ++ ServiceDefaults/Extensions.cs | 197 +++++++++++++++++++++++++ ServiceDefaults/ServiceDefaults.csproj | 22 +++ bitwarden-server.sln | 13 ++ dev/setup_secrets.ps1 | 1 + 11 files changed, 526 insertions(+) create mode 100644 .aspire/settings.json create mode 100644 AppHost/AppHost.cs create mode 100644 AppHost/AppHost.csproj create mode 100644 AppHost/BuilderExtensions.cs create mode 100644 AppHost/Properties/launchSettings.json create mode 100644 AppHost/appsettings.Development.json create mode 100644 AppHost/appsettings.json create mode 100644 ServiceDefaults/Extensions.cs create mode 100644 ServiceDefaults/ServiceDefaults.csproj diff --git a/.aspire/settings.json b/.aspire/settings.json new file mode 100644 index 000000000000..bbd7ab5c9b3e --- /dev/null +++ b/.aspire/settings.json @@ -0,0 +1,3 @@ +{ + "appHostPath": "../AppHost/AppHost.csproj" +} \ No newline at end of file diff --git a/AppHost/AppHost.cs b/AppHost/AppHost.cs new file mode 100644 index 000000000000..a0cc3990f2e1 --- /dev/null +++ b/AppHost/AppHost.cs @@ -0,0 +1,59 @@ +using Bit.AppHost; + +var builder = DistributedApplication.CreateBuilder(args); +var secretsSetup = builder.ConfigureSecrets(); +var isSelfHosted = builder.Configuration["globalSettings:selfHosted"]?.ToLowerInvariant() == "true"; + +// Add Pricing Service - use port from pricingUri in secrets +var pricingService = + builder + .AddProject("pricing-service", + builder.Configuration["pricingServiceRelativePath"] + ?? throw new ArgumentNullException("pricingServiceRelativePath", "Missing pricing service relative path")); + +// Add Database and run migrations +var db = builder.AddSqlServerDatabaseResource(isSelfHosted); +builder.ConfigureMigrations(isSelfHosted) + .WaitFor(db) + .ExcludeFromManifest() + .WaitForCompletion(secretsSetup); + +var azurite = builder.ConfigureAzurite(); + +// Add MailCatcher +var mail = builder + .AddContainer("mailcatcher", "sj26/mailcatcher:latest") + .WithLifetime(ContainerLifetime.Persistent) + .WithEndpoint(port: 10250, name: "smtp", targetPort: 1025) // SMTP port + .WithHttpEndpoint(port: 1080, name: "web", targetPort: 1080); + + +// Add Services +builder.AddBitwardenService(db, secretsSetup, mail, "admin"); +var api = builder.AddBitwardenService(db, secretsSetup, mail, "api") + .WithReference(pricingService) + .WaitFor(azurite); +var billing = builder.AddBitwardenService(db, secretsSetup, mail, "billing"); +builder.AddBitwardenService(db, secretsSetup, mail, "identity"); +builder.AddBitwardenService(db, secretsSetup, mail, "notifications") + .WaitFor(azurite); + +// Add Client Apps +builder.AddBitwardenNpmApp("web-frontend", "web", api) + .WithHttpsEndpoint(8080, 8080, "angular-http", isProxied: false) + .WithUrl("https://bitwarden.test:8080") + .WithExternalHttpEndpoints(); +builder.AddBitwardenNpmApp("desktop-frontend", "desktop", api, "start"); +builder.AddBitwardenNpmApp("browser-frontend", "browser", api, "build:bit:watch:chrome"); + +// Add Ngrok +builder.ConfigureNgrok((billing, "billing-http")); + +builder.Build().Run(); + + + + + + + diff --git a/AppHost/AppHost.csproj b/AppHost/AppHost.csproj new file mode 100644 index 000000000000..ea03a3bed14d --- /dev/null +++ b/AppHost/AppHost.csproj @@ -0,0 +1,29 @@ + + + + + + Exe + net8.0 + enable + enable + e0dba0c6-d131-43bd-9143-2260f11a14ad + + + + + + + + + + + + + + + + + + + diff --git a/AppHost/BuilderExtensions.cs b/AppHost/BuilderExtensions.cs new file mode 100644 index 000000000000..76239e7dc1aa --- /dev/null +++ b/AppHost/BuilderExtensions.cs @@ -0,0 +1,156 @@ +using Aspire.Hosting.Azure; +using Azure.Provisioning; +using Azure.Provisioning.Storage; + +namespace Bit.AppHost; + +public static class BuilderExtensions +{ + public static IResourceBuilder ConfigureSecrets(this IDistributedApplicationBuilder builder) + { + // Setup secrets before starting services + var secretsScript = builder.Configuration["scripts:secretsSetup"] ?? throw new ArgumentNullException("setupSecretsScriptPath", "Missing setup secrets script path"); + var pricingSecretsPath = builder.Configuration["pricingServiceSecretsPath"] ?? throw new ArgumentNullException("pricingServiceSecretsPath", "Missing secrets path"); + + //Pricing Secrets + builder + .AddExecutable("pricing-setup-secrets", "pwsh", pricingSecretsPath, "-File", secretsScript, "-clear") + .ExcludeFromManifest(); + return builder + .AddExecutable("setup-secrets", "pwsh", "../dev", "-File", secretsScript, "-clear") + .ExcludeFromManifest(); + } + + public static IResourceBuilder AddSqlServerDatabaseResource(this IDistributedApplicationBuilder builder, bool isSelfHosted = false) + { + var password = isSelfHosted + ? builder.Configuration["dev:selfHostOverride:globalSettings:sqlServer:password"] + : builder.Configuration["globalSettings:sqlServer:password"]; + + // Add MSSQL - retrieve password from connection string in secrets + var dbpassword = builder.AddParameter("dbPassword", password!, secret: true); + return builder + .AddSqlServer("mssql", password: dbpassword, 1433) + .WithImage("mssql/server:2022-latest") + .WithLifetime(ContainerLifetime.Persistent) + .WithDataVolume() + .AddDatabase("vault", isSelfHosted ? "self_host_dev" : "vault_dev"); + } + + public static IResourceBuilder ConfigureAzurite(this IDistributedApplicationBuilder builder) + { + + // https://github.com/dotnet/aspire/discussions/5552 + var azurite = builder + .AddAzureStorage("azurite").ConfigureInfrastructure(c => + { + var blobStorage = c.GetProvisionableResources().OfType().Single(); + blobStorage.CorsRules.Add(new BicepValue(new StorageCorsRule + { + AllowedOrigins = [new BicepValue("*")], + AllowedMethods = [CorsRuleAllowedMethod.Get, CorsRuleAllowedMethod.Put], + AllowedHeaders = [new BicepValue("*")], + ExposedHeaders = [new BicepValue("*")], + MaxAgeInSeconds = new BicepValue("30") + })); + }) + .RunAsEmulator(c => + { + c.WithBlobPort(10000). + WithQueuePort(10001). + WithTablePort(10002); + }); + + var workingDirectory = builder.Configuration["workingDirectory"] ?? throw new ArgumentNullException("workingDirectory", "Missing working directory"); + + //Run Azurite setup + var azuriteSetupScript = + builder + .Configuration["scripts:azuriteSetup"] + ?? throw new ArgumentNullException("azuriteSetupScriptPath", "Missing azurite setup script path"); + + builder + .AddExecutable("azurite-setup", "pwsh", workingDirectory, "-File", azuriteSetupScript) + .WaitFor(azurite) + .ExcludeFromManifest(); + return azurite; + } + + public static IResourceBuilder ConfigureNgrok(this IDistributedApplicationBuilder builder, (IResourceBuilder, string) tunnelResource) + { + var authToken = builder + .AddParameter("ngrok-auth-token", + builder.Configuration["ngrokAuthToken"] + ?? throw new ArgumentNullException("ngrokAuthToken", "Missing ngrok auth token"), + secret: true); + + return builder.AddNgrok("billing-webhook-ngrok-endpoint", endpointPort: 59600) + .WithAuthToken(authToken) + .WithTunnelEndpoint(tunnelResource.Item1, tunnelResource.Item2) + .WithExplicitStart(); + } + + public static IResourceBuilder ConfigureMigrations(this IDistributedApplicationBuilder builder, bool isSelfHosted) + { + var workingDirectory = builder.Configuration["workingDirectory"] ?? + throw new ArgumentNullException("workingDirectory", "Missing working directory"); + var migrationArgs = new List + { + "-File", + builder.Configuration["scripts:dbMigration"] + ?? throw new ArgumentNullException("migrationScriptPath", "Missing migration script path") + }; + if (isSelfHosted) + { + migrationArgs.Add("-self-hosted"); + } + + return builder + .AddExecutable("run-db-migrations", "pwsh", workingDirectory, migrationArgs.ToArray()); + } + + public static IResourceBuilder AddBitwardenService( + this IDistributedApplicationBuilder builder, IResourceBuilder db, + IResourceBuilder secretsSetup, IResourceBuilder mail, string name) + where TProject : IProjectMetadata, new() + { + var service = builder.AddProject(name) + .WithHttpEndpoint(port: builder.GetBitwardenServicePort(name), name: $"{name}-http") + .WithReference(db) + .WaitFor(db) + .WaitForCompletion(secretsSetup); + + if (name is "admin" or "identity" or "billing") + { + service.WithReference(mail.GetEndpoint("smtp")); + } + + return service; + } + + public static IResourceBuilder AddBitwardenNpmApp(this IDistributedApplicationBuilder builder, + string name, string path, IResourceBuilder api, string scriptName = "build:bit:watch") + { + var clientsRelativePath = builder.Configuration["clientsRelativePath"] ?? + throw new ArgumentNullException("clientsRelativePath", "Missing client relative path"); + + return builder + .AddNpmApp(name, $"{clientsRelativePath}/{path}", scriptName) + .WithReference(api) + .WaitFor(api) + .WithExplicitStart(); + } + + public static int GetBitwardenServicePort(this IDistributedApplicationBuilder builder, string serviceName) + { + var isSelfHosted = builder.Configuration["isSelfHosted"] == "true"; + var configKey = isSelfHosted + ? $"dev:selfHostOverride:globalSettings:baseServiceUri:{serviceName}" + : $"globalSettings:baseServiceUri:{serviceName}"; + + var uriString = builder.Configuration[configKey] + ?? throw new InvalidOperationException($"Configuration value for '{configKey}' not found."); + + return new Uri(uriString).Port; + } +} diff --git a/AppHost/Properties/launchSettings.json b/AppHost/Properties/launchSettings.json new file mode 100644 index 000000000000..14f58c388d18 --- /dev/null +++ b/AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17271;http://localhost:15055", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21022", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22177" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15055", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19147", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20252" + } + } + } +} diff --git a/AppHost/appsettings.Development.json b/AppHost/appsettings.Development.json new file mode 100644 index 000000000000..0c208ae9181e --- /dev/null +++ b/AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/AppHost/appsettings.json b/AppHost/appsettings.json new file mode 100644 index 000000000000..31c092aa4501 --- /dev/null +++ b/AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/ServiceDefaults/Extensions.cs b/ServiceDefaults/Extensions.cs new file mode 100644 index 000000000000..8e97f0c17840 --- /dev/null +++ b/ServiceDefaults/Extensions.cs @@ -0,0 +1,197 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Bit.ServiceDefaults; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } + + // Overload for IHostBuilder + public static IHostBuilder AddServiceDefaults(this IHostBuilder hostBuilder) + { + hostBuilder.ConfigureServices((context, services) => + { + if (context.HostingEnvironment.IsDevelopment()) + { + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy(), new[] { "live" }); + } + + services.AddServiceDiscovery(); + services.ConfigureHttpClientDefaults(http => + { + http.AddStandardResilienceHandler(); + http.AddServiceDiscovery(); + }); + services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(context.HostingEnvironment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + .AddHttpClientInstrumentation(httpClient => + { + httpClient.EnrichWithHttpRequestMessage = (activity, message) => + { + if (context.HostingEnvironment.IsDevelopment()) + { + activity.SetTag("http.request_content_length", message.Content?.Headers.ContentLength); + activity.SetTag("http.request_method", message.Method.Method); + activity.SetTag("http.request_url", message.RequestUri?.ToString()); + activity.SetTag("http.request_message_headers", message.Headers.ToString()); + activity.SetTag("http.request_body", message.Content?.ReadAsStringAsync().Result); + } + }; + httpClient.EnrichWithHttpResponseMessage = (activity, message) => + { + if (context.HostingEnvironment.IsDevelopment()) + { + activity.SetTag("http.response_content_length", + message.Content.Headers.ContentLength); + activity.SetTag("http.response_status_code", (int)message.StatusCode); + activity.SetTag("http.response_status_text", message.ReasonPhrase); + activity.SetTag("http.response_content_type", + message.Content.Headers.ContentType?.MediaType); + activity.SetTag("http.response_message_headers", message.Headers.ToString()); + activity.SetTag("http.response_body", message.Content.ReadAsStringAsync().Result); + } + }; + }); + }); + var useOtlpExporter = !string.IsNullOrWhiteSpace(context.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + if (useOtlpExporter) + { + services.AddOpenTelemetry().UseOtlpExporter(); + } + }); + return hostBuilder; + } +} diff --git a/ServiceDefaults/ServiceDefaults.csproj b/ServiceDefaults/ServiceDefaults.csproj new file mode 100644 index 000000000000..12b4707e9c89 --- /dev/null +++ b/ServiceDefaults/ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 6786ad610c24..ba5e838ae4dd 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -134,10 +134,15 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbSeederUtility", "util\DbSeederUtility\DbSeederUtility.csproj", "{17A89266-260A-4A03-81AE-C0468C6EE06E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RustSdk", "util\RustSdk\RustSdk.csproj", "{D1513D90-E4F5-44A9-9121-5E46E3E4A3F7}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedWeb.Test", "test\SharedWeb.Test\SharedWeb.Test.csproj", "{AD59537D-5259-4B7A-948F-0CF58E80B359}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSO.Test", "bitwarden_license\test\SSO.Test\SSO.Test.csproj", "{7D98784C-C253-43FB-9873-25B65C6250D6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "AppHost\AppHost.csproj", "{B4CC25D0-BD09-459E-9885-DF9A56E304F6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "ServiceDefaults\ServiceDefaults.csproj", "{7D6F3351-9CA6-4B35-956F-1EE346330A41}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -354,6 +359,14 @@ Global {7D98784C-C253-43FB-9873-25B65C6250D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D98784C-C253-43FB-9873-25B65C6250D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D98784C-C253-43FB-9873-25B65C6250D6}.Release|Any CPU.Build.0 = Release|Any CPU + {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Release|Any CPU.Build.0 = Release|Any CPU + {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dev/setup_secrets.ps1 b/dev/setup_secrets.ps1 index 96dff0463211..d570a74e4619 100644 --- a/dev/setup_secrets.ps1 +++ b/dev/setup_secrets.ps1 @@ -27,6 +27,7 @@ $projects = @{ Sso = "../bitwarden_license/src/Sso" Scim = "../bitwarden_license/src/Scim" IntegrationTests = "../test/Infrastructure.IntegrationTest" + AppHost = "../AppHost" } foreach ($key in $projects.keys) { From 7597b4ea714c62008aa61adbcece21d235bc64ac Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Wed, 10 Dec 2025 17:02:41 -0600 Subject: [PATCH 02/19] feat: Add compiler conditional ServiceDefaults to included projects --- src/Admin/Admin.csproj | 1 + src/Admin/Program.cs | 15 +++++++++++---- src/Api/Api.csproj | 1 + src/Api/Program.cs | 15 +++++++++++---- src/Billing/Billing.csproj | 1 + src/Billing/Program.cs | 16 ++++++++++++---- src/Identity/Identity.csproj | 1 + src/Identity/Program.cs | 14 +++++++++++--- src/Notifications/Notifications.csproj | 1 + src/Notifications/Program.cs | 15 +++++++++++---- 10 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/Admin/Admin.csproj b/src/Admin/Admin.csproj index cd30e841b451..787e9996866d 100644 --- a/src/Admin/Admin.csproj +++ b/src/Admin/Admin.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Admin/Program.cs b/src/Admin/Program.cs index 006a8223b27f..6773cc0d06c2 100644 --- a/src/Admin/Program.cs +++ b/src/Admin/Program.cs @@ -1,4 +1,7 @@ using Bit.Core.Utilities; +#if DEBUG +using Bit.ServiceDefaults; +#endif namespace Bit.Admin; @@ -6,7 +9,7 @@ public class Program { public static void Main(string[] args) { - Host + var builder = Host .CreateDefaultBuilder(args) .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => @@ -17,8 +20,12 @@ public static void Main(string[] args) }); webBuilder.UseStartup(); }) - .AddSerilogFileLogging() - .Build() - .Run(); + .AddSerilogFileLogging(); + +#if DEBUG + builder.AddServiceDefaults(); +#endif + + builder.Build().Run(); } } diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index 48fedfc8c119..8c7dca29dc7a 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Api/Program.cs b/src/Api/Program.cs index bf924af47ff1..62b388f2bef8 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -1,4 +1,7 @@ using Bit.Core.Utilities; +#if DEBUG +using Bit.ServiceDefaults; +#endif namespace Bit.Api; @@ -6,15 +9,19 @@ public class Program { public static void Main(string[] args) { - Host + var builder = Host .CreateDefaultBuilder(args) .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .AddSerilogFileLogging() - .Build() - .Run(); + .AddSerilogFileLogging(); + +#if DEBUG + builder.AddServiceDefaults(); +#endif + + builder.Build().Run(); } } diff --git a/src/Billing/Billing.csproj b/src/Billing/Billing.csproj index 69999dc7958d..36ed5babb8df 100644 --- a/src/Billing/Billing.csproj +++ b/src/Billing/Billing.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Billing/Program.cs b/src/Billing/Program.cs index 334dc49368dc..78159cbe6963 100644 --- a/src/Billing/Program.cs +++ b/src/Billing/Program.cs @@ -1,4 +1,7 @@ using Bit.Core.Utilities; +#if DEBUG +using Bit.ServiceDefaults; +#endif namespace Bit.Billing; @@ -6,15 +9,20 @@ public class Program { public static void Main(string[] args) { - Host + var builder = Host .CreateDefaultBuilder(args) .UseBitwardenSdk() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .AddSerilogFileLogging() - .Build() - .Run(); + .AddSerilogFileLogging(); + +#if DEBUG + builder.AddServiceDefaults(); +#endif + + builder.Build().Run(); } } + diff --git a/src/Identity/Identity.csproj b/src/Identity/Identity.csproj index bf5ab82166bd..e703fe55cdf9 100644 --- a/src/Identity/Identity.csproj +++ b/src/Identity/Identity.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Identity/Program.cs b/src/Identity/Program.cs index 238ad8ce3a9c..8662336b442a 100644 --- a/src/Identity/Program.cs +++ b/src/Identity/Program.cs @@ -1,4 +1,7 @@ using Bit.Core.Utilities; +#if DEBUG +using Bit.ServiceDefaults; +#endif namespace Bit.Identity; @@ -6,9 +9,14 @@ public class Program { public static void Main(string[] args) { - CreateHostBuilder(args) - .Build() - .Run(); + var builder = CreateHostBuilder(args); + +#if DEBUG + builder.AddServiceDefaults(); +#endif + + builder.Build().Run(); + } public static IHostBuilder CreateHostBuilder(string[] args) diff --git a/src/Notifications/Notifications.csproj b/src/Notifications/Notifications.csproj index 4d19f7faf990..1e8ad21b4f02 100644 --- a/src/Notifications/Notifications.csproj +++ b/src/Notifications/Notifications.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Notifications/Program.cs b/src/Notifications/Program.cs index 2792391729a7..ce593bea1a17 100644 --- a/src/Notifications/Program.cs +++ b/src/Notifications/Program.cs @@ -1,4 +1,7 @@ using Bit.Core.Utilities; +#if DEBUG +using Bit.ServiceDefaults; +#endif namespace Bit.Notifications; @@ -6,15 +9,19 @@ public class Program { public static void Main(string[] args) { - Host + var builder = Host .CreateDefaultBuilder(args) .ConfigureCustomAppConfiguration(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .AddSerilogFileLogging() - .Build() - .Run(); + .AddSerilogFileLogging(); + +#if DEBUG + builder.AddServiceDefaults(); +#endif + + builder.Build().Run(); } } From 3e1ee8a57faedcdcebb272f990e46e4bf44bfcaa Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Mon, 19 Jan 2026 10:38:32 -0500 Subject: [PATCH 03/19] fix(billing): update otlexporter duplicate call --- ServiceDefaults/Extensions.cs | 14 +++++++++----- bitwarden-server.sln | 4 ++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ServiceDefaults/Extensions.cs b/ServiceDefaults/Extensions.cs index 8e97f0c17840..352718b7d73f 100644 --- a/ServiceDefaults/Extensions.cs +++ b/ServiceDefaults/Extensions.cs @@ -3,10 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using OpenTelemetry; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; namespace Bit.ServiceDefaults; @@ -189,7 +185,15 @@ public static IHostBuilder AddServiceDefaults(this IHostBuilder hostBuilder) var useOtlpExporter = !string.IsNullOrWhiteSpace(context.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); if (useOtlpExporter) { - services.AddOpenTelemetry().UseOtlpExporter(); + // Check if OTLP exporter is already configured + var otlpExporterAlreadyRegistered = services.Any(sd => + sd.ServiceType.FullName?.Contains("OtlpExporter") == true || + sd.ImplementationType?.FullName?.Contains("OtlpExporter") == true); + + if (!otlpExporterAlreadyRegistered) + { + services.AddOpenTelemetry().UseOtlpExporter(); + } } }); return hostBuilder; diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 2dacaf648952..c92e3de89c79 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -379,6 +379,10 @@ Global {FFB09376-595B-6F93-36F0-70CAE90AFECB}.Debug|Any CPU.Build.0 = Debug|Any CPU {FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.ActiveCfg = Release|Any CPU {FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.Build.0 = Release|Any CPU + {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From fe72d2c6cc6ed2c567148e19a00f6bfd30f7b771 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 14:35:00 -0500 Subject: [PATCH 04/19] fix(billing): update imports --- ServiceDefaults/Extensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ServiceDefaults/Extensions.cs b/ServiceDefaults/Extensions.cs index 352718b7d73f..63e1a0a1a4df 100644 --- a/ServiceDefaults/Extensions.cs +++ b/ServiceDefaults/Extensions.cs @@ -3,6 +3,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; namespace Bit.ServiceDefaults; From a069e72897240e5ab3e29e3ab39c62aad360cf0d Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 15:04:38 -0500 Subject: [PATCH 05/19] fix(billing): revert unintended changes --- test/SeederApi.IntegrationTest/QueryControllerTest.cs | 1 + test/SeederApi.IntegrationTest/SeedControllerTest.cs | 1 + .../SeederApiApplicationFactory.cs | 3 ++- test/Server.IntegrationTest/Server.cs | 6 +++++- util/SeederApi/Commands/DestroySceneCommand.cs | 4 +++- util/SeederApi/Controllers/InfoController.cs | 3 ++- util/SeederApi/Program.cs | 4 +++- util/SeederApi/Queries/GetAllPlayIdsQuery.cs | 3 ++- util/SeederApi/Startup.cs | 1 + 9 files changed, 20 insertions(+), 6 deletions(-) diff --git a/test/SeederApi.IntegrationTest/QueryControllerTest.cs b/test/SeederApi.IntegrationTest/QueryControllerTest.cs index 0b8707c6ce65..571181e49fd4 100644 --- a/test/SeederApi.IntegrationTest/QueryControllerTest.cs +++ b/test/SeederApi.IntegrationTest/QueryControllerTest.cs @@ -1,5 +1,6 @@ using System.Net; using Bit.SeederApi.Models.Request; +using Xunit; namespace Bit.SeederApi.IntegrationTest; diff --git a/test/SeederApi.IntegrationTest/SeedControllerTest.cs b/test/SeederApi.IntegrationTest/SeedControllerTest.cs index 8766634ad020..1d081d019e92 100644 --- a/test/SeederApi.IntegrationTest/SeedControllerTest.cs +++ b/test/SeederApi.IntegrationTest/SeedControllerTest.cs @@ -1,6 +1,7 @@ using System.Net; using Bit.SeederApi.Models.Request; using Bit.SeederApi.Models.Response; +using Xunit; namespace Bit.SeederApi.IntegrationTest; diff --git a/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs b/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs index 3811c317538a..6d815b03eaa5 100644 --- a/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs +++ b/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs @@ -1,4 +1,5 @@ -using Bit.IntegrationTestCommon; +using Bit.Core.Services; +using Bit.IntegrationTestCommon; using Bit.IntegrationTestCommon.Factories; namespace Bit.SeederApi.IntegrationTest; diff --git a/test/Server.IntegrationTest/Server.cs b/test/Server.IntegrationTest/Server.cs index 75f88cbbe77d..073dbffb5a6c 100644 --- a/test/Server.IntegrationTest/Server.cs +++ b/test/Server.IntegrationTest/Server.cs @@ -1,4 +1,8 @@ -namespace Bit.Server.IntegrationTest; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; + +namespace Bit.Server.IntegrationTest; public class Server : WebApplicationFactory { diff --git a/util/SeederApi/Commands/DestroySceneCommand.cs b/util/SeederApi/Commands/DestroySceneCommand.cs index 32294f26c111..0e0f4edd6d9b 100644 --- a/util/SeederApi/Commands/DestroySceneCommand.cs +++ b/util/SeederApi/Commands/DestroySceneCommand.cs @@ -1,4 +1,6 @@ -using Bit.SeederApi.Commands.Interfaces; +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.SeederApi.Commands.Interfaces; using Bit.SeederApi.Services; namespace Bit.SeederApi.Commands; diff --git a/util/SeederApi/Controllers/InfoController.cs b/util/SeederApi/Controllers/InfoController.cs index 100035de4d47..de4a264ddb1a 100644 --- a/util/SeederApi/Controllers/InfoController.cs +++ b/util/SeederApi/Controllers/InfoController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Bit.Core.Utilities; +using Microsoft.AspNetCore.Mvc; namespace Bit.SeederApi.Controllers; diff --git a/util/SeederApi/Program.cs b/util/SeederApi/Program.cs index ef03d54d3b44..2067df307a09 100644 --- a/util/SeederApi/Program.cs +++ b/util/SeederApi/Program.cs @@ -1,4 +1,6 @@ -namespace Bit.SeederApi; +using Bit.Core.Utilities; + +namespace Bit.SeederApi; public class Program { diff --git a/util/SeederApi/Queries/GetAllPlayIdsQuery.cs b/util/SeederApi/Queries/GetAllPlayIdsQuery.cs index 477606162c11..7bc72e5b07f3 100644 --- a/util/SeederApi/Queries/GetAllPlayIdsQuery.cs +++ b/util/SeederApi/Queries/GetAllPlayIdsQuery.cs @@ -1,4 +1,5 @@ -using Bit.SeederApi.Queries.Interfaces; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.SeederApi.Queries.Interfaces; namespace Bit.SeederApi.Queries; diff --git a/util/SeederApi/Startup.cs b/util/SeederApi/Startup.cs index 3d02ddb57f27..420078f5096f 100644 --- a/util/SeederApi/Startup.cs +++ b/util/SeederApi/Startup.cs @@ -1,4 +1,5 @@ using System.Globalization; +using Bit.Core.Settings; using Bit.Seeder; using Bit.Seeder.Factories; using Bit.SeederApi.Extensions; From 79fd6e08ed72d75430368c55eccfe94b96c45810 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 15:13:51 -0500 Subject: [PATCH 06/19] fix(billing): revert unintended changes --- .../Controllers/AccountControllerTests.cs | 12 ++++++++++++ .../Utilities/SsoTestDataBuilder.cs | 13 ++++++++++++- .../Utilities/SuccessfulAuthResult.cs | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bitwarden_license/test/Sso.IntegrationTest/Controllers/AccountControllerTests.cs b/bitwarden_license/test/Sso.IntegrationTest/Controllers/AccountControllerTests.cs index 4c5019530c8b..7a1c9f9628a4 100644 --- a/bitwarden_license/test/Sso.IntegrationTest/Controllers/AccountControllerTests.cs +++ b/bitwarden_license/test/Sso.IntegrationTest/Controllers/AccountControllerTests.cs @@ -1,9 +1,21 @@ using System.Net; +using Bit.Core; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Repositories; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Sso.IntegrationTest.Utilities; using Bit.Test.Common.AutoFixture.Attributes; using Bitwarden.License.Test.Sso.IntegrationTest.Utilities; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Testing; +using NSubstitute; +using Xunit; using AuthenticationSchemes = Bit.Core.AuthenticationSchemes; namespace Bit.Sso.IntegrationTest.Controllers; diff --git a/bitwarden_license/test/Sso.IntegrationTest/Utilities/SsoTestDataBuilder.cs b/bitwarden_license/test/Sso.IntegrationTest/Utilities/SsoTestDataBuilder.cs index 22422586ac1f..95f2387af268 100644 --- a/bitwarden_license/test/Sso.IntegrationTest/Utilities/SsoTestDataBuilder.cs +++ b/bitwarden_license/test/Sso.IntegrationTest/Utilities/SsoTestDataBuilder.cs @@ -1,5 +1,16 @@ -using Bitwarden.License.Test.Sso.IntegrationTest.Utilities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Repositories; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Bitwarden.License.Test.Sso.IntegrationTest.Utilities; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; using Microsoft.AspNetCore.Authentication; +using NSubstitute; using AuthenticationSchemes = Bit.Core.AuthenticationSchemes; namespace Bit.Sso.IntegrationTest.Utilities; diff --git a/bitwarden_license/test/Sso.IntegrationTest/Utilities/SuccessfulAuthResult.cs b/bitwarden_license/test/Sso.IntegrationTest/Utilities/SuccessfulAuthResult.cs index 99a5000ab268..72f5738ad984 100644 --- a/bitwarden_license/test/Sso.IntegrationTest/Utilities/SuccessfulAuthResult.cs +++ b/bitwarden_license/test/Sso.IntegrationTest/Utilities/SuccessfulAuthResult.cs @@ -1,4 +1,6 @@ using System.Security.Claims; +using Bit.Core; +using Duende.IdentityModel; using Microsoft.AspNetCore.Authentication; namespace Bitwarden.License.Test.Sso.IntegrationTest.Utilities; From f7a80708f61a8255a6ea2cb81156d229ef273d46 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 15:28:35 -0500 Subject: [PATCH 07/19] fix(billing): fix unintended files --- bitwarden-server.sln | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/bitwarden-server.sln b/bitwarden-server.sln index c92e3de89c79..0598a2d6f632 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -147,10 +147,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sso.IntegrationTest", "bitw EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.IntegrationTest", "test\Server.IntegrationTest\Server.IntegrationTest.csproj", "{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "AppHost\AppHost.csproj", "{B4CC25D0-BD09-459E-9885-DF9A56E304F6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "ServiceDefaults\ServiceDefaults.csproj", "{7D6F3351-9CA6-4B35-956F-1EE346330A41}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -379,10 +375,10 @@ Global {FFB09376-595B-6F93-36F0-70CAE90AFECB}.Debug|Any CPU.Build.0 = Debug|Any CPU {FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.ActiveCfg = Release|Any CPU {FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.Build.0 = Release|Any CPU - {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4CC25D0-BD09-459E-9885-DF9A56E304F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D6F3351-9CA6-4B35-956F-1EE346330A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 471cb8d628d49e4e99edc8cd201f8cc524ce9847 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 16:12:24 -0500 Subject: [PATCH 08/19] fix(billing): add apphost and extension methods --- bitwarden-server.sln | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 0598a2d6f632..e4aba333f008 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -147,6 +147,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sso.IntegrationTest", "bitw EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.IntegrationTest", "test\Server.IntegrationTest\Server.IntegrationTest.csproj", "{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "AppHost\AppHost.csproj", "{0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "ServiceDefaults\ServiceDefaults.csproj", "{CDC04BCD-A115-456A-8F76-20CB522C5814}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -379,6 +383,14 @@ Global {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.Build.0 = Release|Any CPU + {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Release|Any CPU.Build.0 = Release|Any CPU + {CDC04BCD-A115-456A-8F76-20CB522C5814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDC04BCD-A115-456A-8F76-20CB522C5814}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDC04BCD-A115-456A-8F76-20CB522C5814}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDC04BCD-A115-456A-8F76-20CB522C5814}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From f7414256161ab0c191338aab1ba9e127d518ede9 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 20:52:31 -0500 Subject: [PATCH 09/19] fix(billing): update aspire --- AppHost/AppHost.csproj | 16 +++++++--------- ServiceDefaults/ServiceDefaults.csproj | 16 ++++++++-------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/AppHost/AppHost.csproj b/AppHost/AppHost.csproj index ea03a3bed14d..c31cb35425a0 100644 --- a/AppHost/AppHost.csproj +++ b/AppHost/AppHost.csproj @@ -1,6 +1,4 @@ - - - + Exe @@ -11,12 +9,12 @@ - - - - - - + + + + + + diff --git a/ServiceDefaults/ServiceDefaults.csproj b/ServiceDefaults/ServiceDefaults.csproj index 12b4707e9c89..1487a63e10a0 100644 --- a/ServiceDefaults/ServiceDefaults.csproj +++ b/ServiceDefaults/ServiceDefaults.csproj @@ -8,15 +8,15 @@ - + - - - - - - - + + + + + + + From 9f6286fff60a794aa054559d94d33b5f9a39366c Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 20 Jan 2026 22:19:51 -0500 Subject: [PATCH 10/19] fix(billing): update references to match integration test configuration version --- ServiceDefaults/ServiceDefaults.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ServiceDefaults/ServiceDefaults.csproj b/ServiceDefaults/ServiceDefaults.csproj index 1487a63e10a0..bfaea3b9b8e2 100644 --- a/ServiceDefaults/ServiceDefaults.csproj +++ b/ServiceDefaults/ServiceDefaults.csproj @@ -10,13 +10,13 @@ - + - - - - - + + + + + From 35ca62a2ed6a9199325bc0ebc91bd533043b1d64 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Wed, 21 Jan 2026 10:28:13 -0500 Subject: [PATCH 11/19] chore(billing): update secrets.json example --- dev/secrets.json.example | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/dev/secrets.json.example b/dev/secrets.json.example index 0d4213aec184..f20ab8b2257c 100644 --- a/dev/secrets.json.example +++ b/dev/secrets.json.example @@ -11,8 +11,24 @@ }, "globalSettings": { "selfHosted": true, + "launchDarkly": { + "flagValues": { + } + }, + "pricingUri": "", + "baseServiceUri": { + "cloudRegion": "", + "vault": "", + "api": "", + "identity": "", + "admin": "", + "notifications": "", + "sso": "", + "billing": "" + }, "sqlServer": { - "connectionString": "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True" + "connectionString": "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True", + "password": }, "postgreSql": { "connectionString": "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev;Include Error Detail=true" @@ -40,5 +56,15 @@ "licenseDirectory": "", "enableNewDeviceVerification": true, "enableEmailVerification": true - } + }, + "ngrokAuthToken": "", + "workingDirectory": "../dev", + "clientsRelativePath": "", + "pricingServiceSecretsPath": "", + "pricingServiceRelativePath": "", + "scripts": { + "dbMigration": "migrate.ps1", + "azuriteSetup": "setup_azurite.ps1", + "secretsSetup": "setup_secrets.ps1" + }, } From 798fa3fe2ae71a6fa44e329a710bcffe92c086be Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:01:36 -0400 Subject: [PATCH 12/19] refactor(Aspire): remove ServiceDefaults project --- ServiceDefaults/Extensions.cs | 205 ------------------------- ServiceDefaults/ServiceDefaults.csproj | 22 --- bitwarden-server.sln | 8 +- src/Admin/Admin.csproj | 1 - src/Admin/Program.cs | 7 - src/Api/Api.csproj | 3 +- src/Api/Program.cs | 7 - src/Billing/Billing.csproj | 1 - src/Billing/Program.cs | 7 - src/Identity/Identity.csproj | 1 - src/Identity/Program.cs | 7 - src/Notifications/Notifications.csproj | 1 - src/Notifications/Program.cs | 7 - 13 files changed, 2 insertions(+), 275 deletions(-) delete mode 100644 ServiceDefaults/Extensions.cs delete mode 100644 ServiceDefaults/ServiceDefaults.csproj diff --git a/ServiceDefaults/Extensions.cs b/ServiceDefaults/Extensions.cs deleted file mode 100644 index 63e1a0a1a4df..000000000000 --- a/ServiceDefaults/Extensions.cs +++ /dev/null @@ -1,205 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using OpenTelemetry; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; - -namespace Bit.ServiceDefaults; - -// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. -// This project should be referenced by each service project in your solution. -// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults -public static class Extensions -{ - private const string HealthEndpointPath = "/health"; - private const string AlivenessEndpointPath = "/alive"; - - public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.ConfigureOpenTelemetry(); - - builder.AddDefaultHealthChecks(); - - builder.Services.AddServiceDiscovery(); - - builder.Services.ConfigureHttpClientDefaults(http => - { - // Turn on resilience by default - http.AddStandardResilienceHandler(); - - // Turn on service discovery by default - http.AddServiceDiscovery(); - }); - - // Uncomment the following to restrict the allowed schemes for service discovery. - // builder.Services.Configure(options => - // { - // options.AllowedSchemes = ["https"]; - // }); - - return builder; - } - - public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.Logging.AddOpenTelemetry(logging => - { - logging.IncludeFormattedMessage = true; - logging.IncludeScopes = true; - }); - - builder.Services.AddOpenTelemetry() - .WithMetrics(metrics => - { - metrics.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation(); - }) - .WithTracing(tracing => - { - tracing.AddSource(builder.Environment.ApplicationName) - .AddAspNetCoreInstrumentation(tracing => - // Exclude health check requests from tracing - tracing.Filter = context => - !context.Request.Path.StartsWithSegments(HealthEndpointPath) - && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) - ) - // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) - //.AddGrpcClientInstrumentation() - .AddHttpClientInstrumentation(); - }); - - builder.AddOpenTelemetryExporters(); - - return builder; - } - - private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); - - if (useOtlpExporter) - { - builder.Services.AddOpenTelemetry().UseOtlpExporter(); - } - - // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) - //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) - //{ - // builder.Services.AddOpenTelemetry() - // .UseAzureMonitor(); - //} - - return builder; - } - - public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.Services.AddHealthChecks() - // Add a default liveness check to ensure app is responsive - .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); - - return builder; - } - - public static WebApplication MapDefaultEndpoints(this WebApplication app) - { - // Adding health checks endpoints to applications in non-development environments has security implications. - // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. - if (app.Environment.IsDevelopment()) - { - // All health checks must pass for app to be considered ready to accept traffic after starting - app.MapHealthChecks(HealthEndpointPath); - - // Only health checks tagged with the "live" tag must pass for app to be considered alive - app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions - { - Predicate = r => r.Tags.Contains("live") - }); - } - - return app; - } - - // Overload for IHostBuilder - public static IHostBuilder AddServiceDefaults(this IHostBuilder hostBuilder) - { - hostBuilder.ConfigureServices((context, services) => - { - if (context.HostingEnvironment.IsDevelopment()) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy(), new[] { "live" }); - } - - services.AddServiceDiscovery(); - services.ConfigureHttpClientDefaults(http => - { - http.AddStandardResilienceHandler(); - http.AddServiceDiscovery(); - }); - services.AddOpenTelemetry() - .WithMetrics(metrics => - { - metrics.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation(); - }) - .WithTracing(tracing => - { - tracing.AddSource(context.HostingEnvironment.ApplicationName) - .AddAspNetCoreInstrumentation(tracing => - tracing.Filter = context => - !context.Request.Path.StartsWithSegments(HealthEndpointPath) - && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) - ) - .AddHttpClientInstrumentation(httpClient => - { - httpClient.EnrichWithHttpRequestMessage = (activity, message) => - { - if (context.HostingEnvironment.IsDevelopment()) - { - activity.SetTag("http.request_content_length", message.Content?.Headers.ContentLength); - activity.SetTag("http.request_method", message.Method.Method); - activity.SetTag("http.request_url", message.RequestUri?.ToString()); - activity.SetTag("http.request_message_headers", message.Headers.ToString()); - activity.SetTag("http.request_body", message.Content?.ReadAsStringAsync().Result); - } - }; - httpClient.EnrichWithHttpResponseMessage = (activity, message) => - { - if (context.HostingEnvironment.IsDevelopment()) - { - activity.SetTag("http.response_content_length", - message.Content.Headers.ContentLength); - activity.SetTag("http.response_status_code", (int)message.StatusCode); - activity.SetTag("http.response_status_text", message.ReasonPhrase); - activity.SetTag("http.response_content_type", - message.Content.Headers.ContentType?.MediaType); - activity.SetTag("http.response_message_headers", message.Headers.ToString()); - activity.SetTag("http.response_body", message.Content.ReadAsStringAsync().Result); - } - }; - }); - }); - var useOtlpExporter = !string.IsNullOrWhiteSpace(context.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); - if (useOtlpExporter) - { - // Check if OTLP exporter is already configured - var otlpExporterAlreadyRegistered = services.Any(sd => - sd.ServiceType.FullName?.Contains("OtlpExporter") == true || - sd.ImplementationType?.FullName?.Contains("OtlpExporter") == true); - - if (!otlpExporterAlreadyRegistered) - { - services.AddOpenTelemetry().UseOtlpExporter(); - } - } - }); - return hostBuilder; - } -} diff --git a/ServiceDefaults/ServiceDefaults.csproj b/ServiceDefaults/ServiceDefaults.csproj deleted file mode 100644 index bfaea3b9b8e2..000000000000 --- a/ServiceDefaults/ServiceDefaults.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net8.0 - enable - enable - true - - - - - - - - - - - - - - - diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 5e4f8d82dd59..a687297035db 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -151,8 +151,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Setup.Test", "test\Setup.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "AppHost\AppHost.csproj", "{0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "ServiceDefaults\ServiceDefaults.csproj", "{CDC04BCD-A115-456A-8F76-20CB522C5814}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -389,11 +387,7 @@ Global {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Release|Any CPU.Build.0 = Release|Any CPU - {CDC04BCD-A115-456A-8F76-20CB522C5814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDC04BCD-A115-456A-8F76-20CB522C5814}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDC04BCD-A115-456A-8F76-20CB522C5814}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDC04BCD-A115-456A-8F76-20CB522C5814}.Release|Any CPU.Build.0 = Release|Any CPU - {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +{5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Release|Any CPU.Build.0 = Release|Any CPU diff --git a/src/Admin/Admin.csproj b/src/Admin/Admin.csproj index 884a5243eea4..573358946604 100644 --- a/src/Admin/Admin.csproj +++ b/src/Admin/Admin.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Admin/Program.cs b/src/Admin/Program.cs index 9235fea00131..1a8f78dd2b2c 100644 --- a/src/Admin/Program.cs +++ b/src/Admin/Program.cs @@ -1,7 +1,4 @@ using Bit.Core.Utilities; -#if DEBUG -using Bit.ServiceDefaults; -#endif namespace Bit.Admin; @@ -22,10 +19,6 @@ public static void Main(string[] args) }) .AddSerilogFileLogging(); -#if DEBUG - builder.AddServiceDefaults(); -#endif - builder.Build().Run(); } } diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index d21c07f81327..dcff2f3e16dc 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -1,4 +1,4 @@ - + @@ -21,7 +21,6 @@ - diff --git a/src/Api/Program.cs b/src/Api/Program.cs index 06f76b2b4f1c..e90b4c62fa6f 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -1,7 +1,4 @@ using Bit.Core.Utilities; -#if DEBUG -using Bit.ServiceDefaults; -#endif namespace Bit.Api; @@ -18,10 +15,6 @@ public static void Main(string[] args) }) .AddSerilogFileLogging(); -#if DEBUG - builder.AddServiceDefaults(); -#endif - builder.Build().Run(); } } diff --git a/src/Billing/Billing.csproj b/src/Billing/Billing.csproj index fc6366b4ade3..bbe98ad55089 100644 --- a/src/Billing/Billing.csproj +++ b/src/Billing/Billing.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Billing/Program.cs b/src/Billing/Program.cs index 78159cbe6963..fecb71e7bbe1 100644 --- a/src/Billing/Program.cs +++ b/src/Billing/Program.cs @@ -1,7 +1,4 @@ using Bit.Core.Utilities; -#if DEBUG -using Bit.ServiceDefaults; -#endif namespace Bit.Billing; @@ -18,10 +15,6 @@ public static void Main(string[] args) }) .AddSerilogFileLogging(); -#if DEBUG - builder.AddServiceDefaults(); -#endif - builder.Build().Run(); } } diff --git a/src/Identity/Identity.csproj b/src/Identity/Identity.csproj index 23eb45b72130..f31d8c005e05 100644 --- a/src/Identity/Identity.csproj +++ b/src/Identity/Identity.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Identity/Program.cs b/src/Identity/Program.cs index 6078867a8f29..8a68d6a66fb8 100644 --- a/src/Identity/Program.cs +++ b/src/Identity/Program.cs @@ -1,7 +1,4 @@ using Bit.Core.Utilities; -#if DEBUG -using Bit.ServiceDefaults; -#endif namespace Bit.Identity; @@ -11,10 +8,6 @@ public static void Main(string[] args) { var builder = CreateHostBuilder(args); -#if DEBUG - builder.AddServiceDefaults(); -#endif - builder.Build().Run(); } diff --git a/src/Notifications/Notifications.csproj b/src/Notifications/Notifications.csproj index 0337d38c9205..76278fdea83c 100644 --- a/src/Notifications/Notifications.csproj +++ b/src/Notifications/Notifications.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Notifications/Program.cs b/src/Notifications/Program.cs index fb986710d3af..4d2e68dee32d 100644 --- a/src/Notifications/Program.cs +++ b/src/Notifications/Program.cs @@ -1,7 +1,4 @@ using Bit.Core.Utilities; -#if DEBUG -using Bit.ServiceDefaults; -#endif namespace Bit.Notifications; @@ -18,10 +15,6 @@ public static void Main(string[] args) }) .AddSerilogFileLogging(); -#if DEBUG - builder.AddServiceDefaults(); -#endif - builder.Build().Run(); } } From 8fdd6dcf9934d9c553bf541406b97abd992ff818 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:01:36 -0400 Subject: [PATCH 13/19] build(AppHost): update Aspire SDK and add conditional community plugins --- AppHost/AppHost.csproj | 60 ++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/AppHost/AppHost.csproj b/AppHost/AppHost.csproj index c31cb35425a0..dc173966e975 100644 --- a/AppHost/AppHost.csproj +++ b/AppHost/AppHost.csproj @@ -1,27 +1,41 @@ - + - - Exe - net8.0 - enable - enable - e0dba0c6-d131-43bd-9143-2260f11a14ad - + + Exe + net8.0 + enable + enable + e0dba0c6-d131-43bd-9143-2260f11a14ad + - - - - - - - - + + + false + $(DefineConstants);ENABLE_NGROK_COMMUNITY_PLUGIN + false + $(DefineConstants);ENABLE_NODEJS_COMMUNITY_PLUGIN + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + From 59de0625def4fa0d482bd91efad37ed5d1930422 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:01:36 -0400 Subject: [PATCH 14/19] refactor(AppHost): centralize resource orchestration calls to BuilderExtensions --- AppHost/AppHost.cs | 57 +++++++----------------------------- AppHost/BuilderExtensions.cs | 2 +- 2 files changed, 11 insertions(+), 48 deletions(-) diff --git a/AppHost/AppHost.cs b/AppHost/AppHost.cs index a0cc3990f2e1..b6ba7dcc99ba 100644 --- a/AppHost/AppHost.cs +++ b/AppHost/AppHost.cs @@ -1,59 +1,22 @@ -using Bit.AppHost; +using Bit.AppHost; var builder = DistributedApplication.CreateBuilder(args); var secretsSetup = builder.ConfigureSecrets(); -var isSelfHosted = builder.Configuration["globalSettings:selfHosted"]?.ToLowerInvariant() == "true"; - -// Add Pricing Service - use port from pricingUri in secrets -var pricingService = - builder - .AddProject("pricing-service", - builder.Configuration["pricingServiceRelativePath"] - ?? throw new ArgumentNullException("pricingServiceRelativePath", "Missing pricing service relative path")); - -// Add Database and run migrations -var db = builder.AddSqlServerDatabaseResource(isSelfHosted); -builder.ConfigureMigrations(isSelfHosted) +var db = builder.AddSqlServerDatabaseResource(); +builder.ConfigureMigrations() .WaitFor(db) .ExcludeFromManifest() .WaitForCompletion(secretsSetup); - var azurite = builder.ConfigureAzurite(); +var mail = builder.ConfigureMailCatcher(); +var (_, api, billing, _, _) = builder.ConfigureServices(db, secretsSetup, mail, azurite); -// Add MailCatcher -var mail = builder - .AddContainer("mailcatcher", "sj26/mailcatcher:latest") - .WithLifetime(ContainerLifetime.Persistent) - .WithEndpoint(port: 10250, name: "smtp", targetPort: 1025) // SMTP port - .WithHttpEndpoint(port: 1080, name: "web", targetPort: 1080); - +#if ENABLE_NODEJS_COMMUNITY_PLUGIN +builder.ConfigureWebFrontend(api); +#endif -// Add Services -builder.AddBitwardenService(db, secretsSetup, mail, "admin"); -var api = builder.AddBitwardenService(db, secretsSetup, mail, "api") - .WithReference(pricingService) - .WaitFor(azurite); -var billing = builder.AddBitwardenService(db, secretsSetup, mail, "billing"); -builder.AddBitwardenService(db, secretsSetup, mail, "identity"); -builder.AddBitwardenService(db, secretsSetup, mail, "notifications") - .WaitFor(azurite); - -// Add Client Apps -builder.AddBitwardenNpmApp("web-frontend", "web", api) - .WithHttpsEndpoint(8080, 8080, "angular-http", isProxied: false) - .WithUrl("https://bitwarden.test:8080") - .WithExternalHttpEndpoints(); -builder.AddBitwardenNpmApp("desktop-frontend", "desktop", api, "start"); -builder.AddBitwardenNpmApp("browser-frontend", "browser", api, "build:bit:watch:chrome"); - -// Add Ngrok +#if ENABLE_NGROK_COMMUNITY_PLUGIN builder.ConfigureNgrok((billing, "billing-http")); +#endif builder.Build().Run(); - - - - - - - diff --git a/AppHost/BuilderExtensions.cs b/AppHost/BuilderExtensions.cs index 76239e7dc1aa..2523630fffbd 100644 --- a/AppHost/BuilderExtensions.cs +++ b/AppHost/BuilderExtensions.cs @@ -1,4 +1,4 @@ -using Aspire.Hosting.Azure; +using Aspire.Hosting.Azure; using Azure.Provisioning; using Azure.Provisioning.Storage; From a899e3f14934b471a78892a07f5b4592d4c1055c Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:01:36 -0400 Subject: [PATCH 15/19] feat(AppHost): implement configuration-driven resource setup --- AppHost/BuilderExtensions.cs | 251 +++++++++++++++++++-------- AppHost/appsettings.Development.json | 44 +++++ 2 files changed, 222 insertions(+), 73 deletions(-) diff --git a/AppHost/BuilderExtensions.cs b/AppHost/BuilderExtensions.cs index 2523630fffbd..33cb7867be3d 100644 --- a/AppHost/BuilderExtensions.cs +++ b/AppHost/BuilderExtensions.cs @@ -6,41 +6,53 @@ namespace Bit.AppHost; public static class BuilderExtensions { + /// + /// Configures the secrets setup executable resource. + /// + /// The distributed application builder used to configure the secrets setup resource. + /// >The configured resource builder for the secrets setup executable. public static IResourceBuilder ConfigureSecrets(this IDistributedApplicationBuilder builder) { - // Setup secrets before starting services - var secretsScript = builder.Configuration["scripts:secretsSetup"] ?? throw new ArgumentNullException("setupSecretsScriptPath", "Missing setup secrets script path"); - var pricingSecretsPath = builder.Configuration["pricingServiceSecretsPath"] ?? throw new ArgumentNullException("pricingServiceSecretsPath", "Missing secrets path"); - - //Pricing Secrets - builder - .AddExecutable("pricing-setup-secrets", "pwsh", pricingSecretsPath, "-File", secretsScript, "-clear") - .ExcludeFromManifest(); return builder - .AddExecutable("setup-secrets", "pwsh", "../dev", "-File", secretsScript, "-clear") + .AddExecutable("setup-secrets", "pwsh", "../dev", "-File", builder.Required("Scripts:SecretsSetup"), + "-clear") .ExcludeFromManifest(); } - public static IResourceBuilder AddSqlServerDatabaseResource(this IDistributedApplicationBuilder builder, bool isSelfHosted = false) + /// + /// Configures the migrations executable resource. + /// + /// The distributed application builder used to configure the migrations resource. + /// >The configured resource builder for the migrations executable. + public static IResourceBuilder ConfigureMigrations(this IDistributedApplicationBuilder builder) { - var password = isSelfHosted - ? builder.Configuration["dev:selfHostOverride:globalSettings:sqlServer:password"] - : builder.Configuration["globalSettings:sqlServer:password"]; + var migrationArgs = new List { "-File", builder.Required("Scripts:DbMigration") }; + if (builder.IsSelfHosted()) + migrationArgs.Add("-self-hosted"); - // Add MSSQL - retrieve password from connection string in secrets - var dbpassword = builder.AddParameter("dbPassword", password!, secret: true); return builder - .AddSqlServer("mssql", password: dbpassword, 1433) - .WithImage("mssql/server:2022-latest") + .AddExecutable("run-db-migrations", "pwsh", builder.Required("WorkingDirectory"), migrationArgs.ToArray()); + } + + public static IResourceBuilder AddSqlServerDatabaseResource( + this IDistributedApplicationBuilder builder) + { + var isSelfHosted = builder.IsSelfHosted(); + var passwordKey = isSelfHosted ? "Database:SelfHostPassword" : "Database:Password"; + if (!int.TryParse(builder.Required("Database:Port"), out var dbPort)) + throw new InvalidOperationException("Invalid value for Database:Port."); + var dbPassword = builder.AddParameter("dbPassword", builder.Required(passwordKey), secret: true); + return builder + .AddSqlServer("mssql", password: dbPassword, dbPort) + .WithImage(builder.Required("Database:Image")) .WithLifetime(ContainerLifetime.Persistent) .WithDataVolume() - .AddDatabase("vault", isSelfHosted ? "self_host_dev" : "vault_dev"); + .AddDatabase("vault-db", isSelfHosted ? "self_host_dev" : "vault_dev"); } public static IResourceBuilder ConfigureAzurite(this IDistributedApplicationBuilder builder) { - - // https://github.com/dotnet/aspire/discussions/5552 + // For more information about this configuration: https://github.com/dotnet/aspire/discussions/5552 var azurite = builder .AddAzureStorage("azurite").ConfigureInfrastructure(c => { @@ -56,101 +68,194 @@ public static IResourceBuilder ConfigureAzurite(this IDist }) .RunAsEmulator(c => { - c.WithBlobPort(10000). - WithQueuePort(10001). - WithTablePort(10002); + c.WithBlobPort(10000) + .WithQueuePort(10001) + .WithTablePort(10002); }); - var workingDirectory = builder.Configuration["workingDirectory"] ?? throw new ArgumentNullException("workingDirectory", "Missing working directory"); - - //Run Azurite setup - var azuriteSetupScript = - builder - .Configuration["scripts:azuriteSetup"] - ?? throw new ArgumentNullException("azuriteSetupScriptPath", "Missing azurite setup script path"); - builder - .AddExecutable("azurite-setup", "pwsh", workingDirectory, "-File", azuriteSetupScript) + .AddExecutable("azurite-setup", "pwsh", builder.Required("WorkingDirectory"), "-File", + builder.Required("Scripts:AzuriteSetup")) .WaitFor(azurite) .ExcludeFromManifest(); return azurite; } - public static IResourceBuilder ConfigureNgrok(this IDistributedApplicationBuilder builder, (IResourceBuilder, string) tunnelResource) + public static IResourceBuilder ConfigureMailCatcher(this IDistributedApplicationBuilder builder) { - var authToken = builder - .AddParameter("ngrok-auth-token", - builder.Configuration["ngrokAuthToken"] - ?? throw new ArgumentNullException("ngrokAuthToken", "Missing ngrok auth token"), - secret: true); + var image = builder.Required("MailCatcher:Image"); + var imageParts = image.Split(':'); + var imageName = imageParts[0]; + var imageTag = imageParts.Length > 1 ? imageParts[1] : "latest"; - return builder.AddNgrok("billing-webhook-ngrok-endpoint", endpointPort: 59600) - .WithAuthToken(authToken) - .WithTunnelEndpoint(tunnelResource.Item1, tunnelResource.Item2) - .WithExplicitStart(); + if (!int.TryParse(builder.Required("MailCatcher:SmtpPort"), out var smtpPort)) + throw new InvalidOperationException("Invalid value for MailCatcher:SmtpPort."); + if (!int.TryParse(builder.Required("MailCatcher:WebPort"), out var webPort)) + throw new InvalidOperationException("Invalid value for MailCatcher:WebPort."); + + return builder + .AddContainer("mailcatcher", imageName, imageTag) + .WithLifetime(ContainerLifetime.Persistent) + .WithEndpoint(port: smtpPort, name: "smtp", targetPort: 1025) + .WithHttpEndpoint(port: webPort, name: "web", targetPort: webPort); } - public static IResourceBuilder ConfigureMigrations(this IDistributedApplicationBuilder builder, bool isSelfHosted) + /// + /// Configures and initializes the essential services required for the distributed application, + /// including project-specific services such as admin, API, billing, identity, and notifications. + /// + /// The distributed application builder used to configure resources and services. + /// The SQL Server database resource builder. + /// The executable resource builder for configuring secrets. + /// The container resource builder for setting up the mail service. + /// The Azure Storage resource builder used to configure Azurite storage services. + /// A tuple containing resource builders for the admin, API, billing, identity, and notifications projects. + public static ( + IResourceBuilder admin, + IResourceBuilder api, + IResourceBuilder billing, + IResourceBuilder identity, + IResourceBuilder notifications + ) ConfigureServices( + this IDistributedApplicationBuilder builder, + IResourceBuilder db, + IResourceBuilder secretsSetup, + IResourceBuilder mail, + IResourceBuilder azurite) { - var workingDirectory = builder.Configuration["workingDirectory"] ?? - throw new ArgumentNullException("workingDirectory", "Missing working directory"); - var migrationArgs = new List + var admin = builder.AddBitwardenService(db, secretsSetup, mail, "admin"); + var api = builder.AddBitwardenService(db, secretsSetup, mail, "api") + .WaitFor(azurite); + var billing = builder.AddBitwardenService(db, secretsSetup, mail, "billing"); + var identity = builder.AddBitwardenService(db, secretsSetup, mail, "identity"); + var notifications = builder.AddBitwardenService(db, secretsSetup, mail, "notifications") + .WaitFor(azurite); + builder.ConfigureAdditionalProjects(new Dictionary> { - "-File", - builder.Configuration["scripts:dbMigration"] - ?? throw new ArgumentNullException("migrationScriptPath", "Missing migration script path") - }; - if (isSelfHosted) + ["admin"] = admin, + ["api"] = api, + ["billing"] = billing, + ["identity"] = identity, + ["notifications"] = notifications + }); + return (admin, api, billing, identity, notifications); + } + + /// + /// Configures additional projects specified in the configuration under "AdditionalProjects". + /// This allows for dynamic inclusion of projects without code changes, useful for testing or temporary additions. + /// + /// The distributed application builder used to access configuration and add project resources. + /// All registered services keyed by name; each additional project's ReferencedBy list selects which ones receive a reference. + private static void ConfigureAdditionalProjects(this IDistributedApplicationBuilder builder, + IReadOnlyDictionary> services) + { + // Add via user-secrets: dotnet user-secrets set "AdditionalProjects::Path" "" + foreach (var section in builder.Configuration.GetSection("AdditionalProjects").GetChildren()) { - migrationArgs.Add("-self-hosted"); - } + var path = section["Path"]; + if (string.IsNullOrWhiteSpace(path)) + continue; - return builder - .AddExecutable("run-db-migrations", "pwsh", workingDirectory, migrationArgs.ToArray()); + var project = builder.AddProject(section.Key, path); + var referencedBy = section.GetSection("ReferencedBy").GetChildren().Select(c => c.Value).ToHashSet(); + foreach (var (_, service) in services.Where(s => referencedBy.Contains(s.Key))) + service.WithReference(project); + } } - public static IResourceBuilder AddBitwardenService( + /// + /// Adds and configures a Bitwarden service of the specified project type. This includes linking the service to + /// necessary resources such as a database, secrets setup, and optionally a mail service based on the project's name. + /// + /// The distributed application builder used to configure the service. + /// The SQL Server database resource to link to the service. + /// The executable resource responsible for secrets setup. + /// The container resource representing the mail service, used conditionally for specific projects. + /// The unique name of the Bitwarden service being added. + /// The type of project implementing the interface. + /// The configured resource builder for the Bitwarden project resource. + private static IResourceBuilder AddBitwardenService( this IDistributedApplicationBuilder builder, IResourceBuilder db, IResourceBuilder secretsSetup, IResourceBuilder mail, string name) where TProject : IProjectMetadata, new() { + // launchSettings provide the ports for the services var service = builder.AddProject(name) - .WithHttpEndpoint(port: builder.GetBitwardenServicePort(name), name: $"{name}-http") + .WithEndpoint("http", e => e.Port = builder.GetBitwardenServicePort(name)) .WithReference(db) .WaitFor(db) .WaitForCompletion(secretsSetup); if (name is "admin" or "identity" or "billing") - { service.WithReference(mail.GetEndpoint("smtp")); - } return service; } - public static IResourceBuilder AddBitwardenNpmApp(this IDistributedApplicationBuilder builder, - string name, string path, IResourceBuilder api, string scriptName = "build:bit:watch") + private static int GetBitwardenServicePort(this IDistributedApplicationBuilder builder, string serviceName) + { + if (!int.TryParse(builder.Required($"Services:{serviceName}:BasePort"), out var basePort)) + throw new InvalidOperationException($"Invalid port value for Services:{serviceName}:BasePort."); + return builder.IsSelfHosted() ? basePort + 1 : basePort; + } + + /// + /// Retrieves a required configuration value and throws an exception if it's missing. + /// + /// An instance of . + /// The configuration key to retrieve. + /// The configuration value associated with the specified key. + /// + private static string Required(this IDistributedApplicationBuilder builder, string key) => + builder.Configuration[key] ?? throw new InvalidOperationException($"Missing required configuration: {key}"); + + /// + /// Determines if the application is running in self-hosted mode. + /// + /// An instance of . + /// True if the application is self-hosted, otherwise false. + private static bool IsSelfHosted(this IDistributedApplicationBuilder builder) => + builder.Configuration["SelfHost"]?.Equals("true", StringComparison.OrdinalIgnoreCase) == true; + +#if ENABLE_NODEJS_COMMUNITY_PLUGIN + public static void ConfigureWebFrontend(this IDistributedApplicationBuilder builder, + IResourceBuilder api) { - var clientsRelativePath = builder.Configuration["clientsRelativePath"] ?? - throw new ArgumentNullException("clientsRelativePath", "Missing client relative path"); + if (!int.TryParse(builder.Required("WebFrontend:Port"), out var port)) + throw new InvalidOperationException("Invalid value for WebFrontend:Port."); + builder + .AddBitwardenNpmApp("web-frontend", "web", api) + .WithHttpsEndpoint(port, port, "angular-http", isProxied: false) + .WithUrl(builder.Required("WebFrontend:Url")) + .WithExternalHttpEndpoints(); + } + + private static IResourceBuilder AddBitwardenNpmApp(this IDistributedApplicationBuilder builder, + string name, string path, IResourceBuilder api, string scriptName = "build:bit:watch") + { return builder - .AddNpmApp(name, $"{clientsRelativePath}/{path}", scriptName) + .AddNpmApp(name, $"{builder.Required("ClientsPath")}/{path}", scriptName) .WithReference(api) .WaitFor(api) .WithExplicitStart(); } +#endif - public static int GetBitwardenServicePort(this IDistributedApplicationBuilder builder, string serviceName) +#if ENABLE_NGROK_COMMUNITY_PLUGIN + public static void ConfigureNgrok(this IDistributedApplicationBuilder builder, + (IResourceBuilder, string) tunnelResource) { - var isSelfHosted = builder.Configuration["isSelfHosted"] == "true"; - var configKey = isSelfHosted - ? $"dev:selfHostOverride:globalSettings:baseServiceUri:{serviceName}" - : $"globalSettings:baseServiceUri:{serviceName}"; - - var uriString = builder.Configuration[configKey] - ?? throw new InvalidOperationException($"Configuration value for '{configKey}' not found."); + var rawToken = builder.Configuration["NgrokAuthToken"]; + if (string.IsNullOrWhiteSpace(rawToken)) + return; - return new Uri(uriString).Port; + var authToken = builder.AddParameter("ngrok-auth-token", rawToken, secret: true); + builder.AddNgrok("billing-webhook-ngrok-endpoint", endpointPort: 59600) + .WithAuthToken(authToken) + .WithTunnelEndpoint(tunnelResource.Item1, tunnelResource.Item2) + .WithExplicitStart(); } +#endif } diff --git a/AppHost/appsettings.Development.json b/AppHost/appsettings.Development.json index 0c208ae9181e..9946c553aa19 100644 --- a/AppHost/appsettings.Development.json +++ b/AppHost/appsettings.Development.json @@ -4,5 +4,49 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "SelfHost": false, + "ClientsPath": "../../clients/apps", + "WorkingDirectory": "../dev", + "Services": { + "admin": { + "BasePort": "" + }, + "api": { + "BasePort": "" + }, + "billing": { + "BasePort": "" + }, + "identity": { + "BasePort": "" + }, + "notifications": { + "BasePort": "" + } + }, + "AdditionalProjects": { + }, + "Database": { + "Type": "MsSql", + "Image": "mssql/server:2022-latest", + "Port": 1433, + "Password": "", + "SelfHostPassword": "" + }, + "Scripts": { + "DbMigration": "migrate.ps1", + "AzuriteSetup": "setup_azurite.ps1", + "SecretsSetup": "setup_secrets.ps1" + }, + "NgrokAuthToken": "", + "MailCatcher": { + "Image": "sj26/mailcatcher:latest", + "SmtpPort": 10250, + "WebPort": 1080 + }, + "WebFrontend": { + "Port": 8080, + "Url": "https://bitwarden.test:8080" } } From 4fa8a32edac17ab7e1924c15d1c756057438ecdc Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:01:36 -0400 Subject: [PATCH 16/19] chore(dev): remove obsolete configuration from development files --- dev/secrets.json.example | 10 ---------- dev/setup_secrets.ps1 | 1 - 2 files changed, 11 deletions(-) diff --git a/dev/secrets.json.example b/dev/secrets.json.example index 65996ff71fb7..477807d877cc 100644 --- a/dev/secrets.json.example +++ b/dev/secrets.json.example @@ -65,14 +65,4 @@ } } } - "ngrokAuthToken": "", - "workingDirectory": "../dev", - "clientsRelativePath": "", - "pricingServiceSecretsPath": "", - "pricingServiceRelativePath": "", - "scripts": { - "dbMigration": "migrate.ps1", - "azuriteSetup": "setup_azurite.ps1", - "secretsSetup": "setup_secrets.ps1" - }, } diff --git a/dev/setup_secrets.ps1 b/dev/setup_secrets.ps1 index b6647d68edd3..88327e9c0a87 100755 --- a/dev/setup_secrets.ps1 +++ b/dev/setup_secrets.ps1 @@ -29,7 +29,6 @@ $projects = @{ IntegrationTests = "../test/Infrastructure.IntegrationTest" SeederApi = "../util/SeederApi" SeederUtility = "../util/SeederUtility" - AppHost = "../AppHost" } foreach ($key in $projects.keys) { From 3d87c72e29da1a12d975c5dded996a21318037ed Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:17:43 -0400 Subject: [PATCH 17/19] fix(billing): run dotnet format --- AppHost/AppHost.cs | 2 +- AppHost/BuilderExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AppHost/AppHost.cs b/AppHost/AppHost.cs index b6ba7dcc99ba..e771e06dd023 100644 --- a/AppHost/AppHost.cs +++ b/AppHost/AppHost.cs @@ -1,4 +1,4 @@ -using Bit.AppHost; +using Bit.AppHost; var builder = DistributedApplication.CreateBuilder(args); var secretsSetup = builder.ConfigureSecrets(); diff --git a/AppHost/BuilderExtensions.cs b/AppHost/BuilderExtensions.cs index 33cb7867be3d..7fbd16574e9a 100644 --- a/AppHost/BuilderExtensions.cs +++ b/AppHost/BuilderExtensions.cs @@ -1,4 +1,4 @@ -using Aspire.Hosting.Azure; +using Aspire.Hosting.Azure; using Azure.Provisioning; using Azure.Provisioning.Storage; From 5334991b3c214d77ec40e8d1a7a01f58060b0211 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:28:14 -0400 Subject: [PATCH 18/19] fix(billing): revert changes --- dev/secrets.json.example | 18 +----------------- src/Admin/Program.cs | 8 ++++---- src/Api/Api.csproj | 2 +- src/Api/Program.cs | 8 ++++---- src/Billing/Program.cs | 9 ++++----- src/Identity/Program.cs | 7 +++---- src/Notifications/Program.cs | 8 ++++---- 7 files changed, 21 insertions(+), 39 deletions(-) diff --git a/dev/secrets.json.example b/dev/secrets.json.example index 477807d877cc..7bf753e93834 100644 --- a/dev/secrets.json.example +++ b/dev/secrets.json.example @@ -11,24 +11,8 @@ }, "globalSettings": { "selfHosted": true, - "launchDarkly": { - "flagValues": { - } - }, - "pricingUri": "", - "baseServiceUri": { - "cloudRegion": "", - "vault": "", - "api": "", - "identity": "", - "admin": "", - "notifications": "", - "sso": "", - "billing": "" - }, "sqlServer": { - "connectionString": "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True", - "password": + "connectionString": "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True" }, "postgreSql": { "connectionString": "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev;Include Error Detail=true" diff --git a/src/Admin/Program.cs b/src/Admin/Program.cs index 1a8f78dd2b2c..80a1ae058ca5 100644 --- a/src/Admin/Program.cs +++ b/src/Admin/Program.cs @@ -6,7 +6,7 @@ public class Program { public static void Main(string[] args) { - var builder = Host + Host .CreateDefaultBuilder(args) .UseBitwardenSdk() .ConfigureWebHostDefaults(webBuilder => @@ -17,8 +17,8 @@ public static void Main(string[] args) }); webBuilder.UseStartup(); }) - .AddSerilogFileLogging(); - - builder.Build().Run(); + .AddSerilogFileLogging() + .Build() + .Run(); } } diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index dcff2f3e16dc..b0be357c0895 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -1,4 +1,4 @@ - + diff --git a/src/Api/Program.cs b/src/Api/Program.cs index e90b4c62fa6f..baeaab9fdbb4 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -6,15 +6,15 @@ public class Program { public static void Main(string[] args) { - var builder = Host + Host .CreateDefaultBuilder(args) .UseBitwardenSdk() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .AddSerilogFileLogging(); - - builder.Build().Run(); + .AddSerilogFileLogging() + .Build() + .Run(); } } diff --git a/src/Billing/Program.cs b/src/Billing/Program.cs index fecb71e7bbe1..334dc49368dc 100644 --- a/src/Billing/Program.cs +++ b/src/Billing/Program.cs @@ -6,16 +6,15 @@ public class Program { public static void Main(string[] args) { - var builder = Host + Host .CreateDefaultBuilder(args) .UseBitwardenSdk() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .AddSerilogFileLogging(); - - builder.Build().Run(); + .AddSerilogFileLogging() + .Build() + .Run(); } } - diff --git a/src/Identity/Program.cs b/src/Identity/Program.cs index 8a68d6a66fb8..ae284c86f28e 100644 --- a/src/Identity/Program.cs +++ b/src/Identity/Program.cs @@ -6,10 +6,9 @@ public class Program { public static void Main(string[] args) { - var builder = CreateHostBuilder(args); - - builder.Build().Run(); - + CreateHostBuilder(args) + .Build() + .Run(); } public static IHostBuilder CreateHostBuilder(string[] args) diff --git a/src/Notifications/Program.cs b/src/Notifications/Program.cs index 4d2e68dee32d..ec7ea67fdaf2 100644 --- a/src/Notifications/Program.cs +++ b/src/Notifications/Program.cs @@ -6,15 +6,15 @@ public class Program { public static void Main(string[] args) { - var builder = Host + Host .CreateDefaultBuilder(args) .UseBitwardenSdk() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .AddSerilogFileLogging(); - - builder.Build().Run(); + .AddSerilogFileLogging() + .Build() + .Run(); } } From ed09ed187f984866b5f1790aa55e52fb81aae7e3 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 28 Apr 2026 17:30:41 -0400 Subject: [PATCH 19/19] fix: solution spacing --- bitwarden-server.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitwarden-server.sln b/bitwarden-server.sln index a687297035db..87d1a0341fc8 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -387,7 +387,7 @@ Global {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EEFA4FC-4EEC-4E3F-8ED7-28FD33201701}.Release|Any CPU.Build.0 = Release|Any CPU -{5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A3AB73D-F0E8-4DC6-B072-0D3B394621ED}.Release|Any CPU.Build.0 = Release|Any CPU