//----------------------------------------------------------------------- // // Copyright (c) aliasvault. All rights reserved. // Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. // //----------------------------------------------------------------------- namespace AliasServerDb; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Npgsql; /// /// The PostgreSQL DbContext factory. /// public class PostgresqlDbContextFactory : IAliasServerDbContextFactory { private readonly IConfiguration _configuration; /// /// Initializes a new instance of the class. /// /// The configuration. public PostgresqlDbContextFactory(IConfiguration configuration) { _configuration = configuration; } /// public AliasServerDbContext CreateDbContext() { var optionsBuilder = new DbContextOptionsBuilder(); ConfigureDbContextOptions(optionsBuilder); return new AliasServerDbContext(optionsBuilder.Options); } /// public Task CreateDbContextAsync(CancellationToken cancellationToken = default) { return Task.FromResult(CreateDbContext()); } /// public void ConfigureDbContextOptions(DbContextOptionsBuilder optionsBuilder) { AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); // Check environment variable first. var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__AliasServerDbContext"); // If no environment variable, fall back to configuration. if (string.IsNullOrEmpty(connectionString)) { connectionString = _configuration.GetConnectionString("AliasServerDbContext"); } // If running in container override the connection string with the one from the secret file if (IsRunningInContainer()) { try { // Parse the existing connection string var builder = new NpgsqlConnectionStringBuilder(); // Check for environment variables first, fall back to defaults if not set builder.Host = Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "postgres"; builder.Database = Environment.GetEnvironmentVariable("POSTGRES_DATABASE") ?? "aliasvault"; builder.Username = Environment.GetEnvironmentVariable("POSTGRES_USER") ?? "aliasvault"; var portEnvString = Environment.GetEnvironmentVariable("POSTGRES_PORT"); builder.Port = int.TryParse(portEnvString, out var port) ? port : 5432; builder.Password = GetPostgresPasswordFromSecretFile(); // Build the connection string with the new password connectionString = builder.ConnectionString; } catch (Exception ex) { // Log the error but don't fail - use the original connection string Console.WriteLine($"Warning: Failed to override PostgreSQL password from secret file: {ex.Message}"); } } optionsBuilder .UseNpgsql(connectionString, options => options.CommandTimeout(60)) .UseLazyLoadingProxies(); } /// /// Determines if the application is running in a container. /// /// True if running in a container, false otherwise. private static bool IsRunningInContainer() { return Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; } /// /// Gets the PostgreSQL password from the secret file. /// /// The PostgreSQL password. /// Thrown when the PostgreSQL password cannot be found. private static string GetPostgresPasswordFromSecretFile() { const string secretsFilePath = "/secrets/postgres_password"; if (!File.Exists(secretsFilePath)) { throw new KeyNotFoundException($"PostgreSQL password file not found at {secretsFilePath}. Container initialization may have failed."); } var secretValue = File.ReadAllText(secretsFilePath).Trim(); if (string.IsNullOrEmpty(secretValue)) { throw new KeyNotFoundException($"PostgreSQL password file at {secretsFilePath} is empty."); } return secretValue; } }