diff --git a/CHANGELOG.md b/CHANGELOG.md index fd72f90..62fca62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.3.9] - 2026-04-26 + +### Fixed + +- **Startup / database:** FluentMigrator (`EnsureCertsEngineMigratedAsync`) now runs in `Program.cs` immediately after `WebApplication.Build()` and before `RunAsync`, so schema (including `app_runtime_leases`) exists before any `IHostedService` starts. `InitializationHostedService` only performs bootstrap lease + identity init. + ## [3.3.8] - 2026-04-26 ### Fixed diff --git a/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs b/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs index 1aec7af..8ea5ab9 100644 --- a/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs +++ b/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs @@ -4,11 +4,11 @@ using Microsoft.Extensions.Hosting; namespace MaksIT.CertsUI.Engine.Extensions; /// -/// DB migrations are handled by FluentMigrator and optional schema sync from InitializationHostedService. -/// This method is a no-op for backward compatibility with host startup. +/// DB migrations run in Program.cs via before RunAsync. +/// This method is a no-op kept for backward compatibility with older host wiring. /// public static class ApplicationBuilderExtensions { public static void AddCertsEngineMigrations(this IHost host) { - // No-op: migrations and schema sync run from InitializationHostedService via IRunMigrationsService and ISchemaSyncService. + // No-op: see Program.cs (migrations) and InitializationHostedService (identity bootstrap under lease). } } diff --git a/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs b/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs index b2da0fa..9622088 100644 --- a/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs +++ b/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs @@ -67,7 +67,7 @@ public static class ServiceCollectionExtensions { #region Host initialization helpers /// - /// Runs FluentMigrator then optional add-only schema sync (when is true). Called from host startup (e.g. InitializationHostedService). + /// Runs FluentMigrator then optional add-only schema sync (when is true). Called from Program.cs before RunAsync. /// public static async Task EnsureCertsEngineMigratedAsync(this IServiceProvider serviceProvider) { await using var scope = serviceProvider.CreateAsyncScope(); diff --git a/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs b/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs index fc1e4dc..0516686 100644 --- a/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs +++ b/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs @@ -1,15 +1,14 @@ using Microsoft.Extensions.Options; using MaksIT.CertsUI.Engine.DomainServices; -using MaksIT.CertsUI.Engine.Extensions; using MaksIT.CertsUI.Engine.Infrastructure; using MaksIT.CertsUI.Engine.RuntimeCoordination; namespace MaksIT.CertsUI.HostedServices; /// -/// Runs startup initialization (migrations + identity bootstrap) before the API starts serving requests. -/// FluentMigrator runs first on every instance (same pattern as Vault); the bootstrap lease then ensures -/// only one replica performs identity bootstrap against shared . +/// Runs identity bootstrap before the API starts serving requests. FluentMigrator already ran in Program.cs +/// before the host starts. The bootstrap lease ensures only one replica writes against shared +/// . /// public sealed class InitializationHostedService( ILogger logger, @@ -23,18 +22,11 @@ public sealed class InitializationHostedService( public async Task StartAsync(CancellationToken cancellationToken) { const int delayMilliseconds = 2000; - var migrationsApplied = false; while (!cancellationToken.IsCancellationRequested) { try { logger.LogInformation("Running startup initialization..."); - // Migrations must run before lease acquisition: app_runtime_leases is created by FluentMigrator. - if (!migrationsApplied) { - await serviceProvider.EnsureCertsEngineMigratedAsync().ConfigureAwait(false); - migrationsApplied = true; - } - var holder = runtimeInstance.InstanceId; var acquired = await runtimeLease.TryAcquireAsync(RuntimeLeaseNames.Bootstrap, holder, BootstrapLeaseTtl, cancellationToken).ConfigureAwait(false); if (!acquired.IsSuccess) diff --git a/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj b/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj index b03c9c6..e446068 100644 --- a/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj +++ b/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj @@ -1,7 +1,7 @@ - 3.3.8 + 3.3.9 net10.0 enable enable diff --git a/src/MaksIT.CertsUI/Program.cs b/src/MaksIT.CertsUI/Program.cs index 56bfaa8..2866dcf 100644 --- a/src/MaksIT.CertsUI/Program.cs +++ b/src/MaksIT.CertsUI/Program.cs @@ -135,6 +135,9 @@ builder.Services.AddHealthChecks() var app = builder.Build(); +// FluentMigrator must complete before any IHostedService starts; bootstrap lease uses app_runtime_leases. +await app.Services.EnsureCertsEngineMigratedAsync(); + app.UseMiddleware(); app.AddCertsEngineMigrations(); @@ -169,4 +172,4 @@ app.MapGet("/health/ready", async (CancellationToken ct) => { } }); -app.Run(); +await app.RunAsync();