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;
37using Serilog.Formatting.Display;
38using Serilog.Sinks.Elasticsearch;
83#pragma warning disable CA1506
105 assemblyInformationProvider,
118 if (postSetupServices.GeneralConfiguration.UseBasicWatchdog)
130 IConfiguration configuration,
132 : base(configuration)
145 IServiceCollection services,
152 ArgumentNullException.ThrowIfNull(postSetupServices);
162 services.AddOptions();
165 services.Configure<HostOptions>(
168 static LogEventLevel? ConvertSeriLogLevel(LogLevel logLevel) =>
171 LogLevel.Critical => LogEventLevel.Fatal,
172 LogLevel.Debug => LogEventLevel.Debug,
173 LogLevel.Error => LogEventLevel.Error,
174 LogLevel.Information => LogEventLevel.Information,
175 LogLevel.Trace => LogEventLevel.Verbose,
176 LogLevel.Warning => LogEventLevel.Warning,
177 LogLevel.None =>
null,
178 _ =>
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid log level {0}", logLevel)),
183 services.SetupLogging(
186 if (microsoftEventLevel.HasValue)
188 config.MinimumLevel.Override(
"Microsoft", microsoftEventLevel.Value);
189 config.MinimumLevel.Override(
"System.Net.Http.HttpClient", microsoftEventLevel.Value);
199 assemblyInformationProvider,
204 var formatter =
new MessageTemplateTextFormatter(
207 +
"): [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}",
210 logPath = ioManager.
ConcatPath(logPath,
"tgs-.log");
211 var rollingFileConfig = sinkConfig.File(
214 logEventLevel ?? LogEventLevel.Verbose,
216 flushToDiskInterval: TimeSpan.FromSeconds(2),
217 rollingInterval: RollingInterval.Day,
218 rollOnFileSizeLimit:
true);
220 elasticsearchConfiguration.Enable
221 ?
new ElasticsearchSinkOptions(elasticsearchConfiguration.Host ??
throw new InvalidOperationException($
"Missing {ElasticsearchConfiguration.Section}:{nameof(elasticsearchConfiguration.Host)}!"))
225 ModifyConnectionSettings = connectionConfigration => (!String.IsNullOrWhiteSpace(elasticsearchConfiguration.Username) && !String.IsNullOrWhiteSpace(elasticsearchConfiguration.Password))
226 ? connectionConfigration
227 .BasicAuthentication(
228 elasticsearchConfiguration.Username,
229 elasticsearchConfiguration.Password)
230 .ServerCertificateValidationCallback((o, certificate, chain, errors) =>
true)
232 CustomFormatter =
new EcsTextFormatter(),
233 AutoRegisterTemplate =
true,
234 AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
235 IndexFormat =
"tgs-logs",
245 var jsonVersionConverterList =
new List<JsonConverter>
250 void ConfigureNewtonsoftJsonSerializerSettingsForApi(JsonSerializerSettings settings)
252 settings.NullValueHandling = NullValueHandling.Ignore;
253 settings.CheckAdditionalContent =
true;
254 settings.MissingMemberHandling = MissingMemberHandling.Error;
255 settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
256 settings.Converters = jsonVersionConverterList;
262 options.ReturnHttpNotAcceptable =
true;
263 options.RespectBrowserAcceptHeader =
true;
265 .AddNewtonsoftJson(options =>
267 options.AllowInputFormatterExceptionMessages =
true;
268 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.SerializerSettings);
276 .AddNewtonsoftJsonProtocol(options =>
278 ConfigureNewtonsoftJsonSerializerSettingsForApi(options.PayloadSerializerSettings);
286 var assemblyDocumentationPath = GetDocumentationFilePath(GetType().Assembly.Location);
287 var apiDocumentationPath = GetDocumentationFilePath(typeof(
ApiHeaders).Assembly.Location);
289 services.AddSwaggerGenNewtonsoftSupport();
296 services.AddHttpClient();
302 services.AddSingleton<IMetricFactory>(_ => Metrics.DefaultFactory);
303 services.AddSingleton<ICollectorRegistry>(_ => Metrics.DefaultRegistry);
306 services.AddMetricServer(options => options.Port = prometheusPort.Value);
308 services.UseHttpClientMetrics();
310 var healthChecksBuilder = services
312 .ForwardToPrometheus();
319 options => options.AddPolicy(
322 .ModifyOptions(options =>
324 options.EnsureAllNodesCanBeResolved =
true;
325 options.EnableFlagEnums =
true;
328 .ModifyCostOptions(options =>
330 options.EnforceCostLimits =
false;
333 .AddMutationConventions()
334 .AddInMemorySubscriptions(
335 new SubscriptionOptions
337 TopicBufferCapacity = 1024,
339 .AddGlobalObjectIdentification()
340 .AddQueryFieldToMutationPayloads()
341 .ModifyOptions(options =>
343 options.EnableDefer =
true;
345 .ModifyPagingOptions(pagingOptions =>
347 pagingOptions.IncludeTotalCount =
true;
348 pagingOptions.RequirePagingBoundaries =
false;
356 .AddType<StandaloneNode>()
358 .AddType<RemoteGateway>()
359 .AddType<GraphQL.Types.UserName>()
360 .AddType<UnsignedIntType>()
362 .TryAddTypeInterceptor<RightsTypeInterceptor>()
363 .AddQueryType<
Query>()
364 .AddMutationType<Mutation>()
367 void AddTypedContext<TContext>()
372 services.AddDbContextPool<TContext>((serviceProvider, builder) =>
375 builder.EnableSensitiveDataLogging();
377 var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
378 var databaseConfig = databaseConfigOptions.Value ??
throw new InvalidOperationException(
"DatabaseConfiguration missing!");
379 configureAction(builder, databaseConfig);
381 services.AddScoped<
IDatabaseContext>(x => x.GetRequiredService<TContext>());
384 .AddDbContextCheck<TContext>();
393 AddTypedContext<MySqlDatabaseContext>();
396 AddTypedContext<SqlServerDatabaseContext>();
399 AddTypedContext<SqliteDatabaseContext>();
402 AddTypedContext<PostgresSqlDatabaseContext>();
405 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid {0}: {1}!", nameof(
DatabaseType), dbType));
418 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
423 AddWatchdog<WindowsWatchdogFactory>(services, postSetupServices);
437 AddWatchdog<PosixWatchdogFactory>(services, postSetupServices);
448 services.AddSingleton(x =>
new Lazy<IProcessExecutor>(() => x.GetRequiredService<
IProcessExecutor>(),
true));
456 var openDreamRepositoryDirectory = ioManager.
ConcatPath(
457 ioManager.GetPathInLocalDirectory(assemblyInformationProvider),
458 "OpenDreamRepository");
459 services.AddSingleton(
461 .GetRequiredService<IRepositoryManagerFactory>()
462 .CreateRepositoryManager(
465 openDreamRepositoryDirectory),
468 services.AddSingleton(
469 serviceProvider =>
new Dictionary<EngineType, IEngineInstaller>
474 .ToFrozenDictionary());
500 services.AddChatProviderFactory();
523 services.AddFileDownloader();
524 services.AddGitHub();
555 IApplicationBuilder applicationBuilder,
560 IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
561 IOptions<GeneralConfiguration> generalConfigurationOptions,
562 IOptions<DatabaseConfiguration> databaseConfigurationOptions,
563 IOptions<SwarmConfiguration> swarmConfigurationOptions,
564 IOptions<InternalConfiguration> internalConfigurationOptions,
565 ILogger<Application> logger)
567 ArgumentNullException.ThrowIfNull(applicationBuilder);
568 ArgumentNullException.ThrowIfNull(serverControl);
572 ArgumentNullException.ThrowIfNull(serverPortProvider);
573 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
575 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
576 var generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
577 var databaseConfiguration = databaseConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(databaseConfigurationOptions));
578 var swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
579 var internalConfiguration = internalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(internalConfigurationOptions));
581 ArgumentNullException.ThrowIfNull(logger);
583 logger.LogDebug(
"Database provider: {provider}", databaseConfiguration.DatabaseType);
589 applicationBuilder.UseAdditionalRequestLoggingContext(swarmConfiguration);
592 applicationBuilder.UseServerErrorHandling();
595 applicationBuilder.UseHttpMetrics();
598 applicationBuilder.UseServerBranding(assemblyInformationProvider);
601 applicationBuilder.UseDisabledNginxProxyBuffering();
604 applicationBuilder.UseCancelledRequestSuppression();
608 (instanceManager, cancellationToken) => instanceManager.
Ready.WaitAsync(cancellationToken));
610 if (generalConfiguration.HostApiDocumentation)
612 var siteDocPath = Routes.ApiRoot + $
"doc/{SwaggerConfiguration.DocumentName}.json";
613 if (!String.IsNullOrWhiteSpace(controlPanelConfiguration.PublicPath))
614 siteDocPath = controlPanelConfiguration.PublicPath.TrimEnd(
'/') + siteDocPath;
616 applicationBuilder.UseSwagger(options =>
618 options.RouteTemplate = Routes.ApiRoot +
"doc/{documentName}.{json|yaml}";
620 applicationBuilder.UseSwaggerUI(options =>
623 options.SwaggerEndpoint(siteDocPath,
"TGS API");
625 logger.LogTrace(
"Swagger API generation enabled");
629 if (controlPanelConfiguration.Enable)
631 logger.LogInformation(
"Web control panel enabled.");
632 applicationBuilder.UseFileServer(
new FileServerOptions
635 EnableDefaultFiles =
true,
636 EnableDirectoryBrowsing =
false,
637 RedirectToAppendTrailingSlash =
false,
642 logger.LogDebug(
"Web control panel was not included in TGS build!");
644 logger.LogTrace(
"Web control panel disabled!");
648 applicationBuilder.UseRouting();
651 Action<CorsPolicyBuilder>? corsBuilder =
null;
652 if (controlPanelConfiguration.AllowAnyOrigin)
654 logger.LogTrace(
"Access-Control-Allow-Origin: *");
655 corsBuilder = builder => builder.SetIsOriginAllowed(_ =>
true);
657 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
659 logger.LogTrace(
"Access-Control-Allow-Origin: {allowedOrigins}", String.Join(
',', controlPanelConfiguration.AllowedOrigins));
660 corsBuilder = builder => builder.WithOrigins([.. controlPanelConfiguration.AllowedOrigins]);
663 var originalBuilder = corsBuilder;
664 corsBuilder = builder =>
670 .SetPreflightMaxAge(TimeSpan.FromDays(1));
671 originalBuilder?.Invoke(builder);
673 applicationBuilder.UseCors(corsBuilder);
676 applicationBuilder.UseApiCompatibility();
679 applicationBuilder.UseAuthentication();
682 applicationBuilder.UseAuthorization();
685 applicationBuilder.UseDbConflictHandling();
688 applicationBuilder.UseEndpoints(endpoints =>
695 options.Transports = HttpTransportType.ServerSentEvents;
696 options.CloseOnAuthenticationExpiration =
true;
698 .RequireAuthorization()
699 .RequireCors(corsBuilder);
702 endpoints.MapControllers();
704 if (internalConfiguration.EnableGraphQL)
706 logger.LogWarning(
"Enabling GraphQL. This API is experimental and breaking changes may occur at any time!");
707 var gqlOptions =
new GraphQLServerOptions
709 EnableBatching =
true,
712 gqlOptions.Tool.Enable = generalConfiguration.HostApiDocumentation;
716 .WithOptions(gqlOptions);
719 if (generalConfiguration.PrometheusPort.HasValue)
720 if (generalConfiguration.PrometheusPort == generalConfiguration.ApiPort)
722 endpoints.MapMetrics();
723 logger.LogDebug(
"Prometheus being hosted alongside server");
726 logger.LogDebug(
"Prometheus being hosted on port {prometheusPort}", generalConfiguration.PrometheusPort);
728 logger.LogTrace(
"Prometheus disabled");
730 endpoints.MapHealthChecks(
"/health");
737 if (controlPanelConfiguration.Enable)
740 logger.LogDebug(
"Starting hosting on port {httpApiPort}...", serverPortProvider.
HttpApiPort);
753 services.AddHttpContextAccessor();
763 services.AddScoped(provider => (provider
764 .GetRequiredService<IHttpContextAccessor>()
765 .HttpContext ??
throw new InvalidOperationException($
"Unable to resolve {nameof(IAuthenticationContext)} due to no HttpContext being available!"))
767 .GetRequiredService<AuthenticationContextFactory>()
768 .CurrentAuthenticationContext);
772 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
773 .AddJwtBearer(jwtBearerOptions =>
778 jwtBearerOptions.MapInboundClaims =
false;
779 jwtBearerOptions.Events =
new JwtBearerEvents
781 OnMessageReceived = context =>
783 if (String.IsNullOrWhiteSpace(context.Token))
785 var accessToken = context.Request.Query[
"access_token"];
786 var path = context.HttpContext.Request.Path;
788 if (!String.IsNullOrWhiteSpace(accessToken) &&
789 path.StartsWithSegments(
Routes.
HubsRoot, StringComparison.OrdinalIgnoreCase))
791 context.Token = accessToken;
795 return Task.CompletedTask;
797 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.
ushort? PrometheusPort
The port Prometheus metrics are published on, if any.
ushort ApiPort
The port the TGS API listens on.
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< DatabaseConfiguration > databaseConfigurationOptions, 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.
Helper for using the AuthorizeAttribute with the Api.Rights system.
const string PolicyName
Policy used to apply global requirement of UserEnabledRole.
const string UserEnabledRole
Role used to indicate access to the server is allowed.
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.