using LANCommander.SDK.Extensions; using LANCommander.Server.Settings.Enums; using LANCommander.Server.Settings.Models; using Microsoft.Extensions.Options; namespace LANCommander.Server.Startup; public static class ApplicationSettings { public static WebApplicationBuilder AddSettings(this WebApplicationBuilder builder) { var configuration = builder.Configuration.ReadFromFile(); var refresher = builder.Configuration.ReadFromServer(configuration); builder.Services.AddSingleton(refresher); builder.Services.Configure(builder.Configuration); // .NET 9's ConfigurationBinder treats IEnumerable as an "immutable array-compatible // interface" and calls BindArray, which CONCATENATES the existing default values with the // YAML values on every binding. This causes the collections to grow on each reload. // The post-configure deduplicates them to prevent unbounded growth. builder.Services.AddSingleton>( new SettingsDeduplicatePostConfigure()); return builder; } private sealed class SettingsDeduplicatePostConfigure : IPostConfigureOptions { public void PostConfigure(string? name, Settings.Settings options) { var launcher = options.Server.Launcher; if (launcher.Architectures != null) launcher.Architectures = launcher.Architectures.Distinct().ToArray(); if (launcher.Platforms != null) launcher.Platforms = launcher.Platforms.Distinct().ToArray(); var logs = options.Server.Logs; if (logs.Providers != null) logs.Providers = logs.Providers.DistinctBy(p => p.Name).ToList(); var media = options.Server.Media; if (media.MediaTypes != null) media.MediaTypes = media.MediaTypes.DistinctBy(m => m.Type).ToList(); var gameServers = options.Server.GameServers; if (gameServers.ServerEngines != null) gameServers.ServerEngines = gameServers.ServerEngines.DistinctBy(e => e.Name).ToList(); } } public static async Task ValidateSettings(this WebApplication app) { var settingsProvider = app.Services.GetRequiredService>(); var settings = app.Services.GetRequiredService>(); var election = app.Services.GetRequiredService(); var logger = app.Services.GetRequiredService>(); if (settings.Value.Server.Authentication.TokenSecret.Length < 16) { if (await election.TryAcquireAsync()) { logger.LogDebug("JWT token secret is too short. Regenerating..."); settingsProvider.Update(s => { s.Server.Authentication.TokenSecret = Guid.NewGuid().ToString(); }); } } return app; } }