8080 is a very common, collision-prone port. 8760 (hours in a year) is an uncommon default that also avoids Home Assistant's 8123. Applied across Dockerfile (ASPNETCORE_URLS/EXPOSE), compose (mapping + healthcheck), the Unraid template, README and a code comment. The METERVAULT_PORT host override still works. Claude-Session: https://claude.ai/code/session_01Kib2MniVFbD95fkgLgBBnB
119 lines
4.0 KiB
C#
119 lines
4.0 KiB
C#
using MeterVault.App.Api;
|
|
using MeterVault.App.Components;
|
|
using MeterVault.Infrastructure;
|
|
using MeterVault.Infrastructure.Options;
|
|
using MeterVault.Infrastructure.Persistence;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using MudBlazor.Services;
|
|
using Serilog;
|
|
|
|
Log.Logger = new LoggerConfiguration()
|
|
.WriteTo.Console()
|
|
.CreateBootstrapLogger();
|
|
|
|
try
|
|
{
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Host.UseSerilog((context, services, configuration) => configuration
|
|
.ReadFrom.Configuration(context.Configuration)
|
|
.ReadFrom.Services(services)
|
|
.Enrich.FromLogContext()
|
|
.WriteTo.Console());
|
|
|
|
builder.Services.Configure<MeterVaultOptions>(
|
|
builder.Configuration.GetSection(MeterVaultOptions.SectionName));
|
|
|
|
var connectionString = builder.Configuration.GetConnectionString("Default")
|
|
?? "Host=localhost;Port=5432;Database=metervault;Username=metervault;Password=metervault";
|
|
builder.Services.AddMeterVaultInfrastructure(connectionString);
|
|
|
|
var options = builder.Configuration.GetSection(MeterVaultOptions.SectionName).Get<MeterVaultOptions>()
|
|
?? new MeterVaultOptions();
|
|
if (options.EnableLiveIngestion)
|
|
{
|
|
builder.Services.AddMeterVaultIngestion();
|
|
}
|
|
|
|
builder.Services.AddMudServices();
|
|
builder.Services.AddRazorComponents()
|
|
.AddInteractiveServerComponents();
|
|
|
|
// Serialize/accept enums as strings on the REST API (e.g. event Type "Delivery").
|
|
builder.Services.ConfigureHttpJsonOptions(o =>
|
|
o.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()));
|
|
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen(c =>
|
|
c.SwaggerDoc("v1", new() { Title = "MeterVault API", Version = "v1" }));
|
|
|
|
var app = builder.Build();
|
|
|
|
await MigrateDatabaseAsync(app).ConfigureAwait(false);
|
|
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
|
app.UseSerilogRequestLogging();
|
|
app.UseReverseProxyTrust();
|
|
// No HTTPS redirection: the app serves plain HTTP (port 8760) behind a reverse proxy
|
|
// that terminates TLS (SDD §10). HTTPS redirection here would break the container and proxy.
|
|
app.UseAntiforgery();
|
|
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "MeterVault API v1"));
|
|
|
|
app.MapStaticAssets();
|
|
app.MapRazorComponents<App>()
|
|
.AddInteractiveServerRenderMode();
|
|
|
|
app.MapMeterVaultApi();
|
|
|
|
// Liveness/readiness probe for Gatus/Compose healthchecks (SDD §9).
|
|
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));
|
|
|
|
await app.RunAsync().ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Fatal(ex, "MeterVault terminated unexpectedly");
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
await Log.CloseAndFlushAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
static async Task MigrateDatabaseAsync(WebApplication app)
|
|
{
|
|
var options = app.Configuration
|
|
.GetSection(MeterVaultOptions.SectionName)
|
|
.Get<MeterVaultOptions>() ?? new MeterVaultOptions();
|
|
|
|
if (!options.RunMigrationsAtStartup)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await using var scope = app.Services.CreateAsyncScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<MeterVaultDbContext>();
|
|
await db.Database.MigrateAsync().ConfigureAwait(false);
|
|
await DatabaseSeeder.SeedAsync(db).ConfigureAwait(false);
|
|
Log.Information("Database migrations applied and defaults seeded");
|
|
|
|
if (options.SeedReferenceData)
|
|
{
|
|
var importer = scope.ServiceProvider.GetRequiredService<MeterVault.Infrastructure.Import.ReferenceDataImporter>();
|
|
var dir = Path.Combine(AppContext.BaseDirectory, "sampledata");
|
|
await importer.LoadAsync(dir).ConfigureAwait(false);
|
|
Log.Information("Reference dataset ensured (SeedReferenceData=true)");
|
|
}
|
|
}
|
|
|
|
/// <summary>Exposed for WebApplicationFactory-based integration tests.</summary>
|
|
public partial class Program;
|