1 using Cyberboss.AspNetCore.AsyncInitializer;
2 using Microsoft.AspNetCore.Authentication.JwtBearer;
3 using Microsoft.AspNetCore.Builder;
4 using Microsoft.AspNetCore.Cors.Infrastructure;
5 using Microsoft.AspNetCore.Hosting;
6 using Microsoft.AspNetCore.Identity;
7 using Microsoft.Extensions.Configuration;
8 using Microsoft.Extensions.DependencyInjection;
9 using Microsoft.Extensions.Hosting;
10 using Microsoft.Extensions.Logging;
11 using Microsoft.Extensions.Options;
12 using Microsoft.Extensions.Primitives;
13 using Newtonsoft.Json;
16 using Serilog.Formatting.Display;
18 using System.Globalization;
19 using System.IdentityModel.Tokens.Jwt;
20 using System.Threading.Tasks;
46 #pragma warning disable CA1506 65 IConfiguration configuration,
66 IWebHostEnvironment hostingEnvironment)
69 this.hostingEnvironment = hostingEnvironment ??
throw new ArgumentNullException(nameof(hostingEnvironment));
88 ConfigureServices(services);
90 if (postSetupServices == null)
91 throw new ArgumentNullException(nameof(postSetupServices));
98 services.AddOptions();
100 static LogEventLevel? ConvertSeriLogLevel(LogLevel logLevel) =>
103 LogLevel.Critical => LogEventLevel.Fatal,
104 LogLevel.Debug => LogEventLevel.Debug,
105 LogLevel.Error => LogEventLevel.Error,
106 LogLevel.Information => LogEventLevel.Information,
107 LogLevel.Trace => LogEventLevel.Verbose,
108 LogLevel.Warning => LogEventLevel.Warning,
109 LogLevel.None => null,
110 _ =>
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid log level {0}", logLevel)),
114 services.SetupLogging(
117 if (microsoftEventLevel.HasValue)
118 config.MinimumLevel.Override(
"Microsoft", microsoftEventLevel.Value);
132 var formatter =
new MessageTemplateTextFormatter(
135 +
": [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
138 logPath = IOManager.ConcatPath(logPath,
"tgs-.log");
139 var rollingFileConfig = sinkConfig.File(
142 logEventLevel ?? LogEventLevel.Verbose,
144 flushToDiskInterval: TimeSpan.FromSeconds(2),
145 rollingInterval: RollingInterval.Day,
146 rollOnFileSizeLimit:
true);
150 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions =>
155 jwtBearerOptions.Events =
new JwtBearerEvents
159 OnTokenValidated = ctx => ctx
163 .InjectClaimsIntoContext(
165 ctx.HttpContext.RequestAborted)
172 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
178 options.EnableEndpointRouting =
false;
179 options.ReturnHttpNotAcceptable =
true;
180 options.RespectBrowserAcceptHeader =
true;
182 .AddNewtonsoftJson(options =>
184 options.AllowInputFormatterExceptionMessages =
true;
185 options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
186 options.SerializerSettings.CheckAdditionalContent =
true;
187 options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
188 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
192 if (hostingEnvironment.IsDevelopment())
194 static string GetDocumentationFilePath(
string assemblyLocation) => IOManager.ConcatPath(IOManager.GetDirectoryName(assemblyLocation), String.Concat(IOManager.GetFileNameWithoutExtension(assemblyLocation),
".xml"));
195 var assemblyDocumentationPath = GetDocumentationFilePath(typeof(
Application).Assembly.Location);
196 var apiDocumentationPath = GetDocumentationFilePath(typeof(
ApiHeaders).Assembly.Location);
198 services.AddSwaggerGenNewtonsoftSupport();
202 services.AddDetectionCore().AddBrowser();
211 services.AddDbContextPool<TContext>((serviceProvider, builder) =>
213 if (hostingEnvironment.IsDevelopment())
214 builder.EnableSensitiveDataLogging();
216 var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
217 var databaseConfig = databaseConfigOptions.Value ??
throw new InvalidOperationException(
"DatabaseConfiguration missing!");
218 configureAction(builder, databaseConfig);
220 services.AddScoped<
IDatabaseContext>(x => x.GetRequiredService<TContext>());
229 AddTypedContext<MySqlDatabaseContext>();
232 AddTypedContext<SqlServerDatabaseContext>();
235 AddTypedContext<SqliteDatabaseContext>();
238 AddTypedContext<PostgresSqlDatabaseContext>();
241 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid {0}: {1}!", nameof(
DatabaseType), dbType));
254 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
259 AddWatchdog<WindowsWatchdogFactory>(services, postSetupServices);
272 AddWatchdog<PosixWatchdogFactory>(services, postSetupServices);
281 services.AddSingleton(x =>
new Lazy<IProcessExecutor>(() => x.GetRequiredService<
IProcessExecutor>(),
true));
313 static void AddWatchdog<TSystemWatchdogFactory>(IServiceCollection services,
IPostSetupServices postSetupServices)
316 if (postSetupServices.GeneralConfiguration.UseBasicWatchdog)
323 protected override void ConfigureHostedService(IServiceCollection services)
337 IApplicationBuilder applicationBuilder,
341 IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
342 IOptions<GeneralConfiguration> generalConfigurationOptions,
343 ILogger<Application> logger)
345 if (applicationBuilder == null)
346 throw new ArgumentNullException(nameof(applicationBuilder));
347 if (serverControl == null)
348 throw new ArgumentNullException(nameof(serverControl));
350 this.tokenFactory = tokenFactory ??
throw new ArgumentNullException(nameof(tokenFactory));
352 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
353 var generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
356 throw new ArgumentNullException(nameof(logger));
358 logger.LogDebug(
"Content Root: {0}", hostingEnvironment.ContentRootPath);
359 logger.LogTrace(
"Web Root: {0}", hostingEnvironment.WebRootPath);
363 logger.LogCritical(
"Configured minimum password length ({0}) is greater than the maximum database string length ({1})!");
364 serverControl.
Die(
new InvalidOperationException(
"Minimum password length greater than database limit!"));
370 ChangeToken.OnChange(Configuration.GetReloadToken, () =>
372 logger.LogInformation(
"Configuration change detected");
373 serverControl.Restart();
378 applicationBuilder.UseServerErrorHandling();
381 applicationBuilder.UseAsyncInitialization(async (cancellationToken) =>
383 var tcs =
new TaskCompletionSource<object>();
384 using (cancellationToken.Register(() => tcs.SetCanceled()))
385 await Task.WhenAny(tcs.Task, instanceManager.
Ready).ConfigureAwait(
false);
389 applicationBuilder.UseCancelledRequestSuppression();
391 if (hostingEnvironment.IsDevelopment())
393 applicationBuilder.UseSwagger();
394 applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint(
"/swagger/v1/swagger.json",
"TGS API V4"));
395 logger.LogTrace(
"Swagger API generation enabled");
399 Action<CorsPolicyBuilder> corsBuilder = null;
400 if (controlPanelConfiguration.AllowAnyOrigin)
402 logger.LogTrace(
"Access-Control-Allow-Origin: *");
403 corsBuilder = builder => builder.AllowAnyOrigin();
405 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
407 logger.LogTrace(
"Access-Control-Allow-Origin: ", String.Join(
',', controlPanelConfiguration.AllowedOrigins));
408 corsBuilder = builder => builder.WithOrigins(controlPanelConfiguration.AllowedOrigins.ToArray());
411 var originalBuilder = corsBuilder;
412 corsBuilder = builder =>
414 builder.AllowAnyHeader().AllowAnyMethod();
415 originalBuilder?.Invoke(builder);
417 applicationBuilder.UseCors(corsBuilder);
420 if (controlPanelConfiguration.Enable)
422 logger.LogWarning(
"Web control panel enabled. This is a highly WIP feature!");
423 applicationBuilder.UseStaticFiles();
426 logger.LogDebug(
"Web control panel disabled!");
428 logger.LogDebug(
"Starting hosting...");
431 applicationBuilder.UseAuthentication();
434 applicationBuilder.UseDbConflictHandling();
437 applicationBuilder.UseMvc();
LogLevel MicrosoftLogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs for Microsoft library sources ...
For creating and accessing authentication contexts
ISymlinkFactory for windows systems
const int MaximumStringLength
Length limit for strings in fields.
IPostWriteHandler for POSIX systems
Handles changing file modes/permissions after writing
bool Disable
If file logging is disabled
void ConfigureServices(IServiceCollection services, IPostSetupServices postSetupServices)
Configure the Application's services.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
For low level interactions with a LibGit2Sharp.IRepository.
bool WatchdogPresent
if live updates are supported, . ApplyUpdate(Version, Uri, IIOManager) and Restart will fail if this ...
Task Die(Exception exception)
Kill the server with a fatal exception
LogLevel LogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs
const string SerilogContextTemplate
Common template used for adding our custom log context to serilog.
For initially setting up a database.
ITokenFactory tokenFactory
The ITokenFactory for the Application
IPlatformIdentifier PlatformIdentifier
The IPlatformIdentifier.
Sanity limits to prevent users from overloading
For creating filesystem symbolic links
Sets up dependency injection.
Implements various filters for Swashbuckle.
Implementation of IServerFactory.
ISystemIdentityFactory for windows systems. Uses long running tasks due to potential networked domain...
Factory for ITopicClients
Task Ready
Task that completes when the IInstanceManager finishes initializing.
Contains various cryptographic functions
For creating IGitHubClients
Backend abstract implementation of IDatabaseContext
ISymlinkFactory for posix systems
Application(IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
Construct an Application
Extensions for IServiceCollection
readonly IWebHostEnvironment hostingEnvironment
The IWebHostEnvironment for the Application.
Factory for creating LibGit2Sharp.IRepositorys.
DatabaseConfiguration DatabaseConfiguration
The Configuration.DatabaseConfiguration.
Configuration for the automatic update system
For downloading and installing BYOND extractions for a given system
void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, IInstanceManager instanceManager, IOptions< ControlPanelConfiguration > controlPanelConfigurationOptions, IOptions< GeneralConfiguration > generalConfigurationOptions, ILogger< Application > logger)
Configure the Application
For accessing the disk in a synchronous manner
DatabaseType DatabaseType
The Configuration.DatabaseType to create
Factory for creating IInstances
IByondInstaller for Posix systems
string GetFullLogDirectory(IIOManager ioManager, IAssemblyInformationProvider assemblyInformationProvider, IPlatformIdentifier platformIdentifier)
Gets the evaluated log Directory.
Factory for ISystemIdentitys
IPostWriteHandler for Windows systems
DI root for configuring a SetupWizard.
Configuration options for the web control panel
For creating IChatManagers
Set of objects needed to configure an Core.Application.
JsonConverter for serializing Versions for BYOND.
static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
Configure the swagger settings.
TokenValidationParameters ValidationParameters
The TokenValidationParameters for the ITokenFactory
ISystemIdentityFactory for posix systems
Manages the runtime of Jobs
On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort(). This looks out for those prompts and immediately clicks "Yes" if the owning process has registered for it
For injecting global::System.Security.Claims.Claims that Controllers.TgsAuthorizeAttribute can look f...
DatabaseType
Type of database to user
FileLoggingConfiguration FileLoggingConfiguration
The Configuration.FileLoggingConfiguration.
Abstraction for suspending and resuming processes.
Provides access to the server's HttpApiPort.
Handler for BridgeParameters.
For launching IProcess'
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
IByondInstaller for windows systems
For caching ISystemIdentitys