From 512955219ecab075c37f7dac92bcc09b1b9e0eca Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 30 Aug 2022 21:59:13 +0100 Subject: [PATCH] Add run modes --- VoidCat/Model/Extensions.cs | 3 + VoidCat/Model/VoidSettings.cs | 1 - VoidCat/Program.cs | 326 +++++++----------- .../VirusScanner/VirusScannerStartup.cs | 8 - VoidCat/VoidStartup.cs | 184 ++++++++++ 5 files changed, 305 insertions(+), 217 deletions(-) create mode 100644 VoidCat/VoidStartup.cs diff --git a/VoidCat/Model/Extensions.cs b/VoidCat/Model/Extensions.cs index d2f4647..6a90f63 100644 --- a/VoidCat/Model/Extensions.cs +++ b/VoidCat/Model/Extensions.cs @@ -233,4 +233,7 @@ public static class Extensions public static bool HasPrometheus(this VoidSettings settings) => settings.Prometheus?.Url != null; + + public static bool HasVirusScanner(this VoidSettings settings) + => settings.VirusScanner?.ClamAV != default || settings.VirusScanner?.VirusTotal != default; } \ No newline at end of file diff --git a/VoidCat/Model/VoidSettings.cs b/VoidCat/Model/VoidSettings.cs index 8fe7fe6..2cbbd90 100644 --- a/VoidCat/Model/VoidSettings.cs +++ b/VoidCat/Model/VoidSettings.cs @@ -157,6 +157,5 @@ namespace VoidCat.Model { public Uri? Url { get; init; } public string? EgressQuery { get; init; } - public string? IngressQuery { get; init; } } } \ No newline at end of file diff --git a/VoidCat/Program.cs b/VoidCat/Program.cs index e1df2db..db20af4 100644 --- a/VoidCat/Program.cs +++ b/VoidCat/Program.cs @@ -1,195 +1,28 @@ -using System.Data; -using System.Reflection; -using System.Text; -using FluentMigrator.Runner; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.HttpLogging; -using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; using Newtonsoft.Json; -using Npgsql; using Prometheus; -using StackExchange.Redis; +using VoidCat; using VoidCat.Model; -using VoidCat.Services; -using VoidCat.Services.Abstractions; -using VoidCat.Services.Background; -using VoidCat.Services.Captcha; -using VoidCat.Services.Files; -using VoidCat.Services.InMemory; using VoidCat.Services.Migrations; -using VoidCat.Services.Paywall; -using VoidCat.Services.Redis; -using VoidCat.Services.Stats; -using VoidCat.Services.Users; -using VoidCat.Services.VirusScanner; -// setup JsonConvert default settings -JsonSerializerSettings ConfigJsonSettings(JsonSerializerSettings s) -{ - s.NullValueHandling = NullValueHandling.Ignore; - s.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; - s.MissingMemberHandling = MissingMemberHandling.Ignore; - return s; -} +JsonConvert.DefaultSettings = () => VoidStartup.ConfigJsonSettings(new()); -JsonConvert.DefaultSettings = () => ConfigJsonSettings(new()); - -var builder = WebApplication.CreateBuilder(args); -var services = builder.Services; - -var configuration = builder.Configuration; -var voidSettings = configuration.GetSection("Settings").Get(); -services.AddSingleton(voidSettings); -services.AddSingleton(voidSettings.Strike ?? new()); - -var seqSettings = configuration.GetSection("Seq"); -builder.Logging.AddSeq(seqSettings); - -if (voidSettings.HasRedis()) -{ - var cx = await ConnectionMultiplexer.ConnectAsync(voidSettings.Redis); - services.AddSingleton(cx); - services.AddSingleton(cx.GetDatabase()); -} - -services.AddHttpLogging((o) => -{ - o.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders | HttpLoggingFields.ResponsePropertiesAndHeaders; - o.RequestBodyLogLimit = 4096; - o.ResponseBodyLogLimit = 4096; - - o.MediaTypeOptions.Clear(); - o.MediaTypeOptions.AddText("application/json"); - - foreach (var h in voidSettings.RequestHeadersLog ?? Enumerable.Empty()) - { - o.RequestHeaders.Add(h); - } -}); -services.AddHttpClient(); -services.AddSwaggerGen(c => -{ - c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Description = "Please insert JWT with Bearer into field", - Name = "Authorization", - Type = SecuritySchemeType.ApiKey - }); - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - new string[] { } - } - }); - var path = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); - c.IncludeXmlComments(path); -}); -services.AddCors(opt => -{ - opt.AddDefaultPolicy(p => - { - p.AllowAnyMethod() - .AllowAnyHeader() - .WithOrigins(voidSettings.CorsOrigins.Select(a => a.OriginalString).ToArray()); - }); -}); -services.AddRazorPages(); -services.AddRouting(); -services.AddControllers() - .AddNewtonsoftJson((opt) => { ConfigJsonSettings(opt.SerializerSettings); }); -services.AddHealthChecks(); - -services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.TokenValidationParameters = new() - { - ValidateIssuer = true, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = voidSettings.JwtSettings.Issuer, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(voidSettings.JwtSettings.Key)) - }; - }); - -services.AddAuthorization((opt) => -{ - opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); }); -}); - -// void.cat services -// -services.AddTransient(); -services.AddTransient(); -services.AddTransient(); -services.AddTransient(); - -// file storage -services.AddStorage(voidSettings); - -// stats -services.AddMetrics(voidSettings); - -// paywall -services.AddPaywallServices(voidSettings); - -// users -services.AddUserServices(voidSettings); - -// background services -services.AddHostedService(); - -// virus scanner -services.AddVirusScanner(voidSettings); - -// captcha -services.AddCaptcha(voidSettings); - -// postgres -if (!string.IsNullOrEmpty(voidSettings.Postgres)) -{ - services.AddSingleton(); - services.AddTransient(_ => new NpgsqlConnection(voidSettings.Postgres)); - - // fluent migrations - services.AddTransient(); - services.AddFluentMigratorCore() - .ConfigureRunner(r => - r.AddPostgres() - .WithGlobalConnectionString(voidSettings.Postgres) - .ScanIn(typeof(Program).Assembly).For.Migrations()); -} - -if (voidSettings.HasRedis()) -{ - services.AddTransient(); - - // redis specific migrations - services.AddTransient(); -} -else -{ - services.AddMemoryCache(); - services.AddTransient(); -} - -var app = builder.Build(); +RunModes mode = args.Length == 0 ? RunModes.All : 0; if (args.Contains("--run-migrations")) { - // run migrations - using var migrationScope = app.Services.CreateScope(); + mode |= RunModes.Migrations; +} + +if (args.Contains("--run-background-jobs")) +{ + mode |= RunModes.BackgroundJobs; +} + +Console.WriteLine($"Running with modes: {mode}"); + +async Task RunMigrations(IServiceProvider services) +{ + using var migrationScope = services.CreateScope(); var migrations = migrationScope.ServiceProvider.GetServices(); var logger = migrationScope.ServiceProvider.GetRequiredService>(); foreach (var migration in migrations.OrderBy(a => a.Order)) @@ -202,32 +35,109 @@ if (args.Contains("--run-migrations")) return; } } - - return; } -#if HostSPA -app.UseStaticFiles(); -#endif - -app.UseHttpLogging(); -app.UseRouting(); -app.UseCors(); -app.UseSwagger(); -app.UseSwaggerUI(); -app.UseAuthentication(); -app.UseAuthorization(); - -app.UseHealthChecks("/healthz"); - -app.UseEndpoints(ep => +if (mode.HasFlag(RunModes.Webserver)) { - ep.MapControllers(); - ep.MapMetrics(); - ep.MapRazorPages(); -#if HostSPA - ep.MapFallbackToFile("index.html"); -#endif -}); + var builder = WebApplication.CreateBuilder(args); + var services = builder.Services; -app.Run(); \ No newline at end of file + var configuration = builder.Configuration; + var voidSettings = configuration.GetSection("Settings").Get(); + services.AddSingleton(voidSettings); + services.AddSingleton(voidSettings.Strike ?? new()); + + var seqSettings = configuration.GetSection("Seq"); + builder.Logging.AddSeq(seqSettings); + + services.AddBaseServices(voidSettings); + services.AddDatabaseServices(voidSettings); + services.AddWebServices(voidSettings); + + if (mode.HasFlag(RunModes.Migrations)) + { + services.AddMigrations(voidSettings); + } + + if (mode.HasFlag(RunModes.BackgroundJobs)) + { + services.AddBackgroundServices(voidSettings); + } + + var app = builder.Build(); + + if (mode.HasFlag(RunModes.Migrations)) + { + await RunMigrations(app.Services); + } + +#if HostSPA + app.UseStaticFiles(); +#endif + + app.UseHttpLogging(); + app.UseRouting(); + app.UseCors(); + app.UseSwagger(); + app.UseSwaggerUI(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseHealthChecks("/healthz"); + + app.UseEndpoints(ep => + { + ep.MapControllers(); + ep.MapMetrics(); + ep.MapRazorPages(); +#if HostSPA + ep.MapFallbackToFile("index.html"); +#endif + }); + + app.Run(); +} +else +{ + // daemon style, dont run web server + var builder = Host.CreateDefaultBuilder(args); + builder.ConfigureServices((context, services) => + { + var voidSettings = context.Configuration.GetSection("Settings").Get(); + services.AddSingleton(voidSettings); + services.AddSingleton(voidSettings.Strike ?? new()); + + services.AddBaseServices(voidSettings); + services.AddDatabaseServices(voidSettings); + if (mode.HasFlag(RunModes.Migrations)) + { + services.AddMigrations(voidSettings); + } + + if (mode.HasFlag(RunModes.BackgroundJobs)) + { + services.AddBackgroundServices(voidSettings); + } + }); + builder.ConfigureLogging((context, logging) => { logging.AddSeq(context.Configuration.GetSection("Seq")); }); + + var app = builder.Build(); + if (mode.HasFlag(RunModes.Migrations)) + { + await RunMigrations(app.Services); + } + + if (mode.HasFlag(RunModes.BackgroundJobs)) + { + app.Run(); + } +} + +[Flags] +internal enum RunModes +{ + Webserver = 1, + BackgroundJobs = 2, + Migrations = 4, + All = 255 +} \ No newline at end of file diff --git a/VoidCat/Services/VirusScanner/VirusScannerStartup.cs b/VoidCat/Services/VirusScanner/VirusScannerStartup.cs index 0df1f11..b899cce 100644 --- a/VoidCat/Services/VirusScanner/VirusScannerStartup.cs +++ b/VoidCat/Services/VirusScanner/VirusScannerStartup.cs @@ -20,12 +20,9 @@ public static class VirusScannerStartup var avSettings = settings.VirusScanner; if (avSettings != default) { - var loadService = false; - // load ClamAV scanner if (avSettings.ClamAV != default) { - loadService = true; services.AddTransient((_) => new ClamClient(avSettings.ClamAV.Endpoint.Host, avSettings.ClamAV.Endpoint.Port) { @@ -33,11 +30,6 @@ public static class VirusScannerStartup }); services.AddTransient(); } - - if (loadService) - { - services.AddHostedService(); - } } } } \ No newline at end of file diff --git a/VoidCat/VoidStartup.cs b/VoidCat/VoidStartup.cs new file mode 100644 index 0000000..7ee911d --- /dev/null +++ b/VoidCat/VoidStartup.cs @@ -0,0 +1,184 @@ +using System.Data; +using System.Reflection; +using System.Text; +using FluentMigrator.Runner; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.HttpLogging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json; +using Npgsql; +using StackExchange.Redis; +using VoidCat.Model; +using VoidCat.Services; +using VoidCat.Services.Abstractions; +using VoidCat.Services.Background; +using VoidCat.Services.Captcha; +using VoidCat.Services.Files; +using VoidCat.Services.InMemory; +using VoidCat.Services.Migrations; +using VoidCat.Services.Paywall; +using VoidCat.Services.Redis; +using VoidCat.Services.Stats; +using VoidCat.Services.Users; +using VoidCat.Services.VirusScanner; + +namespace VoidCat; + +public static class VoidStartup +{ + public static void AddDatabaseServices(this IServiceCollection services, VoidSettings voidSettings) + { + if (voidSettings.HasRedis()) + { + var cx = ConnectionMultiplexer.Connect(voidSettings.Redis!); + services.AddSingleton(cx); + services.AddSingleton(cx.GetDatabase()); + } + + if (voidSettings.HasPostgres()) + { + services.AddSingleton(); + services.AddTransient(_ => new NpgsqlConnection(voidSettings.Postgres)); + + // fluent migrations + services.AddTransient(); + services.AddFluentMigratorCore() + .ConfigureRunner(r => + r.AddPostgres() + .WithGlobalConnectionString(voidSettings.Postgres) + .ScanIn(typeof(Program).Assembly).For.Migrations()); + } + + if (voidSettings.HasRedis()) + { + services.AddTransient(); + } + else + { + services.AddMemoryCache(); + services.AddTransient(); + } + } + + public static void AddBaseServices(this IServiceCollection services, VoidSettings voidSettings) + { + services.AddStorage(voidSettings); + services.AddMetrics(voidSettings); + services.AddPaywallServices(voidSettings); + services.AddUserServices(voidSettings); + services.AddVirusScanner(voidSettings); + services.AddCaptcha(voidSettings); + } + + public static void AddWebServices(this IServiceCollection services, VoidSettings voidSettings) + { + services.AddHttpLogging((o) => + { + o.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders | + HttpLoggingFields.ResponsePropertiesAndHeaders; + o.RequestBodyLogLimit = 4096; + o.ResponseBodyLogLimit = 4096; + + o.MediaTypeOptions.Clear(); + o.MediaTypeOptions.AddText("application/json"); + + foreach (var h in voidSettings.RequestHeadersLog ?? Enumerable.Empty()) + { + o.RequestHeaders.Add(h); + } + }); + services.AddHttpClient(); + services.AddSwaggerGen(c => + { + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please insert JWT with Bearer into field", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); + var path = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); + c.IncludeXmlComments(path); + }); + services.AddCors(opt => + { + opt.AddDefaultPolicy(p => + { + p.AllowAnyMethod() + .AllowAnyHeader() + .WithOrigins(voidSettings.CorsOrigins.Select(a => a.OriginalString).ToArray()); + }); + }); + services.AddRazorPages(); + services.AddRouting(); + services.AddControllers() + .AddNewtonsoftJson((opt) => { ConfigJsonSettings(opt.SerializerSettings); }); + services.AddHealthChecks(); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new() + { + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = voidSettings.JwtSettings.Issuer, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(voidSettings.JwtSettings.Key)) + }; + }); + + services.AddAuthorization((opt) => + { + opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); }); + }); + + services.AddTransient(); + } + + public static void AddBackgroundServices(this IServiceCollection services, VoidSettings voidSettings) + { + services.AddHostedService(); + + if (voidSettings.HasVirusScanner()) + { + services.AddHostedService(); + } + } + + public static void AddMigrations(this IServiceCollection services, VoidSettings voidSettings) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + if (voidSettings.HasRedis()) + { + services.AddTransient(); + } + } + + public static JsonSerializerSettings ConfigJsonSettings(JsonSerializerSettings s) + { + s.NullValueHandling = NullValueHandling.Ignore; + s.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; + s.MissingMemberHandling = MissingMemberHandling.Ignore; + return s; + } +} \ No newline at end of file