1 using Byond.TopicSender;
2 using Cyberboss.AspNetCore.AsyncInitializer;
3 using Microsoft.AspNetCore.Authentication.JwtBearer;
4 using Microsoft.AspNetCore.Builder;
5 using Microsoft.AspNetCore.Cors.Infrastructure;
6 using Microsoft.AspNetCore.Hosting;
7 using Microsoft.AspNetCore.Identity;
8 using Microsoft.Extensions.Configuration;
9 using Microsoft.Extensions.DependencyInjection;
10 using Microsoft.Extensions.Hosting;
11 using Microsoft.Extensions.Logging;
12 using Microsoft.Extensions.Options;
13 using Microsoft.Extensions.Primitives;
14 using Newtonsoft.Json;
15 using Newtonsoft.Json.Converters;
18 using Serilog.Formatting.Display;
20 using System.Globalization;
21 using System.IdentityModel.Tokens.Jwt;
23 using System.Threading.Tasks;
40 public string VersionPrefix =>
"tgstation-server";
43 public Version Version {
get; }
46 public string VersionString {
get; }
73 public Application(IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment)
75 this.configuration = configuration ??
throw new ArgumentNullException(nameof(configuration));
76 this.hostingEnvironment = hostingEnvironment ??
throw new ArgumentNullException(nameof(hostingEnvironment));
78 startupTcs =
new TaskCompletionSource<object>();
80 Version = Assembly.GetExecutingAssembly().GetName().Version;
81 VersionString = String.Format(CultureInfo.InvariantCulture,
"{0} v{1}", VersionPrefix, Version);
91 throw new ArgumentNullException(nameof(services));
104 services.AddOptions();
108 services.AddSingleton<
IConsole, IO.Console>();
124 using (var provider = services.BuildServiceProvider())
127 var setupWizard = provider.GetRequiredService<
ISetupWizard>();
128 var applicationLifetime = provider.GetRequiredService<Microsoft.AspNetCore.Hosting.IApplicationLifetime>();
129 var setupWizardRan = setupWizard.
CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult();
132 var generalOptions = provider.GetRequiredService<IOptions<GeneralConfiguration>>();
133 generalConfiguration = generalOptions.Value;
136 if (setupWizardRan && generalConfiguration.SetupWizardMode ==
SetupWizardMode.Only)
138 throw new OperationCanceledException(
"Exiting due to SetupWizardMode configuration!");
140 var dbOptions = provider.GetRequiredService<IOptions<DatabaseConfiguration>>();
141 databaseConfiguration = dbOptions.Value;
143 var loggingOptions = provider.GetRequiredService<IOptions<FileLoggingConfiguration>>();
144 fileLoggingConfiguration = loggingOptions.Value;
146 var controlPanelOptions = provider.GetRequiredService<IOptions<ControlPanelConfiguration>>();
147 controlPanelConfiguration = controlPanelOptions.Value;
149 ioManager = provider.GetRequiredService<
IIOManager>();
154 if (!fileLoggingConfiguration.
Disable)
155 services.AddLogging(builder =>
158 var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.
Directory) ? fileLoggingConfiguration.
Directory : ioManager.
ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix,
"Logs");
160 logPath = ioManager.
ConcatPath(logPath,
"tgs-{Date}.log");
162 LogEventLevel? ConvertLogLevel(LogLevel logLevel)
166 case LogLevel.Critical:
167 return LogEventLevel.Fatal;
169 return LogEventLevel.Debug;
171 return LogEventLevel.Error;
172 case LogLevel.Information:
173 return LogEventLevel.Information;
175 return LogEventLevel.Verbose;
176 case LogLevel.Warning:
177 return LogEventLevel.Warning;
181 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid log level {0}", logLevel));
185 var logEventLevel = ConvertLogLevel(fileLoggingConfiguration.
LogLevel);
186 var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.
MicrosoftLogLevel);
188 var formatter =
new MessageTemplateTextFormatter(
"{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null);
190 var configuration =
new LoggerConfiguration()
191 .Enrich.FromLogContext()
192 .WriteTo.Async(w => w.RollingFile(formatter, logPath, shared:
true, flushToDiskInterval: TimeSpan.FromSeconds(2)));
194 if (logEventLevel.HasValue)
195 configuration.MinimumLevel.Is(logEventLevel.Value);
197 if (microsoftEventLevel.HasValue)
198 configuration.MinimumLevel.Override(
"Microsoft", microsoftEventLevel.Value);
200 builder.AddSerilog(configuration.CreateLogger(),
true);
204 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions =>
209 jwtBearerOptions.Events =
new JwtBearerEvents
212 OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService<
IClaimsInjector>().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted)
217 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
220 services.AddMvc().AddJsonOptions(options =>
222 options.AllowInputFormatterExceptionMessages =
true;
223 options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
224 options.SerializerSettings.CheckAdditionalContent =
true;
225 options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
226 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
227 options.SerializerSettings.Converters =
new[] {
new VersionConverter() };
231 services.AddDetectionCore().AddBrowser();
239 services.AddDbContext<TContext>(builder =>
241 if (hostingEnvironment.IsDevelopment())
242 builder.EnableSensitiveDataLogging();
244 services.AddScoped<
IDatabaseContext>(x => x.GetRequiredService<TContext>());
253 AddTypedContext<MySqlDatabaseContext>();
256 AddTypedContext<SqlServerDatabaseContext>();
259 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid {0}: {1}!", nameof(
DatabaseType), dbType));
271 services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
298 services.AddSingleton<IByondTopicSender>(
new ByondTopicSender
300 ReceiveTimeout = generalConfiguration.ByondTopicTimeout,
301 SendTimeout = generalConfiguration.ByondTopicTimeout
327 public void Configure(IApplicationBuilder applicationBuilder,
IServerControl serverControl,
ITokenFactory tokenFactory, IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions, ILogger<Application> logger)
329 if (applicationBuilder == null)
330 throw new ArgumentNullException(nameof(applicationBuilder));
331 if (serverControl == null)
332 throw new ArgumentNullException(nameof(serverControl));
334 this.tokenFactory = tokenFactory ??
throw new ArgumentNullException(nameof(tokenFactory));
336 var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
339 throw new ArgumentNullException(nameof(logger));
341 logger.LogInformation(VersionString);
342 logger.LogDebug(
"Content Root: {0}", hostingEnvironment.ContentRootPath);
343 logger.LogTrace(
"Web Root: {0}", hostingEnvironment.WebRootPath);
347 ChangeToken.OnChange(configuration.GetReloadToken, () => serverControl.
Restart());
352 applicationBuilder.UseDeveloperExceptionPage();
355 applicationBuilder.UseCancelledRequestSuppression();
358 Action<CorsPolicyBuilder> corsBuilder = null;
359 if (controlPanelConfiguration.AllowAnyOrigin)
361 logger.LogTrace(
"Access-Control-Allow-Origin: *");
362 corsBuilder = builder => builder.AllowAnyOrigin();
364 else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
366 logger.LogTrace(
"Access-Control-Allow-Origin: ", String.Join(
',', controlPanelConfiguration.AllowedOrigins));
367 corsBuilder = builder => builder.WithOrigins(controlPanelConfiguration.AllowedOrigins.ToArray());
370 if (corsBuilder != null)
372 var originalBuilder = corsBuilder;
373 corsBuilder = builder => originalBuilder(builder.AllowAnyHeader().AllowAnyMethod());
374 applicationBuilder.UseCors(corsBuilder);
378 applicationBuilder.UseAsyncInitialization(async cancellationToken =>
380 using (cancellationToken.Register(() => startupTcs.SetCanceled()))
381 await startupTcs.Task.ConfigureAwait(
false);
385 if (controlPanelConfiguration.Enable)
387 logger.LogWarning(
"Web control panel enabled. This is a highly WIP feature!");
388 applicationBuilder.UseStaticFiles();
391 logger.LogDebug(
"Web control panel disabled!");
394 applicationBuilder.UseAuthentication();
397 applicationBuilder.UseDbConflictHandling();
400 applicationBuilder.UseMvc();
406 public void Ready(Exception initializationError)
410 if (startupTcs.Task.IsCompleted)
411 throw new InvalidOperationException(
"Ready has already been called!");
412 if (initializationError == null)
413 startupTcs.SetResult(null);
415 startupTcs.SetException(initializationError);
LogLevel MicrosoftLogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs for Microsoft library sources ...
Manages the runtime of Jobs
For creating and accessing authentication contexts
ISymlinkFactory for windows systems
readonly Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment
The Microsoft.AspNetCore.Hosting.IHostingEnvironment for the Application
IPostWriteHandler for POSIX systems
List< string > AllowedOrigins
Origins allowed for CORS requests
bool Disable
If file logging is disabled
void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, IOptions< ControlPanelConfiguration > controlPanelConfigurationOptions, ILogger< Application > logger)
Configure the Application
Configures the ASP.NET Core web application
SetupWizardMode
Determines if the Core.ISetupWizard will run
Abstraction for System.Console
bool WatchdogPresent
if live updates are supported, . ApplyUpdate(Version, Uri, IIOManager) and Restart will fail if this ...
For creating DbConnection
LogLevel LogLevel
The minimum Microsoft.Extensions.Logging.LogLevel to display in logs
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
For launching IProcess'
string Directory
Where log files are stored
ITokenFactory tokenFactory
The ITokenFactory for the Application
For initially seeding a database
For creating filesystem symbolic links
For waiting asynchronously
ISystemIdentityFactory for windows systems. Uses long running tasks due to potential networked domain...
Contains various cryptographic functions
For creating IGitHubClients
ISymlinkFactory for posix systems
Task Restart()
Restarts the Host
bool AllowAnyOrigin
If any origin is allowed for CORS requests. This overrides AllowedOrigins
Configuration for the automatic update system
For downloading and installing BYOND extractions for a given system
For accessing the disk in a synchronous manner
DatabaseType DatabaseType
The Configuration.DatabaseType to create
Factory for creating IInstances
void Ready(Exception initializationError)
Mark the IApplication as ready to run
IByondInstaller for Posix systems
Factory for ISystemIdentitys
IPostWriteHandler for Windows systems
Configuration options for the web control panel
For generating CredentialsHandlers
File logging configuration options
readonly TaskCompletionSource< object > startupTcs
The TaskCompletionSource<TResult> used for determining when the Application is Ready(Exception) ...
void ConfigureServices(IServiceCollection services)
Configure dependency injected services
TokenValidationParameters ValidationParameters
The TokenValidationParameters for the ITokenFactory
ISystemIdentityFactory for posix systems
Configuration options for the Models.DatabaseContext<TParentContext>
General configuration options
IIOManager that resolves paths to Environment.CurrentDirectory
Application(IConfiguration configuration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment)
Construct an Application
For injecting System.Security.Claims.Claims that Controllers.TgsAuthorizeAttribute can look for ...
DatabaseType
Type of database to user
The command line Configuration setup wizard
Task< bool > CheckRunWizard(CancellationToken cancellationToken)
Run the setup wizard if necessary
Interface for using filesystems
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
readonly IConfiguration configuration
The IConfiguration for the Application
string ConcatPath(params string[] paths)
Combines an array of strings into a path
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
IByondInstaller for windows systems
For caching ISystemIdentitys