Broke the startup of the app into a series of extension methods (and classes to contain them), rather than one long method. This means the stack tracers are easier to follow when the startup fails, and it's clearer what's happening when throughout the pipeline. I've included a few optimisations, such as starting to rely more on DI for injecting things (like the logger) rather than the static logger, which in turn can help with testability and evaluation of the callstack.
207 lines
No EOL
6.2 KiB
C#
207 lines
No EOL
6.2 KiB
C#
using LANCommander.Server.Data;
|
|
using LANCommander.Server.Hubs;
|
|
using LANCommander.Server.Services;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Hangfire;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using LANCommander.SDK.Enums;
|
|
using Serilog;
|
|
using LANCommander.Server;
|
|
using LANCommander.Server.Models;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.AddLogger();
|
|
|
|
// Add services to the container.
|
|
Log.Debug("Loading settings");
|
|
LANCommanderSettings settings = SettingService.GetSettings(true);
|
|
builder.Services.AddSingleton(settings);
|
|
Log.Debug("Validating settings");
|
|
if (settings.Authentication.TokenSecret.Length < 16)
|
|
{
|
|
Log.Debug("JWT token secret is too short. Regenerating...");
|
|
settings.Authentication.TokenSecret = Guid.NewGuid().ToString();
|
|
SettingService.SaveSettings(settings);
|
|
}
|
|
Log.Debug("Done validating settings");
|
|
|
|
builder.AddRazor();
|
|
builder.AddSignalR();
|
|
builder.AddCors();
|
|
builder.AddControllers();
|
|
|
|
builder.Services.AddAutoMapper(typeof(LANCommanderMappingProfile));
|
|
|
|
builder.WebHost.ConfigureKestrel((ctx, options) =>
|
|
{
|
|
var settings = options.ApplicationServices.GetRequiredService<LANCommanderSettings>();
|
|
var logger = options.ApplicationServices.GetRequiredService<ILogger<Program>>();
|
|
logger.LogDebug("Starting web server on port {Port}", settings.Port);
|
|
// Configure as HTTP only
|
|
options.ListenAnyIP(settings.Port);
|
|
|
|
options.Limits.MaxRequestBodySize = long.MaxValue;
|
|
options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(5);
|
|
}).UseKestrel();
|
|
|
|
builder.AddIdentity(settings);
|
|
|
|
builder.AddHangfire();
|
|
|
|
builder.Services.AddFusionCache();
|
|
|
|
Log.Debug("Registering Swashbuckle");
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen();
|
|
|
|
Log.Debug("Registering AntDesign Blazor");
|
|
builder.Services.AddAntDesign();
|
|
|
|
builder.Services.AddHttpClient();
|
|
|
|
builder.WebHost.UseStaticWebAssets();
|
|
|
|
builder.AddLANCommanderServices(settings);
|
|
builder.AddDatabase();
|
|
|
|
builder.Services.Configure<FormOptions>(options =>
|
|
{
|
|
options.MultipartBodyLengthLimit = long.MaxValue;
|
|
});
|
|
|
|
Log.Debug("Building Application");
|
|
var app = builder.Build();
|
|
|
|
app.UseCors("CorsPolicy");
|
|
|
|
app.MapHub<GameServerHub>("/hubs/gameserver");
|
|
|
|
app.UseRobots();
|
|
app.UseApiVersioning();
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
Log.Debug("App has been run in a development environment");
|
|
app.UseMigrationsEndPoint();
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
}
|
|
else
|
|
{
|
|
app.UseExceptionHandler("/Home/Error");
|
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHangfireDashboard();
|
|
|
|
// app.UseHttpsRedirection();
|
|
app.UseStaticFiles();
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.UseMvcWithDefaultRoute();
|
|
|
|
Log.Debug("Registering Endpoints");
|
|
|
|
app.MapHub<LoggingHub>("/logging");
|
|
|
|
app.UseEndpoints(endpoints =>
|
|
{
|
|
endpoints.MapBlazorHub();
|
|
endpoints.MapFallbackToPage("/_Host");
|
|
endpoints.MapControllers();
|
|
});
|
|
|
|
PrepareDirectories(app);
|
|
|
|
await EnsureDatabase(app);
|
|
|
|
await InitializeServerProcesses(app);
|
|
|
|
app.Run();
|
|
|
|
static void PrepareDirectories(WebApplication app)
|
|
{
|
|
var settings = app.Services.GetRequiredService<LANCommanderSettings>();
|
|
var logger = app.Services.GetRequiredService<ILogger<Program>>();
|
|
logger.LogDebug("Ensuring required directories exist");
|
|
|
|
IEnumerable<string> directories = [
|
|
settings.Logs.StoragePath,
|
|
settings.Archives.StoragePath,
|
|
settings.UserSaves.StoragePath,
|
|
settings.Media.StoragePath,
|
|
settings.Update.StoragePath,
|
|
"Snippets",
|
|
"Backups"
|
|
];
|
|
|
|
foreach (var directory in directories)
|
|
{
|
|
logger.LogDebug("Ensuring directory {Directory} exists", directory);
|
|
if (!Directory.Exists(directory))
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
}
|
|
|
|
static async Task EnsureDatabase(WebApplication app)
|
|
{
|
|
// Migrate
|
|
using var scope = app.Services.CreateAsyncScope();
|
|
using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
|
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
|
|
var settings = scope.ServiceProvider.GetRequiredService<LANCommanderSettings>();
|
|
logger.LogDebug("Migrating database if required");
|
|
|
|
if (!(await db.Database.GetPendingMigrationsAsync()).Any())
|
|
{
|
|
logger.LogDebug("No pending migrations are available. Skipping database migration.");
|
|
return;
|
|
}
|
|
|
|
var dataSource = new SqliteConnectionStringBuilder(settings.DatabaseConnectionString).DataSource;
|
|
|
|
var backupName = Path.Combine("Backups", $"LANCommander.db.{DateTime.Now:dd-MM-yyyy-HH.mm.ss.bak}");
|
|
|
|
if (File.Exists(dataSource))
|
|
{
|
|
logger.LogInformation("Migrations pending, database will be backed up to {BackupName}", backupName);
|
|
File.Copy(dataSource, backupName);
|
|
}
|
|
|
|
await db.Database.MigrateAsync();
|
|
}
|
|
|
|
static async Task InitializeServerProcesses(WebApplication app)
|
|
{
|
|
// Autostart any server processes
|
|
using var scope = app.Services.CreateScope();
|
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
var serverProcessService = scope.ServiceProvider.GetRequiredService<ServerProcessService>();
|
|
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
|
|
logger.LogDebug("Autostarting Servers");
|
|
|
|
foreach (var server in await serverService.Get(s => s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnApplicationStart).ToListAsync())
|
|
{
|
|
try
|
|
{
|
|
logger.LogDebug("Autostarting server {ServerName} with a delay of {AutostartDelay} seconds", server.Name, server.AutostartDelay);
|
|
|
|
if (server.AutostartDelay > 0)
|
|
await Task.Delay(server.AutostartDelay);
|
|
|
|
await serverProcessService.StartServerAsync(server.Id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "An unexpected error occurred while trying to autostart the server {ServerName}", server.Name);
|
|
}
|
|
}
|
|
} |