using ZB.MOM.WW.Secrets.DependencyInjection;
using ZB.MOM.WW.Secrets.Replicator.SqlServer.DependencyInjection;
namespace ZB.MOM.WW.ScadaBridge.Host;
///
/// Single composition-root entry point for the host container's secret store, shared by the
/// central-role registrations in Program.cs and the site-role registrations in
/// so both roles wire secrets identically.
///
///
///
/// Clustered replication is opt-in and off by default. ScadaBridge is hub-and-spoke — one
/// central cluster plus N separate site clusters — and hub mode gives each node a LOCAL store that
/// syncs bidirectionally with a shared central SQL hub, so a site keeps resolving secrets straight
/// through a WAN outage.
///
///
/// The gate is not a style preference. AddZbSecretsSqlServerReplication validates its options
/// EAGERLY at registration time, so calling it unconditionally would throw at startup on every node
/// where Secrets:SqlServer:ConnectionString is unset — which is every dev box, every docker
/// node and every existing deployment. Both the explicit Secrets:Replication:Enabled flag and
/// a non-blank connection string are therefore required before the SQL-Server package is touched at
/// all; otherwise this is byte-for-byte the plain local-SQLite registration it has always been.
///
///
/// Bootstrap constraint. The hub connection string is itself a secret, and it can never come
/// from the hub — a node cannot read the hub to learn how to reach the hub. It must arrive from
/// outside the replicated set: an environment variable, or a ${secret:} reference that is
/// seeded in that node's own LOCAL store (the pre-host expander in Program.cs runs against a
/// plain local SQLite store before the host container exists, so such a reference does resolve).
/// That expander is deliberately left un-replicated for exactly this reason.
///
///
public static class SecretsRegistration
{
/// Configuration section holding the core secrets options.
public const string SecretsSectionPath = "Secrets";
/// Configuration section holding the SQL-Server hub options.
public const string SqlServerSectionPath = "Secrets:SqlServer";
/// Configuration key gating clustered replication. Absent or false = off.
public const string ReplicationEnabledKey = "Secrets:Replication:Enabled";
/// Configuration key holding the shared SQL-Server hub connection string.
public const string HubConnectionStringKey = "Secrets:SqlServer:ConnectionString";
///
/// Registers the host container's secret store: a plain local SQLite store by default, or a
/// local store replicating against a shared SQL-Server hub when replication is explicitly
/// enabled and a hub connection string is present.
///
/// The service collection to register into.
/// Application configuration for options binding.
/// The same instance, for chaining.
public static IServiceCollection AddScadaBridgeSecrets(
this IServiceCollection services,
IConfiguration config)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(config);
var enabled = config.GetValue(ReplicationEnabledKey);
var connectionString = config[HubConnectionStringKey];
if (enabled && !string.IsNullOrWhiteSpace(connectionString))
{
// Registers the validated hub options, decorates ISecretStore with
// ReplicatingSecretStore, and adds the hub-schema migration + bidirectional sweep
// hosted services. Calls AddZbSecrets internally — do NOT also call it here.
return services.AddZbSecretsSqlServerReplication(
config, SecretsSectionPath, SqlServerSectionPath);
}
if (enabled)
{
// Asked for replication but gave nothing to replicate against. Falling back to the
// local-only store keeps the node up, but it is silently NOT participating in the
// cluster, so say so loudly rather than letting it look healthy.
Serilog.Log.Warning(
"{Key} is true but {ConnKey} is empty — clustered secret replication is DISABLED "
+ "and this node's secrets are local-only. The hub connection string cannot come "
+ "from the hub itself; supply it via environment variable or seed it in this "
+ "node's local store.",
ReplicationEnabledKey,
HubConnectionStringKey);
}
return services.AddZbSecrets(config, SecretsSectionPath);
}
}