Public Access
The API deliberately never migrated: it failed readiness while a migration was pending and named it, and a separate step applied them. That is the right split for a deployment with a release pipeline and the wrong one for a self-hosted server, where it means an image that boots, refuses traffic, and waits for somebody to know that dotnet ef exists. The schema and the code that expects it ship in the same image, so the image is where the two are reconciled now. Before RunAsync rather than in the background. A migration racing the first requests would let them through against a half-applied schema, and the first authenticated request is the one that provisions accounts. Failing to migrate therefore fails to start, which is the loudest signal available and the one an orchestrator already acts on. Concurrent starts take a Postgres advisory lock first. Without it two replicas rolled out together read the same empty history table, both apply the same migration, and the second dies on an object that already exists — a crash loop on the day of a schema change, which is the worst day to have one. The lock is held on a connection of its own because EF opens and closes one per command, and a session lock belongs to the connection that took it. The exception is a database that does not exist yet: there is nothing to hold a lock in, so that path migrates without one and says so. Two instances creating it at once still converges — one wins, the other restarts into the ordinary locked path — and refusing to start would leave a fresh deployment stuck on the step this removes. Database:AutoMigrate turns it off for the deployments that own their schema: a migrator job, a rollout where new code must run against the old schema first, or a database user denied DDL. With it off the behaviour is exactly what it was, and the health check now explains which of the two situations a pending migration means. Verified against a throwaway PostgreSQL container: an empty database gets all seven migrations applied before the port opens, the tables land in the dodo schema, and a second start logs the schema up to date and serves. The API suite passes, which exercises the startup path once per assembly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
75 lines
3.1 KiB
C#
75 lines
3.1 KiB
C#
namespace DodoSSH.Api.Setup;
|
|
|
|
/// <summary>
|
|
/// Source-generated log messages for startup and health.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <c>[LoggerMessage]</c> rather than <c>ILogger</c> extension calls: allocation-free, and the
|
|
/// event ids and message templates become a stable, greppable inventory instead of string literals
|
|
/// scattered through wiring code.
|
|
/// </remarks>
|
|
internal static partial class StartupLog
|
|
{
|
|
[LoggerMessage(
|
|
EventId = 1001,
|
|
Level = LogLevel.Warning,
|
|
Message = "Oidc:RequireHttpsMetadata is false outside Development. Token signing keys are "
|
|
+ "being fetched over plaintext HTTP, so anyone on the network path can serve their "
|
|
+ "own keys and mint tokens this server will accept.")]
|
|
internal static partial void InsecureMetadataOutsideDevelopment(ILogger logger);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1002,
|
|
Level = LogLevel.Warning,
|
|
Message = "Oidc:AllowEmailLinking is enabled. A token from any configured provider "
|
|
+ "carrying a victim's email address will be linked to that victim's existing account. "
|
|
+ "Only enable this when every configured provider verifies email ownership.")]
|
|
internal static partial void EmailLinkingEnabled(ILogger logger);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1010,
|
|
Level = LogLevel.Critical,
|
|
Message = "Database schema is out of date: {PendingCount} migration(s) pending "
|
|
+ "({PendingMigrations}). Readiness will fail until they are applied.")]
|
|
internal static partial void PendingMigrations(
|
|
ILogger logger,
|
|
int pendingCount,
|
|
string pendingMigrations);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1011,
|
|
Level = LogLevel.Information,
|
|
Message = "Database schema is up to date.")]
|
|
internal static partial void SchemaUpToDate(ILogger logger);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1012,
|
|
Level = LogLevel.Information,
|
|
Message = "Applying {PendingCount} pending migration(s): {PendingMigrations}.")]
|
|
internal static partial void ApplyingMigrations(
|
|
ILogger logger,
|
|
int pendingCount,
|
|
string pendingMigrations);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1013,
|
|
Level = LogLevel.Information,
|
|
Message = "Applied {AppliedCount} migration(s). The schema is now up to date.")]
|
|
internal static partial void MigrationsApplied(ILogger logger, int appliedCount);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1014,
|
|
Level = LogLevel.Information,
|
|
Message = "Database:AutoMigrate is false. This instance will not touch the schema, and will "
|
|
+ "fail readiness for as long as a migration is pending.")]
|
|
internal static partial void AutoMigrateDisabled(ILogger logger);
|
|
|
|
[LoggerMessage(
|
|
EventId = 1015,
|
|
Level = LogLevel.Warning,
|
|
Message = "The database does not exist yet, so migrations run without the advisory lock that "
|
|
+ "normally serialises replicas. Two instances creating it at once will have one of them "
|
|
+ "fail and restart into the ordinary path.")]
|
|
internal static partial void MigratingUnlockedDatabase(ILogger logger);
|
|
}
|