ci / build-test (push) Successful in 1m9s
Adds the button, plus POST /api/v1/system/update for driving it from Home Assistant or curl. Both pull the latest source, rebuild and restart the service. The gating is the substance of this change. The updater builds whatever is on the branch and the LXC runs MeterVault as root, so triggering it is root-equivalent on that host, and the web UI has no login — "reachable from the dashboard" alone would mean any device on the network could take the machine. Three independent conditions must hold before anything runs: the operator set MeterVault__AllowInAppUpdate, at least one API key is configured, and the caller presented one, compared in constant time so retries cannot time out the key. AllowAnonymousApi cannot reach it. That flag opens reads, and opening reads must not open root, so the endpoint checks the presented key itself rather than relying on the shared group filter that honours it. Availability is re-checked inside LaunchAsync rather than trusting the caller to have done so. The UI button asks for the key every time instead of remembering it: with no login, a browser left open on the dashboard would otherwise be a standing permission to execute code on the host. The key is cleared from component state immediately, and a wrong key and a keyless deployment give the same message so an unauthenticated caller cannot tell them apart. Launched detached through systemd-run: the updater restarts the service, so a child process would be killed part-way through, leaving the app down with a half-published build. --collect reaps the transient unit so a later update is not blocked by the remains of the previous one. Off by default, and where there is no /usr/bin/update — a container, a dev box — it reports that rather than half-running something. Tests pin every refusal, including through the real HTTP pipeline; none of them launch anything. Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
71 lines
3.4 KiB
C#
71 lines
3.4 KiB
C#
using MeterVault.Core.Normalization;
|
|
using MeterVault.Infrastructure.Import;
|
|
using MeterVault.Infrastructure.Ingestion;
|
|
using MeterVault.Infrastructure.Normalization;
|
|
using MeterVault.Infrastructure.Persistence;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace MeterVault.Infrastructure;
|
|
|
|
/// <summary>Composition root for the infrastructure layer (persistence, ingestion, import).</summary>
|
|
public static class DependencyInjection
|
|
{
|
|
public static IServiceCollection AddMeterVaultInfrastructure(
|
|
this IServiceCollection services,
|
|
string connectionString)
|
|
{
|
|
// A factory (for per-operation contexts in Blazor components/read services — a shared
|
|
// circuit-scoped DbContext is not thread-safe) plus a scoped context (from the factory)
|
|
// for request/tick-scoped services (API, workers, importers) that inject it directly.
|
|
services.AddDbContextFactory<MeterVaultDbContext>(options =>
|
|
options
|
|
.UseNpgsql(connectionString, npgsql =>
|
|
npgsql.MigrationsAssembly(typeof(MeterVaultDbContext).Assembly.FullName))
|
|
.UseSnakeCaseNamingConvention());
|
|
services.AddScoped<MeterVaultDbContext>(sp =>
|
|
sp.GetRequiredService<IDbContextFactory<MeterVaultDbContext>>().CreateDbContext());
|
|
|
|
services.AddSingleton<INormalizationEngine>(_ => NormalizationEngine.CreateDefault());
|
|
services.AddScoped<NormalizationService>();
|
|
services.AddScoped<CsvImporter>();
|
|
services.AddScoped<ImportService>();
|
|
services.AddScoped<ReferenceDataImporter>();
|
|
services.AddScoped<IngestionService>();
|
|
services.AddScoped<MqttMessageRouter>();
|
|
// HttpClient + HA tester are available even with live ingestion off, so the admin
|
|
// "Test connection" works without the background workers running.
|
|
services.AddHttpClient();
|
|
services.AddScoped<HaConnectionTester>();
|
|
// Singleton: wraps one IDataProtector, and the ingestion workers (themselves singletons)
|
|
// resolve connector secrets on every reconnect.
|
|
services.AddSingleton<Security.SecretProtector>();
|
|
// Singleton: it caches the last answer so the dashboard never waits on a remote call.
|
|
services.AddSingleton<Update.UpdateCheckService>();
|
|
services.AddSingleton<Update.UpdateRunner>();
|
|
services.AddScoped<Costing.CostService>();
|
|
services.AddScoped<Dashboard.DashboardService>();
|
|
services.AddScoped<Dashboard.SolarService>();
|
|
services.AddScoped<Dashboard.MeterPeriodService>();
|
|
services.AddScoped<Dashboard.ConsumableService>();
|
|
services.AddScoped<Dashboard.MeterDetailService>();
|
|
services.AddScoped<Dashboard.FlowService>();
|
|
services.AddScoped<Backup.ExportService>();
|
|
|
|
return services;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the live-ingestion background workers (MQTT/Tasmota + Home Assistant). Kept
|
|
/// separate from <see cref="AddMeterVaultInfrastructure"/> so tests can opt out of brokers.
|
|
/// </summary>
|
|
public static IServiceCollection AddMeterVaultIngestion(this IServiceCollection services)
|
|
{
|
|
services.AddHttpClient();
|
|
services.AddHostedService<MqttIngestionWorker>();
|
|
services.AddHostedService<HomeAssistantWorker>();
|
|
services.AddHostedService<HomeAssistantWebSocketWorker>();
|
|
return services;
|
|
}
|
|
}
|