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 options => options.AddPolicy(
305 .ModifyOptions(options =>
307 options.EnsureAllNodesCanBeResolved =
true;
308 options.EnableFlagEnums =
true;
311 .ModifyCostOptions(options =>
313 options.EnforceCostLimits =
false;
316 .AddMutationConventions()
317 .AddInMemorySubscriptions(
318 new SubscriptionOptions
320 TopicBufferCapacity = 1024,
322 .AddGlobalObjectIdentification()
323 .AddQueryFieldToMutationPayloads()
324 .ModifyOptions(options =>
326 options.EnableDefer =
true;
328 .ModifyPagingOptions(pagingOptions =>
330 pagingOptions.IncludeTotalCount =
true;
331 pagingOptions.RequirePagingBoundaries =
false;
339 .AddType<StandaloneNode>()
341 .AddType<RemoteGateway>()
342 .AddType<GraphQL.Types.UserName>()
343 .AddType<UnsignedIntType>()
345 .TryAddTypeInterceptor<RightsTypeInterceptor>()
346 .AddQueryType<
Query>()
347 .AddMutationType<Mutation>()
350 void AddTypedContext<TContext>()
355 services.AddDbContextPool<TContext>((serviceProvider, builder) =>
358 builder.EnableSensitiveDataLogging();
360 var databaseConfigOptions = serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>();
361 var databaseConfig = databaseConfigOptions.Value ??
throw new InvalidOperationException(
"DatabaseConfiguration missing!");
362 configureAction(builder, databaseConfig);
364 services.AddScoped<
IDatabaseContext>(x => x.GetRequiredService<TContext>());
373 AddTypedContext<MySqlDatabaseContext>();
376 AddTypedContext<SqlServerDatabaseContext>();
379 AddTypedContext<SqliteDatabaseContext>();
382 AddTypedContext<PostgresSqlDatabaseContext>();
385 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid {0}: {1}!", nameof(
DatabaseType), dbType));
398 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
403 AddWatchdog<WindowsWatchdogFactory>(services, postSetupServices);
417 AddWatchdog<PosixWatchdogFactory>(services, postSetupServices);
428 services.AddSingleton(x =>
new Lazy<IProcessExecutor>(() => x.GetRequiredService<
IProcessExecutor>(),
true));
436 var openDreamRepositoryDirectory = ioManager.
ConcatPath(
437 ioManager.GetPathInLocalDirectory(assemblyInformationProvider),
438 "OpenDreamRepository");
439 services.AddSingleton(
441 .GetRequiredService<IRepositoryManagerFactory>()
442 .CreateRepositoryManager(
445 openDreamRepositoryDirectory),
448 services.AddSingleton(
449 serviceProvider =>
new Dictionary<EngineType, IEngineInstaller>
454 .ToFrozenDictionary());
480 services.AddChatProviderFactory();
503 services.AddFileDownloader();
504 services.AddGitHub();
534 IApplicationBuilder applicationBuilder,
539 IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
540 IOptions<GeneralConfiguration> generalConfigurationOptions,
541 IOptions<SwarmConfiguration> swarmConfigurationOptions,
542 IOptions<InternalConfiguration> internalConfigurationOptions,
543 ILogger<Application> logger)
545 ArgumentNullException.ThrowIfNull(applicationBuilder);
546 ArgumentNullException.ThrowIfNull(serverControl);
550 ArgumentNullException.ThrowIfNull(serverPortProvider);
551 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
553 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
554 var generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
555 var swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
556 var internalConfiguration = internalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(internalConfigurationOptions));
558 ArgumentNullException.ThrowIfNull(logger);
565 applicationBuilder.UseAdditionalRequestLoggingContext(swarmConfiguration);
568 applicationBuilder.UseServerErrorHandling();
571 applicationBuilder.UseServerBranding(assemblyInformationProvider);
574 applicationBuilder.UseDisabledNginxProxyBuffering();
577 applicationBuilder.UseCancelledRequestSuppression();
581 (instanceManager, cancellationToken) => instanceManager.
Ready.WaitAsync(cancellationToken));
583 if (generalConfiguration.HostApiDocumentation)
585 var siteDocPath = Routes.ApiRoot + $
"doc/{SwaggerConfiguration.DocumentName}.json";
586 if (!String.IsNullOrWhiteSpace(controlPanelConfiguration.PublicPath))
587 siteDocPath = controlPanelConfiguration.PublicPath.TrimEnd(
'/') + siteDocPath;
589 applicationBuilder.UseSwagger(options =>
591 options.RouteTemplate = Routes.ApiRoot +
"doc/{documentName}.{json|yaml}";
593 applicationBuilder.UseSwaggerUI(options =>
596 options.SwaggerEndpoint(siteDocPath,
"TGS API");
598 logger.LogTrace(
"Swagger API generation enabled");
602 if (controlPanelConfiguration.Enable)
604 logger.LogInformation(
"Web control panel enabled.");
605 applicationBuilder.UseFileServer(
new FileServerOptions
608 EnableDefaultFiles =
true,
609 EnableDirectoryBrowsing =
false,
610 RedirectToAppendTrailingSlash =
false,
615 logger.LogDebug(
"Web control panel was not included in TGS build!");
617 logger.LogTrace(
"Web control panel disabled!");
621 applicationBuilder.UseRouting();
624 Action<CorsPolicyBuilder>? corsBuilder =
null;
625 if (controlPanelConfiguration.AllowAnyOrigin)
627 logger.LogTrace(
"Access-Control-Allow-Origin: *");
628 corsBuilder = builder => builder.SetIsOriginAllowed(_ =>
true);
630 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
632 logger.LogTrace(
"Access-Control-Allow-Origin: {allowedOrigins}", String.Join(
',', controlPanelConfiguration.AllowedOrigins));
633 corsBuilder = builder => builder.WithOrigins([.. controlPanelConfiguration.AllowedOrigins]);
636 var originalBuilder = corsBuilder;
637 corsBuilder = builder =>
643 .SetPreflightMaxAge(TimeSpan.FromDays(1));
644 originalBuilder?.Invoke(builder);
646 applicationBuilder.UseCors(corsBuilder);
649 applicationBuilder.UseApiCompatibility();
652 applicationBuilder.UseAuthentication();
655 applicationBuilder.UseAuthorization();
658 applicationBuilder.UseDbConflictHandling();
661 applicationBuilder.UseEndpoints(endpoints =>
668 options.Transports = HttpTransportType.ServerSentEvents;
669 options.CloseOnAuthenticationExpiration =
true;
671 .RequireAuthorization()
672 .RequireCors(corsBuilder);
675 endpoints.MapControllers();
677 if (internalConfiguration.EnableGraphQL)
679 logger.LogWarning(
"Enabling GraphQL. This API is experimental and breaking changes may occur at any time!");
680 var gqlOptions =
new GraphQLServerOptions
682 EnableBatching =
true,
685 gqlOptions.Tool.Enable = generalConfiguration.HostApiDocumentation;
689 .WithOptions(gqlOptions);
697 if (controlPanelConfiguration.Enable)
700 logger.LogDebug(
"Starting hosting on port {httpApiPort}...", serverPortProvider.
HttpApiPort);
713 services.AddHttpContextAccessor();
723 services.AddScoped(provider => (provider
724 .GetRequiredService<IHttpContextAccessor>()
725 .HttpContext ??
throw new InvalidOperationException($
"Unable to resolve {nameof(IAuthenticationContext)} due to no HttpContext being available!"))
727 .GetRequiredService<AuthenticationContextFactory>()
728 .CurrentAuthenticationContext);
732 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
733 .AddJwtBearer(jwtBearerOptions =>
738 jwtBearerOptions.MapInboundClaims =
false;
739 jwtBearerOptions.Events =
new JwtBearerEvents
741 OnMessageReceived = context =>
743 if (String.IsNullOrWhiteSpace(context.Token))
745 var accessToken = context.Request.Query[
"access_token"];
746 var path = context.HttpContext.Request.Path;
748 if (!String.IsNullOrWhiteSpace(accessToken) &&
749 path.StartsWithSegments(
Routes.
HubsRoot, StringComparison.OrdinalIgnoreCase))
751 context.Token = accessToken;
755 return Task.CompletedTask;
757 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.
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.