[backend/startup] Validate configuration on startup (ISH-23)

This commit is contained in:
Laura Hausmann 2024-03-12 22:07:48 +01:00
parent 64e882c0e7
commit 104a2c485d
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 64 additions and 30 deletions

View file

@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using Iceshrimp.Backend.Core.Middleware;
@ -38,12 +39,12 @@ public sealed class Config
public string UserAgent => $"Iceshrimp.NET/{Version} (https://{WebDomain})";
public int ListenPort { get; init; } = 3000;
public string ListenHost { get; init; } = "localhost";
public string? ListenSocket { get; init; }
public string WebDomain { get; init; } = null!;
public string AccountDomain { get; init; } = null!;
public int CharacterLimit { get; init; } = 8192;
[Range(1, 65535)] public int ListenPort { get; init; } = 3000;
[Required] public string ListenHost { get; init; } = "localhost";
public string? ListenSocket { get; init; }
[Required] public string WebDomain { get; init; } = null!;
[Required] public string AccountDomain { get; init; } = null!;
[Range(1, 100000)] public int CharacterLimit { get; init; } = 8192;
}
public sealed class SecuritySection
@ -60,22 +61,22 @@ public sealed class Config
public sealed class DatabaseSection
{
public string Host { get; init; } = "localhost";
public int Port { get; init; } = 5432;
public string Database { get; init; } = null!;
public string Username { get; init; } = null!;
public string? Password { get; init; }
[Required] public string Host { get; init; } = "localhost";
[Range(1, 65535)] public int Port { get; init; } = 5432;
[Required] public string Database { get; init; } = null!;
[Required] public string Username { get; init; } = null!;
public string? Password { get; init; }
}
public sealed class RedisSection
{
public string Host { get; init; } = "localhost";
public int Port { get; init; } = 6379;
public string? UnixDomainSocket { get; init; }
public string? Prefix { get; init; }
public string? Username { get; init; }
public string? Password { get; init; }
public int? Database { get; init; }
[Required] public string Host { get; init; } = "localhost";
[Range(1, 65535)] public int Port { get; init; } = 6379;
public string? UnixDomainSocket { get; init; }
public string? Prefix { get; init; }
public string? Username { get; init; }
public string? Password { get; init; }
public int? Database { get; init; }
//TODO: TLS settings
}

View file

@ -14,6 +14,7 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using StackExchange.Redis;
using NoteRenderer = Iceshrimp.Backend.Controllers.Renderers.NoteRenderer;
@ -85,15 +86,36 @@ public static class ServiceExtensions
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
//TODO: fail if config doesn't parse correctly / required things are missing
services.Configure<Config>(configuration);
services.Configure<Config.InstanceSection>(configuration.GetSection("Instance"));
services.Configure<Config.SecuritySection>(configuration.GetSection("Security"));
services.Configure<Config.DatabaseSection>(configuration.GetSection("Database"));
services.Configure<Config.RedisSection>(configuration.GetSection("Redis"));
services.Configure<Config.StorageSection>(configuration.GetSection("Storage"));
services.Configure<Config.LocalStorageSection>(configuration.GetSection("Storage:Local"));
services.Configure<Config.ObjectStorageSection>(configuration.GetSection("Storage:ObjectStorage"));
services.ConfigureWithValidation<Config>(configuration)
.ConfigureWithValidation<Config.InstanceSection>(configuration, "Instance")
.ConfigureWithValidation<Config.SecuritySection>(configuration, "Security")
.ConfigureWithValidation<Config.DatabaseSection>(configuration, "Database")
.ConfigureWithValidation<Config.RedisSection>(configuration, "Redis")
.ConfigureWithValidation<Config.StorageSection>(configuration, "Storage")
.ConfigureWithValidation<Config.LocalStorageSection>(configuration, "Storage:Local")
.ConfigureWithValidation<Config.ObjectStorageSection>(configuration, "Storage:ObjectStorage");
}
private static IServiceCollection ConfigureWithValidation<T>(
this IServiceCollection services, IConfiguration config
) where T : class
{
services.AddOptionsWithValidateOnStart<T>()
.Bind(config)
.ValidateDataAnnotations();
return services;
}
private static IServiceCollection ConfigureWithValidation<T>(
this IServiceCollection services, IConfiguration config, string name
) where T : class
{
services.AddOptionsWithValidateOnStart<T>()
.Bind(config.GetSection(name))
.ValidateDataAnnotations();
return services;
}
public static void AddDatabaseContext(this IServiceCollection services, IConfiguration configuration)

View file

@ -5,6 +5,7 @@ using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Extensions;
@ -46,11 +47,18 @@ public static class WebApplicationExtensions
var instanceConfig = app.Configuration.GetSection("Instance").Get<Config.InstanceSection>() ??
throw new Exception("Failed to read Instance config section");
var storageConfig = app.Configuration.GetSection("Storage").Get<Config.StorageSection>() ??
throw new Exception("Failed to read Storage config section");
app.Logger.LogInformation("Iceshrimp.NET v{version} ({domain})", instanceConfig.Version,
instanceConfig.AccountDomain);
try
{
app.Logger.LogInformation("Validating configuration...");
app.Services.CreateScope().ServiceProvider.GetRequiredService<IStartupValidator>().Validate();
}
catch (OptionsValidationException e)
{
app.Logger.LogCritical("Failed to validate configuration: {error}", e.Message);
Environment.Exit(1);
}
if (app.Environment.IsDevelopment())
{
@ -115,6 +123,9 @@ public static class WebApplicationExtensions
Environment.Exit(1);
}
var storageConfig = app.Configuration.GetSection("Storage").Get<Config.StorageSection>() ??
throw new Exception("Failed to read Storage config section");
if (storageConfig.Mode == Enums.FileStorage.Local)
{
if (string.IsNullOrWhiteSpace(storageConfig.Local?.Path) ||