2using System.Collections.Frozen;
3using System.Collections.Generic;
4using System.Globalization;
5using System.Threading.Tasks;
7using Cyberboss.AspNetCore.AsyncInitializer;
9using Elastic.CommonSchema.Serilog;
11using Microsoft.AspNetCore.Authentication;
12using Microsoft.AspNetCore.Authentication.JwtBearer;
13using Microsoft.AspNetCore.Builder;
14using Microsoft.AspNetCore.Cors.Infrastructure;
15using Microsoft.AspNetCore.Hosting;
16using Microsoft.AspNetCore.Http;
17using Microsoft.AspNetCore.Http.Connections;
18using Microsoft.AspNetCore.Identity;
19using Microsoft.AspNetCore.Mvc.Infrastructure;
20using Microsoft.AspNetCore.SignalR;
21using Microsoft.Extensions.Configuration;
22using Microsoft.Extensions.DependencyInjection;
23using Microsoft.Extensions.Hosting;
24using Microsoft.Extensions.Logging;
25using Microsoft.Extensions.Options;
31using Serilog.Formatting.Display;
32using Serilog.Sinks.Elasticsearch;
70#pragma warning disable CA1506
92 assemblyInformationProvider,
105 if (postSetupServices.GeneralConfiguration.UseBasicWatchdog)
117 IConfiguration configuration,
119 : base(configuration)
132 IServiceCollection services,
139 ArgumentNullException.ThrowIfNull(postSetupServices);
149 services.AddOptions();
152 services.Configure<HostOptions>(
155 static LogEventLevel? ConvertSeriLogLevel(LogLevel logLevel) =>
158 LogLevel.Critical => LogEventLevel.Fatal,
159 LogLevel.Debug => LogEventLevel.Debug,
160 LogLevel.Error => LogEventLevel.Error,
161 LogLevel.Information => LogEventLevel.Information,
162 LogLevel.Trace => LogEventLevel.Verbose,
163 LogLevel.Warning => LogEventLevel.Warning,
164 LogLevel.None =>
null,
165 _ =>
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid log level {0}", logLevel)),
170 services.SetupLogging(
173 if (microsoftEventLevel.HasValue)
175 config.MinimumLevel.Override(
"Microsoft", microsoftEventLevel.Value);
176 config.MinimumLevel.Override(
"System.Net.Http.HttpClient", microsoftEventLevel.Value);
186 assemblyInformationProvider,
191 var formatter =
new MessageTemplateTextFormatter(
194 +
"): [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
197 logPath = ioManager.
ConcatPath(logPath,
"tgs-.log");
198 var rollingFileConfig = sinkConfig.File(
201 logEventLevel ?? LogEventLevel.Verbose,
203 flushToDiskInterval: TimeSpan.FromSeconds(2),
204 rollingInterval: RollingInterval.Day,
205 rollOnFileSizeLimit:
true);
207 elasticsearchConfiguration.Enable
208 ?
new ElasticsearchSinkOptions(elasticsearchConfiguration.Host ??
throw new InvalidOperationException($
"Missing {ElasticsearchConfiguration.Section}:{nameof(elasticsearchConfiguration.Host)}!"))
212 ModifyConnectionSettings = connectionConfigration => (!String.IsNullOrWhiteSpace(elasticsearchConfiguration.Username) && !String.IsNullOrWhiteSpace(elasticsearchConfiguration.Password))
213 ? connectionConfigration
214 .BasicAuthentication(
215 elasticsearchConfiguration.Username,
216 elasticsearchConfiguration.Password)
217 .ServerCertificateValidationCallback((o, certificate, chain, errors) =>
true)
219 CustomFormatter =
new EcsTextFormatter(),
220 AutoRegisterTemplate =
true,
221 AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
222 IndexFormat =
"tgs-logs",
232 var jsonVersionConverterList =
new List<JsonConverter>
237 void ConfigureNewtonsoftJsonSerializerSettingsForApi(JsonSerializerSettings settings)
239 settings.NullValueHandling = NullValueHandling.Ignore;
240 settings.CheckAdditionalContent =
true;
241 settings.MissingMemberHandling = MissingMemberHandling.Error;
242 settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
243 settings.Converters = jsonVersionConverterList;
249 options.ReturnHttpNotAcceptable =
true;
250 options.RespectBrowserAcceptHeader =
true;
252 .AddNewtonsoftJson(options =>
254 options.AllowInputFormatterExceptionMessages =
true;
255 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.SerializerSettings);
263 .AddNewtonsoftJsonProtocol(options =>
265 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.PayloadSerializerSettings);
273 var assemblyDocumentationPath = GetDocumentationFilePath(GetType().Assembly.Location);
274 var apiDocumentationPath = GetDocumentationFilePath(typeof(
ApiHeaders).Assembly.Location);
276 services.AddSwaggerGenNewtonsoftSupport();
283 services.AddHttpClient();
286 void AddTypedContext<TContext>()
291 services.AddDbContextPool<TContext>((serviceProvider, builder) =>
294 builder.EnableSensitiveDataLogging();
296 var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
297 var databaseConfig = databaseConfigOptions.Value ??
throw new InvalidOperationException(
"DatabaseConfiguration missing!");
298 configureAction(builder, databaseConfig);
300 services.AddScoped<
IDatabaseContext>(x => x.GetRequiredService<TContext>());
309 AddTypedContext<MySqlDatabaseContext>();
312 AddTypedContext<SqlServerDatabaseContext>();
315 AddTypedContext<SqliteDatabaseContext>();
318 AddTypedContext<PostgresSqlDatabaseContext>();
321 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid {0}: {1}!", nameof(
DatabaseType), dbType));
333 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
338 AddWatchdog<WindowsWatchdogFactory>(services, postSetupServices);
352 AddWatchdog<PosixWatchdogFactory>(services, postSetupServices);
363 services.AddSingleton(x =>
new Lazy<IProcessExecutor>(() => x.GetRequiredService<
IProcessExecutor>(),
true));
371 var openDreamRepositoryDirectory = ioManager.
ConcatPath(
372 ioManager.GetPathInLocalDirectory(assemblyInformationProvider),
373 "OpenDreamRepository");
374 services.AddSingleton(
376 .GetRequiredService<IRepositoryManagerFactory>()
377 .CreateRepositoryManager(
380 openDreamRepositoryDirectory),
383 services.AddSingleton(
384 serviceProvider =>
new Dictionary<EngineType, IEngineInstaller>
389 .ToFrozenDictionary());
415 services.AddChatProviderFactory();
429 services.AddFileDownloader();
430 services.AddGitHub();
459 IApplicationBuilder applicationBuilder,
464 IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
465 IOptions<GeneralConfiguration> generalConfigurationOptions,
466 IOptions<SwarmConfiguration> swarmConfigurationOptions,
467 ILogger<Application> logger)
469 ArgumentNullException.ThrowIfNull(applicationBuilder);
470 ArgumentNullException.ThrowIfNull(serverControl);
474 ArgumentNullException.ThrowIfNull(serverPortProvider);
475 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
477 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
478 var generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
479 var swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
481 ArgumentNullException.ThrowIfNull(logger);
488 applicationBuilder.UseAdditionalRequestLoggingContext(swarmConfiguration);
491 applicationBuilder.UseServerErrorHandling();
494 applicationBuilder.UseServerBranding(assemblyInformationProvider);
497 applicationBuilder.UseDisabledNginxProxyBuffering();
500 applicationBuilder.UseCancelledRequestSuppression();
504 (instanceManager, cancellationToken) => instanceManager.
Ready.WaitAsync(cancellationToken));
506 if (generalConfiguration.HostApiDocumentation)
508 var siteDocPath = Routes.ApiRoot + $
"doc/{SwaggerConfiguration.DocumentName}.json";
509 if (!String.IsNullOrWhiteSpace(controlPanelConfiguration.PublicPath))
510 siteDocPath = controlPanelConfiguration.PublicPath.TrimEnd(
'/') + siteDocPath;
512 applicationBuilder.UseSwagger(options =>
514 options.RouteTemplate = Routes.ApiRoot +
"doc/{documentName}.{json|yaml}";
516 applicationBuilder.UseSwaggerUI(options =>
519 options.SwaggerEndpoint(siteDocPath,
"TGS API");
521 logger.LogTrace(
"Swagger API generation enabled");
525 if (controlPanelConfiguration.Enable)
527 logger.LogInformation(
"Web control panel enabled.");
528 applicationBuilder.UseFileServer(
new FileServerOptions
531 EnableDefaultFiles =
true,
532 EnableDirectoryBrowsing =
false,
533 RedirectToAppendTrailingSlash =
false,
538 logger.LogDebug(
"Web control panel was not included in TGS build!");
540 logger.LogTrace(
"Web control panel disabled!");
544 applicationBuilder.UseRouting();
547 Action<CorsPolicyBuilder>? corsBuilder =
null;
548 if (controlPanelConfiguration.AllowAnyOrigin)
550 logger.LogTrace(
"Access-Control-Allow-Origin: *");
551 corsBuilder = builder => builder.SetIsOriginAllowed(_ =>
true);
553 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
555 logger.LogTrace(
"Access-Control-Allow-Origin: {allowedOrigins}", String.Join(
',', controlPanelConfiguration.AllowedOrigins));
556 corsBuilder = builder => builder.WithOrigins([.. controlPanelConfiguration.AllowedOrigins]);
559 var originalBuilder = corsBuilder;
560 corsBuilder = builder =>
566 .SetPreflightMaxAge(TimeSpan.FromDays(1));
567 originalBuilder?.Invoke(builder);
569 applicationBuilder.UseCors(corsBuilder);
572 applicationBuilder.UseApiCompatibility();
575 applicationBuilder.UseAuthentication();
578 applicationBuilder.UseAuthorization();
581 applicationBuilder.UseDbConflictHandling();
584 applicationBuilder.UseEndpoints(endpoints =>
591 options.Transports = HttpTransportType.ServerSentEvents;
592 options.CloseOnAuthenticationExpiration =
true;
594 .RequireAuthorization()
595 .RequireCors(corsBuilder);
598 endpoints.MapControllers();
605 if (controlPanelConfiguration.Enable)
608 logger.LogDebug(
"Starting hosting on port {httpApiPort}...", serverPortProvider.
HttpApiPort);
621 services.AddHttpContextAccessor();
631 services.AddScoped(provider => (provider
632 .GetRequiredService<IHttpContextAccessor>()
633 .HttpContext ??
throw new InvalidOperationException($
"Unable to resolve {nameof(IAuthenticationContext)} due to no HttpContext being available!"))
635 .GetRequiredService<AuthenticationContextFactory>()
636 .CurrentAuthenticationContext);
640 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
641 .AddJwtBearer(jwtBearerOptions =>
646 jwtBearerOptions.MapInboundClaims =
false;
647 jwtBearerOptions.Events =
new JwtBearerEvents
649 OnMessageReceived = context =>
651 if (String.IsNullOrWhiteSpace(context.Token))
653 var accessToken = context.Request.Query[
"access_token"];
654 var path = context.HttpContext.Request.Path;
656 if (!String.IsNullOrWhiteSpace(accessToken) &&
657 path.StartsWithSegments(
Routes.
HubsRoot, StringComparison.OrdinalIgnoreCase))
659 context.Token = accessToken;
663 return Task.CompletedTask;
Routes to a server actions.
const string HubsRoot
The root route of all hubs.
const string JobsHub
The root route of all hubs.
Base implementation of IEngineInstaller for EngineType.Byond.
Implementation of IEngineInstaller that forwards calls to different IEngineInstaller based on their a...
Implementation of IEngineInstaller for EngineType.OpenDream.
IEngineInstaller for Posix systems.
IEngineInstaller for windows systems.
Implementation of OpenDreamInstaller for Windows systems.
No-op implementation of IEventConsumer.
Constants used for communication with the DMAPI.
static readonly Version InteropVersion
The DMAPI InteropVersion being used.
Configuration options for the web control panel.
DatabaseType DatabaseType
The Configuration.DatabaseType to create.
LogLevel MicrosoftLogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs for Microsoft library sources.
string GetFullLogDirectory(IIOManager ioManager, IAssemblyInformationProvider assemblyInformationProvider, IPlatformIdentifier platformIdentifier)
Gets the evaluated log Directory.
LogLevel LogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs.
bool Disable
If file logging is disabled.
General configuration options.
static readonly Version CurrentConfigVersion
The current ConfigVersion.
bool HostApiDocumentation
If the swagger documentation and UI should be made avaiable.
uint RestartTimeoutMinutes
The timeout minutes for restarting the server.
bool UsingSystemD
If the server is running under SystemD.
Configuration options for the game sessions.
Configuration for the server swarm system.
Configuration options for telemetry.
Configuration for the automatic update system.
Controller for the web control panel.
const string ControlPanelRoute
Route to the ControlPanelController.
IActionResultExecutor<TResult> for LimitedStreamResults.
Sets up dependency injection.
override void ConfigureHostedService(IServiceCollection services)
Configures the IHostedService.
static void AddWatchdog< TSystemWatchdogFactory >(IServiceCollection services, IPostSetupServices postSetupServices)
Adds the IWatchdogFactory implementation.
void ConfigureServices(IServiceCollection services, IAssemblyInformationProvider assemblyInformationProvider, IIOManager ioManager, IPostSetupServices postSetupServices)
Configure the Application's services .
static IServerFactory CreateDefaultServerFactory()
Create the default IServerFactory.
void ConfigureAuthenticationPipeline(IServiceCollection services)
Configure the services for the authentication pipeline.
ITokenFactory? tokenFactory
The ITokenFactory for the Application.
void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, IServerPortProvider serverPortProvider, IAssemblyInformationProvider assemblyInformationProvider, IOptions< ControlPanelConfiguration > controlPanelConfigurationOptions, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, ILogger< Application > logger)
Configure the Application.
Application(IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
Initializes a new instance of the Application class.
readonly IWebHostEnvironment hostingEnvironment
The IWebHostEnvironment for the Application.
Reads from the command pipe opened by the host watchdog.
Handles TGS version reporting, if enabled.
Backend abstract implementation of IDatabaseContext.
IIOManager that resolves paths to Environment.CurrentDirectory.
IFilesystemLinkFactory for POSIX systems.
IPostWriteHandler for POSIX systems.
An IIOManager that resolve relative paths from another IIOManager to a subdirectory of that.
IFilesystemLinkFactory for windows systems.
IPostWriteHandler for Windows systems.
Handles mapping groups for the JobsHub.
A SignalR Hub for pushing job updates.
Attribute for bringing in the master versions list from MSBuild that aren't embedded into assemblies ...
string RawWebpanelVersion
The Version string of the control panel version built.
static MasterVersionsAttribute Instance
Return the Assembly's instance of the MasterVersionsAttribute.
A IClaimsTransformation that maps Claims using an IAuthenticationContext.
An IHubFilter that denies method calls and connections if the IAuthenticationContext is not valid for...
ISystemIdentityFactory for posix systems.
ISystemIdentityFactory for windows systems. Uses long running tasks due to potential networked domain...
Implementation of IServerFactory.
DI root for configuring a SetupWizard.
IConfiguration Configuration
The IConfiguration for the SetupApplication.
Helps keep servers connected to the same database in sync by coordinating updates.
Implements the SystemD notify service protocol.
Implementation of the file transfer service.
Helpers for manipulating the Serilog.Context.LogContext.
static string Template
Common template used for adding our custom log context to serilog.
Implements various filters for Swashbuckle.
const string DocumentationSiteRouteExtension
The path to the hosted documentation site.
static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
Configure the swagger settings.
JsonConverter and IYamlTypeConverter for serializing global::System.Versions in semver format.
SignalR client methods for receiving JobResponses.
For creating IChatManagers.
Factory for creating IRemoteDeploymentManagers.
For downloading and installing game engines for a given system.
Factory for creating IInstances.
Task Ready
Task that completes when the IInstanceManager finishes initializing.
Handler for BridgeParameters.
Factory for creating IGitRemoteFeatures.
For low level interactions with a LibGit2Sharp.IRepository.
Factory for creating LibGit2Sharp.IRepositorys.
Factory for creating IRepositoryManagers.
Factory for ITopicClients.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
Provides access to the server's HttpApiPort.
ushort HttpApiPort
The port the server listens on.
Initiates server self updates.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
For initially setting up a database.
For creating filesystem symbolic links.
Interface for using filesystems.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
string GetDirectoryName(string path)
Gets the directory portion of a given path .
string GetFileNameWithoutExtension(string path)
Gets the file name portion of a path with.
Handles changing file modes/permissions after writing.
For accessing the disk in a synchronous manner.
Manages the runtime of Jobs.
The service that manages everything to do with jobs.
Allows manually triggering jobs hub updates.
For creating and accessing authentication contexts.
Contains various cryptographic functions.
For caching ISystemIdentitys.
Receives notifications about permissions updates.
Factory for ISystemIdentitys.
For creating TokenResponses.
TokenValidationParameters ValidationParameters
The TokenValidationParameters for the ITokenFactory.
Contains IOAuthValidators.
Set of objects needed to configure an Core.Application.
GeneralConfiguration GeneralConfiguration
The Configuration.GeneralConfiguration.
ElasticsearchConfiguration ElasticsearchConfiguration
The Configuration.ElasticsearchConfiguration.
FileLoggingConfiguration FileLoggingConfiguration
The Configuration.FileLoggingConfiguration.
DatabaseConfiguration DatabaseConfiguration
The Configuration.DatabaseConfiguration.
InternalConfiguration InternalConfiguration
The Configuration.InternalConfiguration.
IPlatformIdentifier PlatformIdentifier
The IPlatformIdentifier.
Swarm service operations for the Controllers.SwarmController.
Start and stop controllers for a swarm service.
Used for swarm operations. Functions may be no-op based on configuration.
Service for managing the dotnet-dump installation.
On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort()....
Abstraction for suspending and resuming processes.
Reads and writes to Streams associated with FileTicketResponses.
Service for temporarily storing files to be downloaded or uploaded.
Gets unassigned ports for use by TGS.
EngineType
The type of engine the codebase is using.
DatabaseType
Type of database to user.