2using System.Collections.Frozen;
3using System.Collections.Generic;
4using System.Globalization;
5using System.Threading.Tasks;
7using Cyberboss.AspNetCore.AsyncInitializer;
9using Elastic.CommonSchema.Serilog;
11using HotChocolate.AspNetCore;
12using HotChocolate.Subscriptions;
13using HotChocolate.Types;
15using Microsoft.AspNetCore.Authentication;
16using Microsoft.AspNetCore.Authentication.JwtBearer;
17using Microsoft.AspNetCore.Builder;
18using Microsoft.AspNetCore.Cors.Infrastructure;
19using Microsoft.AspNetCore.Hosting;
20using Microsoft.AspNetCore.Http;
21using Microsoft.AspNetCore.Http.Connections;
22using Microsoft.AspNetCore.Identity;
23using Microsoft.AspNetCore.Mvc.Infrastructure;
24using Microsoft.AspNetCore.SignalR;
25using Microsoft.Extensions.Configuration;
26using Microsoft.Extensions.DependencyInjection;
27using Microsoft.Extensions.Hosting;
28using Microsoft.Extensions.Logging;
29using Microsoft.Extensions.Options;
35using Serilog.Formatting.Display;
36using Serilog.Sinks.Elasticsearch;
81#pragma warning disable CA1506
103 assemblyInformationProvider,
116 if (postSetupServices.GeneralConfiguration.UseBasicWatchdog)
128 IConfiguration configuration,
130 : base(configuration)
143 IServiceCollection services,
150 ArgumentNullException.ThrowIfNull(postSetupServices);
160 services.AddOptions();
163 services.Configure<HostOptions>(
166 static LogEventLevel? ConvertSeriLogLevel(LogLevel logLevel) =>
169 LogLevel.Critical => LogEventLevel.Fatal,
170 LogLevel.Debug => LogEventLevel.Debug,
171 LogLevel.Error => LogEventLevel.Error,
172 LogLevel.Information => LogEventLevel.Information,
173 LogLevel.Trace => LogEventLevel.Verbose,
174 LogLevel.Warning => LogEventLevel.Warning,
175 LogLevel.None =>
null,
176 _ =>
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid log level {0}", logLevel)),
181 services.SetupLogging(
184 if (microsoftEventLevel.HasValue)
186 config.MinimumLevel.Override(
"Microsoft", microsoftEventLevel.Value);
187 config.MinimumLevel.Override(
"System.Net.Http.HttpClient", microsoftEventLevel.Value);
197 assemblyInformationProvider,
202 var formatter =
new MessageTemplateTextFormatter(
205 +
"): [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
208 logPath = ioManager.
ConcatPath(logPath,
"tgs-.log");
209 var rollingFileConfig = sinkConfig.File(
212 logEventLevel ?? LogEventLevel.Verbose,
214 flushToDiskInterval: TimeSpan.FromSeconds(2),
215 rollingInterval: RollingInterval.Day,
216 rollOnFileSizeLimit:
true);
218 elasticsearchConfiguration.Enable
219 ?
new ElasticsearchSinkOptions(elasticsearchConfiguration.Host ??
throw new InvalidOperationException($
"Missing {ElasticsearchConfiguration.Section}:{nameof(elasticsearchConfiguration.Host)}!"))
223 ModifyConnectionSettings = connectionConfigration => (!String.IsNullOrWhiteSpace(elasticsearchConfiguration.Username) && !String.IsNullOrWhiteSpace(elasticsearchConfiguration.Password))
224 ? connectionConfigration
225 .BasicAuthentication(
226 elasticsearchConfiguration.Username,
227 elasticsearchConfiguration.Password)
228 .ServerCertificateValidationCallback((o, certificate, chain, errors) =>
true)
230 CustomFormatter =
new EcsTextFormatter(),
231 AutoRegisterTemplate =
true,
232 AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
233 IndexFormat =
"tgs-logs",
243 var jsonVersionConverterList =
new List<JsonConverter>
248 void ConfigureNewtonsoftJsonSerializerSettingsForApi(JsonSerializerSettings settings)
250 settings.NullValueHandling = NullValueHandling.Ignore;
251 settings.CheckAdditionalContent =
true;
252 settings.MissingMemberHandling = MissingMemberHandling.Error;
253 settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
254 settings.Converters = jsonVersionConverterList;
260 options.ReturnHttpNotAcceptable =
true;
261 options.RespectBrowserAcceptHeader =
true;
263 .AddNewtonsoftJson(options =>
265 options.AllowInputFormatterExceptionMessages =
true;
266 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.SerializerSettings);
274 .AddNewtonsoftJsonProtocol(options =>
276 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.PayloadSerializerSettings);
284 var assemblyDocumentationPath = GetDocumentationFilePath(GetType().Assembly.Location);
285 var apiDocumentationPath = GetDocumentationFilePath(typeof(
ApiHeaders).Assembly.Location);
287 services.AddSwaggerGenNewtonsoftSupport();
294 services.AddHttpClient();
302 .ModifyOptions(options =>
304 options.EnsureAllNodesCanBeResolved =
true;
305 options.EnableFlagEnums =
true;
308 .ModifyCostOptions(options =>
310 options.EnforceCostLimits =
false;
313 .AddMutationConventions()
314 .AddInMemorySubscriptions(
315 new SubscriptionOptions
317 TopicBufferCapacity = 1024,
319 .AddGlobalObjectIdentification()
320 .AddQueryFieldToMutationPayloads()
321 .ModifyOptions(options =>
323 options.EnableDefer =
true;
325 .ModifyPagingOptions(pagingOptions =>
327 pagingOptions.IncludeTotalCount =
true;
328 pagingOptions.RequirePagingBoundaries =
false;
336 .AddType<StandaloneNode>()
338 .AddType<RemoteGateway>()
339 .AddType<GraphQL.Types.UserName>()
340 .AddType<UnsignedIntType>()
342 .TryAddTypeInterceptor<RightsTypeInterceptor>()
343 .AddQueryType<
Query>()
344 .AddMutationType<Mutation>()
347 void AddTypedContext<TContext>()
352 services.AddDbContextPool<TContext>((serviceProvider, builder) =>
355 builder.EnableSensitiveDataLogging();
357 var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
358 var databaseConfig = databaseConfigOptions.Value ??
throw new InvalidOperationException(
"DatabaseConfiguration missing!");
359 configureAction(builder, databaseConfig);
361 services.AddScoped<
IDatabaseContext>(x => x.GetRequiredService<TContext>());
370 AddTypedContext<MySqlDatabaseContext>();
373 AddTypedContext<SqlServerDatabaseContext>();
376 AddTypedContext<SqliteDatabaseContext>();
379 AddTypedContext<PostgresSqlDatabaseContext>();
382 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid {0}: {1}!", nameof(
DatabaseType), dbType));
395 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
400 AddWatchdog<WindowsWatchdogFactory>(services, postSetupServices);
414 AddWatchdog<PosixWatchdogFactory>(services, postSetupServices);
425 services.AddSingleton(x =>
new Lazy<IProcessExecutor>(() => x.GetRequiredService<
IProcessExecutor>(),
true));
433 var openDreamRepositoryDirectory = ioManager.
ConcatPath(
434 ioManager.GetPathInLocalDirectory(assemblyInformationProvider),
435 "OpenDreamRepository");
436 services.AddSingleton(
438 .GetRequiredService<IRepositoryManagerFactory>()
439 .CreateRepositoryManager(
442 openDreamRepositoryDirectory),
445 services.AddSingleton(
446 serviceProvider =>
new Dictionary<EngineType, IEngineInstaller>
451 .ToFrozenDictionary());
477 services.AddChatProviderFactory();
500 services.AddFileDownloader();
501 services.AddGitHub();
531 IApplicationBuilder applicationBuilder,
536 IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
537 IOptions<GeneralConfiguration> generalConfigurationOptions,
538 IOptions<SwarmConfiguration> swarmConfigurationOptions,
539 IOptions<InternalConfiguration> internalConfigurationOptions,
540 ILogger<Application> logger)
542 ArgumentNullException.ThrowIfNull(applicationBuilder);
543 ArgumentNullException.ThrowIfNull(serverControl);
547 ArgumentNullException.ThrowIfNull(serverPortProvider);
548 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
550 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
551 var generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
552 var swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
553 var internalConfiguration = internalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(internalConfigurationOptions));
555 ArgumentNullException.ThrowIfNull(logger);
562 applicationBuilder.UseAdditionalRequestLoggingContext(swarmConfiguration);
565 applicationBuilder.UseServerErrorHandling();
568 applicationBuilder.UseServerBranding(assemblyInformationProvider);
571 applicationBuilder.UseDisabledNginxProxyBuffering();
574 applicationBuilder.UseCancelledRequestSuppression();
578 (instanceManager, cancellationToken) => instanceManager.
Ready.WaitAsync(cancellationToken));
580 if (generalConfiguration.HostApiDocumentation)
582 var siteDocPath = Routes.ApiRoot + $
"doc/{SwaggerConfiguration.DocumentName}.json";
583 if (!String.IsNullOrWhiteSpace(controlPanelConfiguration.PublicPath))
584 siteDocPath = controlPanelConfiguration.PublicPath.TrimEnd(
'/') + siteDocPath;
586 applicationBuilder.UseSwagger(options =>
588 options.RouteTemplate = Routes.ApiRoot +
"doc/{documentName}.{json|yaml}";
590 applicationBuilder.UseSwaggerUI(options =>
593 options.SwaggerEndpoint(siteDocPath,
"TGS API");
595 logger.LogTrace(
"Swagger API generation enabled");
599 if (controlPanelConfiguration.Enable)
601 logger.LogInformation(
"Web control panel enabled.");
602 applicationBuilder.UseFileServer(
new FileServerOptions
605 EnableDefaultFiles =
true,
606 EnableDirectoryBrowsing =
false,
607 RedirectToAppendTrailingSlash =
false,
612 logger.LogDebug(
"Web control panel was not included in TGS build!");
614 logger.LogTrace(
"Web control panel disabled!");
618 applicationBuilder.UseRouting();
621 Action<CorsPolicyBuilder>? corsBuilder =
null;
622 if (controlPanelConfiguration.AllowAnyOrigin)
624 logger.LogTrace(
"Access-Control-Allow-Origin: *");
625 corsBuilder = builder => builder.SetIsOriginAllowed(_ =>
true);
627 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
629 logger.LogTrace(
"Access-Control-Allow-Origin: {allowedOrigins}", String.Join(
',', controlPanelConfiguration.AllowedOrigins));
630 corsBuilder = builder => builder.WithOrigins([.. controlPanelConfiguration.AllowedOrigins]);
633 var originalBuilder = corsBuilder;
634 corsBuilder = builder =>
640 .SetPreflightMaxAge(TimeSpan.FromDays(1));
641 originalBuilder?.Invoke(builder);
643 applicationBuilder.UseCors(corsBuilder);
646 applicationBuilder.UseApiCompatibility();
649 applicationBuilder.UseAuthentication();
652 applicationBuilder.UseAuthorization();
655 applicationBuilder.UseDbConflictHandling();
658 applicationBuilder.UseEndpoints(endpoints =>
665 options.Transports = HttpTransportType.ServerSentEvents;
666 options.CloseOnAuthenticationExpiration =
true;
668 .RequireAuthorization()
669 .RequireCors(corsBuilder);
672 endpoints.MapControllers();
674 if (internalConfiguration.EnableGraphQL)
676 logger.LogWarning(
"Enabling GraphQL. This API is experimental and breaking changes may occur at any time!");
677 var gqlOptions =
new GraphQLServerOptions
679 EnableBatching =
true,
682 gqlOptions.Tool.Enable = generalConfiguration.HostApiDocumentation;
686 .WithOptions(gqlOptions);
694 if (controlPanelConfiguration.Enable)
697 logger.LogDebug(
"Starting hosting on port {httpApiPort}...", serverPortProvider.
HttpApiPort);
710 services.AddHttpContextAccessor();
720 services.AddScoped(provider => (provider
721 .GetRequiredService<IHttpContextAccessor>()
722 .HttpContext ??
throw new InvalidOperationException($
"Unable to resolve {nameof(IAuthenticationContext)} due to no HttpContext being available!"))
724 .GetRequiredService<AuthenticationContextFactory>()
725 .CurrentAuthenticationContext);
729 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
730 .AddJwtBearer(jwtBearerOptions =>
735 jwtBearerOptions.MapInboundClaims =
false;
736 jwtBearerOptions.Events =
new JwtBearerEvents
738 OnMessageReceived = context =>
740 if (String.IsNullOrWhiteSpace(context.Token))
742 var accessToken = context.Request.Query[
"access_token"];
743 var path = context.HttpContext.Request.Path;
745 if (!String.IsNullOrWhiteSpace(accessToken) &&
746 path.StartsWithSegments(
Routes.
HubsRoot, StringComparison.OrdinalIgnoreCase))
748 context.Token = accessToken;
752 return Task.CompletedTask;
754 OnTokenValidated = context => context
Routes to a server actions.
const string GraphQL
The GraphQL route.
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.
Base Controller for API functions.
const ushort MaximumPageSize
Maximum size of Paginated<TModel> results.
const ushort DefaultPageSize
Default size of Paginated<TModel> results.
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.
Application(IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
Initializes a new instance of the Application class.
void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, IServerPortProvider serverPortProvider, IAssemblyInformationProvider assemblyInformationProvider, IOptions< ControlPanelConfiguration > controlPanelConfigurationOptions, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, IOptions< InternalConfiguration > internalConfigurationOptions, ILogger< Application > logger)
Configure the Application.
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.
IErrorFilter for transforming ErrorMessageResponse-like Exception.
GraphQL query global::System.Type.
A ScalarType<TRuntimeType, TLiteral> for semantic Versions.
Root type for GraphQL subscriptions.
IGateway for the SwarmNode this query is executing on.
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.
IAuthority for administrative server operations.
Invokes TAuthority s from GraphQL endpoints.
IAuthority for authenticating with the server.
IAuthority for managing PermissionSets.
Invokes TAuthority methods and generates IActionResult responses.
IAuthority for managing Users.
IAuthority for managing UserGroups.
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.
Contains various cryptographic functions.
For caching ISystemIdentitys.
Receives notifications about permissions updates.
Handles invalidating user sessions.
Factory for ISystemIdentitys.
For creating TokenResponses.
TokenValidationParameters ValidationParameters
The TokenValidationParameters for the ITokenFactory.
Handles TokenValidatedContexts.
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.