diff --git a/src/Tgstation.Server.Host/Configuration/Validators/GeneralConfigValidator.cs b/src/Tgstation.Server.Host/Configuration/Validators/GeneralConfigValidator.cs
new file mode 100644
index 0000000000..990d7b950f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Configuration/Validators/GeneralConfigValidator.cs
@@ -0,0 +1,24 @@
+using System;
+
+using Microsoft.Extensions.Options;
+
+namespace Tgstation.Server.Host.Configuration.Validators
+{
+ ///
+ /// Configuration validator for .
+ ///
+ sealed class GeneralConfigValidator : IValidateOptions
+ {
+ ///
+ public ValidateOptionsResult Validate(string? name, GeneralConfiguration options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+
+ var metricsValidation = HostingSpecificationConfigValidator.ValidateHostingSpecifications(options.MetricsEndPoints, nameof(options.MetricsEndPoints), false);
+ if (metricsValidation.Failed)
+ return metricsValidation;
+
+ return HostingSpecificationConfigValidator.ValidateHostingSpecifications(options.ApiEndPoints, nameof(GeneralConfiguration.ApiEndPoints), true);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Configuration/Validators/HostingSpecificationConfigValidator.cs b/src/Tgstation.Server.Host/Configuration/Validators/HostingSpecificationConfigValidator.cs
new file mode 100644
index 0000000000..2911921269
--- /dev/null
+++ b/src/Tgstation.Server.Host/Configuration/Validators/HostingSpecificationConfigValidator.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+using Microsoft.Extensions.Options;
+
+using Tgstation.Server.Host.Configuration;
+
+namespace Tgstation.Server.Host
+{
+ ///
+ /// Configuration validation for s.
+ ///
+ static class HostingSpecificationConfigValidator
+ {
+ ///
+ /// Validate given a set of .
+ ///
+ /// The s to validate.
+ /// The name of the config subsection.
+ /// If these endpoints are required.
+ /// The for the .
+ public static ValidateOptionsResult ValidateHostingSpecifications(IReadOnlyList endPoints, string name, bool required)
+ {
+ ArgumentNullException.ThrowIfNull(endPoints);
+ ArgumentNullException.ThrowIfNull(name);
+
+ if (endPoints == null || endPoints.Count == 0)
+ {
+ if (!required)
+ return ValidateOptionsResult.Success;
+
+ return ValidateOptionsResult.Fail($"At least one hosting specification for {name} must be specified");
+ }
+
+ foreach (var endPoint in endPoints)
+ {
+ if (endPoint == null)
+ return ValidateOptionsResult.Fail("A hosting specification cannot be null!");
+
+ if (endPoint.Port == 0)
+ return ValidateOptionsResult.Fail($"Hosting specification port in {name} cannot be 0!");
+
+ if (endPoint.IPAddress != null && !IPAddress.TryParse(endPoint.IPAddress, out _))
+ return ValidateOptionsResult.Fail($"Could not part hosting IP address in {name}: {endPoint.IPAddress}");
+ }
+
+ return ValidateOptionsResult.Success;
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Configuration/Validators/SessionConfigValidator.cs b/src/Tgstation.Server.Host/Configuration/Validators/SessionConfigValidator.cs
new file mode 100644
index 0000000000..0b450e36dc
--- /dev/null
+++ b/src/Tgstation.Server.Host/Configuration/Validators/SessionConfigValidator.cs
@@ -0,0 +1,23 @@
+using System;
+
+using Microsoft.Extensions.Options;
+
+namespace Tgstation.Server.Host.Configuration.Validators
+{
+ ///
+ /// Configuration validator for .
+ ///
+ sealed class SessionConfigValidator : IValidateOptions
+ {
+ ///
+ public ValidateOptionsResult Validate(string? name, SessionConfiguration options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+
+ if (options.BridgePort == 0)
+ return ValidateOptionsResult.Fail($"{nameof(SessionConfiguration.BridgePort)} cannot be zero!");
+
+ return ValidateOptionsResult.Success;
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Configuration/Validators/SwarmConfigValidator.cs b/src/Tgstation.Server.Host/Configuration/Validators/SwarmConfigValidator.cs
new file mode 100644
index 0000000000..cb67d69606
--- /dev/null
+++ b/src/Tgstation.Server.Host/Configuration/Validators/SwarmConfigValidator.cs
@@ -0,0 +1,29 @@
+using System;
+
+using Microsoft.Extensions.Options;
+
+namespace Tgstation.Server.Host.Configuration.Validators
+{
+ ///
+ /// Configuration validator for .
+ ///
+ sealed class SwarmConfigValidator : IValidateOptions
+ {
+ ///
+ public ValidateOptionsResult Validate(string? name, SwarmConfiguration options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+
+ if (options.PrivateKey == null)
+ return ValidateOptionsResult.Success;
+
+ if (options.UpdateRequiredNodeCount == 0)
+ return ValidateOptionsResult.Fail($"{nameof(SwarmConfiguration.UpdateRequiredNodeCount)} must be greater than 0!");
+
+ if (options.Address == null)
+ return ValidateOptionsResult.Fail($"{nameof(SwarmConfiguration.Address)} must be set to an http endpoint of this swarm service accessible from other servers in the swarm!");
+
+ return HostingSpecificationConfigValidator.ValidateHostingSpecifications(options.EndPoints, nameof(SwarmConfiguration.EndPoints), true);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index b3c4820d70..74b566458b 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -822,6 +822,10 @@ namespace Tgstation.Server.Host.Core
protected override void ConfigureHostedService(IServiceCollection services)
=> services.AddSingleton(x => x.GetRequiredService());
+ ///
+ protected override void UseValidatedConfig(IServiceCollection services)
+ => services.UseValidatedConfig(Configuration);
+
///
/// Configure the for the authentication pipeline.
///
diff --git a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs
index ea41b321d4..1a79a32395 100644
--- a/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Tgstation.Server.Host/Extensions/ServiceCollectionExtensions.cs
@@ -165,6 +165,22 @@ namespace Tgstation.Server.Host.Extensions
.BindConfiguration(sectionName);
}
+ ///
+ /// Add a standard binding.
+ ///
+ /// The to bind. Must have a const/static field named "Section".
+ /// The for s.
+ /// The to configure.
+ /// The containing the .
+ /// .
+ public static OptionsBuilder UseValidatedConfig(this IServiceCollection serviceCollection, IConfiguration configuration)
+ where TConfig : class
+ where TValidator : class, IValidateOptions
+ {
+ serviceCollection.AddSingleton, TValidator>();
+ return UseStandardConfig(serviceCollection, configuration);
+ }
+
///
/// Clear previous providers and configure logging.
///
diff --git a/src/Tgstation.Server.Host/Setup/SetupApplication.cs b/src/Tgstation.Server.Host/Setup/SetupApplication.cs
index ef5c999d03..7c31ee843f 100644
--- a/src/Tgstation.Server.Host/Setup/SetupApplication.cs
+++ b/src/Tgstation.Server.Host/Setup/SetupApplication.cs
@@ -3,9 +3,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+
using Serilog.Events;
using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Configuration.Validators;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
@@ -56,14 +59,14 @@ namespace Tgstation.Server.Host.Setup
services.AddSingleton();
// these configs are what's injected into PostSetupServices
- services.UseStandardConfig(Configuration);
+ UseValidatedConfig(services);
services.UseStandardConfig(Configuration);
services.UseStandardConfig(Configuration);
services.UseStandardConfig(Configuration);
services.UseStandardConfig(Configuration);
services.UseStandardConfig(Configuration);
- services.UseStandardConfig(Configuration);
- services.UseStandardConfig(Configuration);
+ UseValidatedConfig(services);
+ UseValidatedConfig(services);
ConfigureHostedService(services);
}
@@ -77,5 +80,16 @@ namespace Tgstation.Server.Host.Setup
services.AddSingleton();
services.AddSingleton();
}
+
+ ///
+ /// Configures the .
+ ///
+ /// The to bind. Must have a const/static field named "Section".
+ /// The for s.
+ /// The to configure.
+ protected virtual void UseValidatedConfig(IServiceCollection services)
+ where TConfig : class
+ where TValidator : class, IValidateOptions
+ => services.UseStandardConfig(Configuration); // don't enable config validation during setup
}
}