Upgraded mooncore versions. Cleaned up code, especially startup code. Changed versions

This commit is contained in:
2025-10-05 16:07:27 +00:00
parent d2ef59d171
commit 9ab69ffef5
43 changed files with 429 additions and 632 deletions

View File

@@ -3,19 +3,24 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using MoonCore.Permissions;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Implementations.LocalAuth;
using Moonlight.ApiServer.Services;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private Task RegisterAuthAsync()
private static void AddAuth(this WebApplicationBuilder builder)
{
WebApplicationBuilder.Services
var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
builder.Services
.AddAuthentication(options => { options.DefaultScheme = "MainScheme"; })
.AddPolicyScheme("MainScheme", null, options =>
{
@@ -42,15 +47,15 @@ public partial class Startup
options.TokenValidationParameters = new()
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
Configuration.Authentication.Secret
configuration.Authentication.Secret
)),
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidateAudience = true,
ValidAudience = Configuration.PublicUrl,
ValidAudience = configuration.PublicUrl,
ValidateIssuer = true,
ValidIssuer = Configuration.PublicUrl
ValidIssuer = configuration.PublicUrl
};
options.Events = new JwtBearerEvents()
@@ -81,11 +86,11 @@ public partial class Startup
})
.AddCookie("Session", null, options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(Configuration.Authentication.Sessions.ExpiresIn);
options.ExpireTimeSpan = TimeSpan.FromDays(configuration.Authentication.Sessions.ExpiresIn);
options.Cookie = new CookieBuilder()
{
Name = Configuration.Authentication.Sessions.CookieName,
Name = configuration.Authentication.Sessions.CookieName,
Path = "/",
IsEssential = true,
SecurePolicy = CookieSecurePolicy.SameAsRequest
@@ -150,16 +155,16 @@ public partial class Startup
options.SignInScheme = "Session";
});
WebApplicationBuilder.Services.AddAuthorization();
builder.Services.AddAuthorization();
WebApplicationBuilder.Services.AddAuthorizationPermissions(options =>
builder.Services.AddAuthorizationPermissions(options =>
{
options.ClaimName = "Permissions";
options.Prefix = "permissions:";
});
WebApplicationBuilder.Services.AddScoped<UserAuthService>();
WebApplicationBuilder.Services.AddScoped<ApiKeyAuthService>();
builder.Services.AddScoped<UserAuthService>();
builder.Services.AddScoped<ApiKeyAuthService>();
// Setup data protection storage within storage folder
// so its persists in containers
@@ -167,23 +172,18 @@ public partial class Startup
Directory.CreateDirectory(dpKeyPath);
WebApplicationBuilder.Services
builder.Services
.AddDataProtection()
.PersistKeysToFileSystem(
new DirectoryInfo(dpKeyPath)
);
WebApplicationBuilder.Services.AddScoped<UserDeletionService>();
return Task.CompletedTask;
builder.Services.AddScoped<UserDeletionService>();
}
private Task UseAuthAsync()
private static void UseAuth(this WebApplication application)
{
WebApplication.UseAuthentication();
WebApplication.UseAuthorization();
return Task.CompletedTask;
application.UseAuthentication();
application.UseAuthorization();
}
}

View File

@@ -1,63 +1,62 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MoonCore.Extended.Extensions;
using MoonCore.Extensions;
using MoonCore.Helpers;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Plugins;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private Task RegisterBaseAsync()
private static void AddBase(this WebApplicationBuilder builder, IPluginStartup[] startups)
{
WebApplicationBuilder.Services.AutoAddServices<Startup>();
WebApplicationBuilder.Services.AddHttpClient();
builder.Services.AutoAddServices<IAssemblyMarker>();
builder.Services.AddHttpClient();
WebApplicationBuilder.Services.AddApiExceptionHandler();
// Add pre-existing services
WebApplicationBuilder.Services.AddSingleton(Configuration);
builder.Services.AddApiExceptionHandler();
// Configure controllers
var mvcBuilder = WebApplicationBuilder.Services.AddControllers();
var mvcBuilder = builder.Services.AddControllers();
// Add plugin assemblies as application parts
foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct())
foreach (var pluginStartup in startups.Select(x => x.GetType().Assembly).Distinct())
mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly);
return Task.CompletedTask;
}
private Task UseBaseAsync()
private static void UseBase(this WebApplication application)
{
WebApplication.UseRouting();
WebApplication.UseExceptionHandler();
return Task.CompletedTask;
application.UseRouting();
application.UseExceptionHandler();
}
private Task MapBaseAsync()
private static void MapBase(this WebApplication application)
{
WebApplication.MapControllers();
if (Configuration.Frontend.EnableHosting)
WebApplication.MapFallbackToController("Index", "Frontend");
return Task.CompletedTask;
application.MapControllers();
// Frontend
var configuration = AppConfiguration.CreateEmpty();
application.Configuration.Bind(configuration);
if (configuration.Frontend.EnableHosting)
application.MapFallbackToController("Index", "Frontend");
}
private Task ConfigureKestrelAsync()
private static void ConfigureKestrel(this WebApplicationBuilder builder)
{
WebApplicationBuilder.WebHost.ConfigureKestrel(kestrelOptions =>
var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
builder.WebHost.ConfigureKestrel(kestrelOptions =>
{
var maxUploadInBytes = ByteConverter
.FromMegaBytes(Configuration.Kestrel.UploadLimit)
.FromMegaBytes(configuration.Kestrel.UploadLimit)
.Bytes;
kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes;
});
return Task.CompletedTask;
}
}

View File

@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MoonCore.EnvConfiguration;
@@ -6,30 +7,23 @@ using Moonlight.ApiServer.Configuration;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private async Task SetupAppConfigurationAsync()
private static void AddConfiguration(this WebApplicationBuilder builder)
{
var configPath = Path.Combine("storage", "config.yml");
// Yaml
var yamlPath = Path.Combine("storage", "config.yml");
await YamlDefaultGenerator.GenerateAsync<AppConfiguration>(configPath);
YamlDefaultGenerator.GenerateAsync<AppConfiguration>(yamlPath).Wait();
// Configure configuration (wow)
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddYamlFile(configPath);
configurationBuilder.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_");
var configurationRoot = configurationBuilder.Build();
builder.Configuration.AddYamlFile(yamlPath);
// Retrieve configuration
Configuration = AppConfiguration.CreateEmpty();
configurationRoot.Bind(Configuration);
}
private Task RegisterAppConfigurationAsync()
{
WebApplicationBuilder.Services.AddSingleton(Configuration);
return Task.CompletedTask;
// Env
builder.Configuration.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_");
var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
builder.Services.AddSingleton(configuration);
}
}

View File

@@ -1,25 +1,17 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Extensions;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private Task RegisterDatabaseAsync()
private static void AddDatabase(this WebApplicationBuilder builder)
{
WebApplicationBuilder.Services.AddDatabaseMappings();
WebApplicationBuilder.Services.AddServiceCollectionAccessor();
builder.Services.AddDbAutoMigrations();
builder.Services.AddDatabaseMappings();
WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>));
return Task.CompletedTask;
}
private async Task PrepareDatabaseAsync()
{
await WebApplication.Services.EnsureDatabaseMigratedAsync();
WebApplication.Services.GenerateDatabaseMappings();
builder.Services.AddScoped(typeof(DatabaseRepository<>));
}
}

View File

@@ -1,5 +1,6 @@
using Hangfire;
using Hangfire.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -7,11 +8,11 @@ using Moonlight.ApiServer.Database;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private Task RegisterHangfireAsync()
private static void AddMoonlightHangfire(this WebApplicationBuilder builder)
{
WebApplicationBuilder.Services.AddHangfire((provider, configuration) =>
builder.Services.AddHangfire((provider, configuration) =>
{
configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_180);
configuration.UseSimpleAssemblyNameTypeSerializer();
@@ -23,26 +24,22 @@ public partial class Startup
}, new EFCoreStorageOptions());
});
WebApplicationBuilder.Services.AddHangfireServer();
builder.Services.AddHangfireServer();
WebApplicationBuilder.Logging.AddFilter(
builder.Logging.AddFilter(
"Hangfire.Server.BackgroundServerProcess",
LogLevel.Warning
);
WebApplicationBuilder.Logging.AddFilter(
builder.Logging.AddFilter(
"Hangfire.BackgroundJobServer",
LogLevel.Warning
);
return Task.CompletedTask;
}
private Task UseHangfireAsync()
private static void UseMoonlightHangfire(this WebApplication application)
{
if (WebApplication.Environment.IsDevelopment())
WebApplication.UseHangfireDashboard();
return Task.CompletedTask;
if (application.Environment.IsDevelopment())
application.UseHangfireDashboard();
}
}

View File

@@ -1,32 +1,24 @@
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using MoonCore.Logging;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private Task SetupLoggingAsync()
private static void AddLogging(this WebApplicationBuilder builder)
{
var loggerFactory = new LoggerFactory();
loggerFactory.AddAnsiConsole();
Logger = loggerFactory.CreateLogger<Startup>();
return Task.CompletedTask;
}
private async Task RegisterLoggingAsync()
{
// Configure application logging
WebApplicationBuilder.Logging.ClearProviders();
WebApplicationBuilder.Logging.AddAnsiConsole();
WebApplicationBuilder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log"));
// Logging providers
builder.Logging.ClearProviders();
builder.Logging.AddAnsiConsole();
builder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log"));
// Logging levels
var logConfigPath = Path.Combine("storage", "logConfig.json");
// Ensure logging config, add a default one is missing
// Ensure default log levels exist
if (!File.Exists(logConfigPath))
{
var defaultLogLevels = new Dictionary<string, string>
@@ -38,25 +30,26 @@ public partial class Startup
};
var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels);
await File.WriteAllTextAsync(logConfigPath, logLevelsJson);
File.WriteAllText(logConfigPath, logLevelsJson);
}
// Add logging configuration
// Read log levels
var logLevels = JsonSerializer.Deserialize<Dictionary<string, string>>(
await File.ReadAllTextAsync(logConfigPath)
File.ReadAllText(logConfigPath)
)!;
// Apply configured log levels
foreach (var level in logLevels)
WebApplicationBuilder.Logging.AddFilter(level.Key, Enum.Parse<LogLevel>(level.Value));
builder.Logging.AddFilter(level.Key, Enum.Parse<LogLevel>(level.Value));
// Mute exception handler middleware
// https://github.com/dotnet/aspnetcore/issues/19740
WebApplicationBuilder.Logging.AddFilter(
builder.Logging.AddFilter(
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware",
LogLevel.Critical
);
WebApplicationBuilder.Logging.AddFilter(
builder.Logging.AddFilter(
"Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware",
LogLevel.Critical
);

View File

@@ -1,12 +1,14 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Moonlight.ApiServer.Configuration;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private Task PrintVersionAsync()
private static void PrintVersionAsync()
{
// Fancy start console output... yes very fancy :>
var rainbow = new Crayon.Rainbow(0.5);
@@ -21,23 +23,22 @@ public partial class Startup
}
Console.WriteLine();
return Task.CompletedTask;
}
private Task CreateStorageAsync()
private static void CreateStorageAsync()
{
Directory.CreateDirectory("storage");
Directory.CreateDirectory(Path.Combine("storage", "logs"));
return Task.CompletedTask;
}
private Task RegisterCorsAsync()
{
var allowedOrigins = Configuration.Kestrel.AllowedOrigins;
WebApplicationBuilder.Services.AddCors(options =>
private static void AddMoonlightCors(this WebApplicationBuilder builder)
{
var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
var allowedOrigins = configuration.Kestrel.AllowedOrigins;
builder.Services.AddCors(options =>
{
var cors = new CorsPolicyBuilder();
@@ -60,14 +61,10 @@ public partial class Startup
cors.Build()
);
});
return Task.CompletedTask;
}
private Task UseCorsAsync()
private static void UseMoonlightCors(this WebApplication application)
{
WebApplication.UseCors();
return Task.CompletedTask;
application.UseCors();
}
}

View File

@@ -1,87 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MoonCore.Logging;
using Microsoft.AspNetCore.Builder;
using Moonlight.ApiServer.Plugins;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private IServiceProvider PluginLoadServiceProvider;
private IPluginStartup[] PluginStartups;
private Task InitializePluginsAsync()
private static void AddPlugins(this WebApplicationBuilder builder, IPluginStartup[] startups)
{
// Create service provider for starting up
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(Configuration);
serviceCollection.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddAnsiConsole();
});
PluginLoadServiceProvider = serviceCollection.BuildServiceProvider();
return Task.CompletedTask;
foreach (var startup in startups)
startup.AddPlugin(builder);
}
private async Task HookPluginBuildAsync()
private static void UsePlugins(this WebApplication application, IPluginStartup[] startups)
{
foreach (var pluginAppStartup in PluginStartups)
{
try
{
await pluginAppStartup.BuildApplicationAsync(PluginLoadServiceProvider, WebApplicationBuilder);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'BuildApp' for '{name}': {e}",
pluginAppStartup.GetType().FullName,
e
);
}
}
foreach (var startup in startups)
startup.UsePlugin(application);
}
private async Task HookPluginConfigureAsync()
private static void MapPlugins(this WebApplication application, IPluginStartup[] startups)
{
foreach (var pluginAppStartup in PluginStartups)
{
try
{
await pluginAppStartup.ConfigureApplicationAsync(PluginLoadServiceProvider, WebApplication);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'ConfigureApp' for '{name}': {e}",
pluginAppStartup.GetType().FullName,
e
);
}
}
}
private async Task HookPluginEndpointsAsync()
{
foreach (var pluginEndpointStartup in PluginStartups)
{
try
{
await pluginEndpointStartup.ConfigureEndpointsAsync(PluginLoadServiceProvider, WebApplication);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'ConfigureEndpoints' for '{name}': {e}",
pluginEndpointStartup.GetType().FullName,
e
);
}
}
foreach (var startup in startups)
startup.MapPlugin(application);
}
}

View File

@@ -1,25 +1,26 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Http.Hubs;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
public Task RegisterSignalRAsync()
private static void AddMoonlightSignalR(this WebApplicationBuilder builder)
{
var signalRBuilder = WebApplicationBuilder.Services.AddSignalR();
if (Configuration.SignalR.UseRedis)
signalRBuilder.AddStackExchangeRedis(Configuration.SignalR.RedisConnectionString);
var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
return Task.CompletedTask;
var signalRBuilder = builder.Services.AddSignalR();
if (configuration.SignalR.UseRedis)
signalRBuilder.AddStackExchangeRedis(configuration.SignalR.RedisConnectionString);
}
public Task MapSignalRAsync()
private static void MapMoonlightSignalR(this WebApplication application)
{
WebApplication.MapHub<DiagnoseHub>("/api/admin/system/diagnose/ws");
return Task.CompletedTask;
application.MapHub<DiagnoseHub>("/api/admin/system/diagnose/ws");
}
}

View File

@@ -1,69 +1,42 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Plugins;
namespace Moonlight.ApiServer.Startup;
public partial class Startup
public static partial class Startup
{
private string[] Args;
// Logger
public ILogger<Startup> Logger { get; private set; }
// Configuration
public AppConfiguration Configuration { get; private set; }
// WebApplication Stuff
public WebApplication WebApplication { get; private set; }
public WebApplicationBuilder WebApplicationBuilder { get; private set; }
public Task InitializeAsync(string[] args, IPluginStartup[]? plugins = null)
public static void AddMoonlight(this WebApplicationBuilder builder, IPluginStartup[] startups)
{
Args = args;
PluginStartups = plugins ?? [];
PrintVersionAsync();
CreateStorageAsync();
return Task.CompletedTask;
builder.AddConfiguration();
builder.AddLogging();
builder.ConfigureKestrel();
builder.AddBase(startups);
builder.AddDatabase();
builder.AddAuth();
builder.AddMoonlightCors();
builder.AddMoonlightHangfire();
builder.AddMoonlightSignalR();
builder.AddPlugins(startups);
}
public async Task AddMoonlightAsync(WebApplicationBuilder builder)
public static void UseMoonlight(this WebApplication application, IPluginStartup[] startups)
{
WebApplicationBuilder = builder;
await PrintVersionAsync();
await CreateStorageAsync();
await SetupAppConfigurationAsync();
await SetupLoggingAsync();
await InitializePluginsAsync();
await ConfigureKestrelAsync();
await RegisterAppConfigurationAsync();
await RegisterLoggingAsync();
await RegisterBaseAsync();
await RegisterDatabaseAsync();
await RegisterAuthAsync();
await RegisterCorsAsync();
await RegisterHangfireAsync();
await RegisterSignalRAsync();
await HookPluginBuildAsync();
application.UseBase();
application.UseMoonlightCors();
application.UseAuth();
application.UseMoonlightHangfire();
application.UsePlugins(startups);
}
public async Task AddMoonlightAsync(WebApplication application)
public static void MapMoonlight(this WebApplication application, IPluginStartup[] startups)
{
WebApplication = application;
await PrepareDatabaseAsync();
await UseCorsAsync();
await UseBaseAsync();
await UseAuthAsync();
await UseHangfireAsync();
await HookPluginConfigureAsync();
await MapBaseAsync();
await MapSignalRAsync();
await HookPluginEndpointsAsync();
application.MapBase();
application.MapMoonlightSignalR();
application.MapPlugins(startups);
}
}