/// <summary> /// Creates a pre-configured <see cref="Process"/> pointing to dmm-tools.exe /// </summary> /// <param name="output">A <see cref="StringBuilder"/> used to accept stdout</param> /// <param name="errorOutput">A <see cref="StringBuilder"/> used to accept stderr</param> /// <returns>A <see cref="Task"/> resulting in a pre-configured <see cref="Process"/> pointing to dmm-tools.exe</returns> Process CreateDMMToolsProcess(StringBuilder output, StringBuilder errorOutput) { var P = new Process(); P.StartInfo.RedirectStandardOutput = true; P.StartInfo.RedirectStandardError = true; P.StartInfo.UseShellExecute = false; P.StartInfo.WorkingDirectory = ioManager.ResolvePath("."); P.OutputDataReceived += new DataReceivedEventHandler( delegate(object sender, DataReceivedEventArgs e) { output.Append(Environment.NewLine); output.Append(e.Data); } ); P.ErrorDataReceived += new DataReceivedEventHandler( delegate(object sender, DataReceivedEventArgs e) { errorOutput.Append(Environment.NewLine); errorOutput.Append(e.Data); } ); try { P.StartInfo.FileName = ioManager.ConcatPath(ioManager.GetDirectoryName(Assembly.GetExecutingAssembly().Location), DMMToolsPath); } catch { P.Dispose(); throw; } return(P); }
async Task <string> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken) { var resolvedPath = ioManager.ResolvePath(databaseName); try { var directoryName = ioManager.GetDirectoryName(resolvedPath); bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(false); await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(false); try { await ioManager.WriteAllBytes(resolvedPath, Array.Empty <byte>(), cancellationToken).ConfigureAwait(false); } catch { if (!directoryExisted) { await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(false); } throw; } } catch (IOException) { return(null); } if (!Path.IsPathRooted(databaseName)) { await console.WriteAsync("Note, this relative path (currently) resolves to the following:", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(resolvedPath, true, cancellationToken).ConfigureAwait(false); bool writeResolved = await PromptYesNo( "Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ", cancellationToken) .ConfigureAwait(false); if (writeResolved) { databaseName = resolvedPath; } } await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(false); return(databaseName); }
/// <summary> /// Configure dependency injected services /// </summary> /// <param name="services">The <see cref="IServiceCollection"/> to configure</param> public void ConfigureServices(IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } // needful services.AddSingleton <IApplication>(this); // configure configuration services.UseStandardConfig <UpdatesConfiguration>(configuration); services.UseStandardConfig <DatabaseConfiguration>(configuration); services.UseStandardConfig <GeneralConfiguration>(configuration); services.UseStandardConfig <FileLoggingConfiguration>(configuration); services.UseStandardConfig <ControlPanelConfiguration>(configuration); // enable options which give us config reloading services.AddOptions(); // this is needful for the setup wizard services.AddLogging(); // other stuff needed for for setup wizard and configuration services.AddSingleton <IConsole, IO.Console>(); services.AddSingleton <IDatabaseConnectionFactory, DatabaseConnectionFactory>(); services.AddSingleton <ISetupWizard, SetupWizard>(); services.AddSingleton <IPlatformIdentifier, PlatformIdentifier>(); services.AddSingleton <IAsyncDelayer, AsyncDelayer>(); GeneralConfiguration generalConfiguration; DatabaseConfiguration databaseConfiguration; FileLoggingConfiguration fileLoggingConfiguration; ControlPanelConfiguration controlPanelConfiguration; IPlatformIdentifier platformIdentifier; // temporarily build the service provider in it's current state // do it here so we can run the setup wizard if necessary // also allows us to get some options and other services we need for continued configuration using (var provider = services.BuildServiceProvider()) { // run the wizard if necessary var setupWizard = provider.GetRequiredService <ISetupWizard>(); var applicationLifetime = provider.GetRequiredService <Microsoft.AspNetCore.Hosting.IApplicationLifetime>(); var setupWizardRan = setupWizard.CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult(); // load the configuration options we need var generalOptions = provider.GetRequiredService <IOptions <GeneralConfiguration> >(); generalConfiguration = generalOptions.Value; // unless this is set, in which case, we leave if (setupWizardRan && generalConfiguration.SetupWizardMode == SetupWizardMode.Only) { throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); // we don't inject a logger in the constuctor to log this because it's not yet configured } var dbOptions = provider.GetRequiredService <IOptions <DatabaseConfiguration> >(); databaseConfiguration = dbOptions.Value; var loggingOptions = provider.GetRequiredService <IOptions <FileLoggingConfiguration> >(); fileLoggingConfiguration = loggingOptions.Value; var controlPanelOptions = provider.GetRequiredService <IOptions <ControlPanelConfiguration> >(); controlPanelConfiguration = controlPanelOptions.Value; platformIdentifier = provider.GetRequiredService <IPlatformIdentifier>(); } // setup file logging via serilog if (!fileLoggingConfiguration.Disable) { services.AddLogging(builder => { // common app data is C:/ProgramData on windows, else /usr/shar var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.Directory) ? fileLoggingConfiguration.Directory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); logPath = ioManager.ConcatPath(logPath, "tgs-{Date}.log"); LogEventLevel?ConvertLogLevel(LogLevel logLevel) { switch (logLevel) { case LogLevel.Critical: return(LogEventLevel.Fatal); case LogLevel.Debug: return(LogEventLevel.Debug); case LogLevel.Error: return(LogEventLevel.Error); case LogLevel.Information: return(LogEventLevel.Information); case LogLevel.Trace: return(LogEventLevel.Verbose); case LogLevel.Warning: return(LogEventLevel.Warning); case LogLevel.None: return(null); default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel)); } } var logEventLevel = ConvertLogLevel(fileLoggingConfiguration.LogLevel); var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.MicrosoftLogLevel); var formatter = new MessageTemplateTextFormatter("{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null); var configuration = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.Async(w => w.RollingFile(formatter, logPath, shared: true, flushToDiskInterval: TimeSpan.FromSeconds(2))); if (logEventLevel.HasValue) { configuration.MinimumLevel.Is(logEventLevel.Value); } if (microsoftEventLevel.HasValue) { configuration.MinimumLevel.Override("Microsoft", microsoftEventLevel.Value); } builder.AddSerilog(configuration.CreateLogger(), true); }); } // configure bearer token validation services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions => { // this line isn't actually run until the first request is made // at that point tokenFactory will be populated jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters; jwtBearerOptions.Events = new JwtBearerEvents { // Application is our composition root so this monstrosity of a line is okay OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService <IClaimsInjector>().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted) }; }); // f*****g prevents converting 'sub' to M$ bs // can't be done in the above lambda, that's too late JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // add mvc, configure the json serializer settings services .AddMvc(options => { var dataAnnotationValidator = options.ModelValidatorProviders.Single(validator => validator.GetType().Name == "DataAnnotationsModelValidatorProvider"); options.ModelValidatorProviders.Remove(dataAnnotationValidator); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) .AddJsonOptions(options => { options.AllowInputFormatterExceptionMessages = true; options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; options.SerializerSettings.CheckAdditionalContent = true; options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error; options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; options.SerializerSettings.Converters = new[] { new VersionConverter() }; }); if (hostingEnvironment.IsDevelopment()) { string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml")); var assemblyDocumentationPath = GetDocumentationFilePath(assemblyInformationProvider.Path); var apiDocumentationPath = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location); services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath)); } // enable browser detection services.AddDetectionCore().AddBrowser(); // CORS conditionally enabled later services.AddCors(); void AddTypedContext <TContext>() where TContext : DatabaseContext <TContext> { services.AddDbContext <TContext>(builder => { if (hostingEnvironment.IsDevelopment()) { builder.EnableSensitiveDataLogging(); } }); services.AddScoped <IDatabaseContext>(x => x.GetRequiredService <TContext>()); } // add the correct database context type var dbType = databaseConfiguration.DatabaseType; switch (dbType) { case DatabaseType.MySql: case DatabaseType.MariaDB: AddTypedContext <MySqlDatabaseContext>(); break; case DatabaseType.SqlServer: AddTypedContext <SqlServerDatabaseContext>(); break; default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType)); } // configure other database services services.AddSingleton <IDatabaseContextFactory, DatabaseContextFactory>(); services.AddSingleton <IDatabaseSeeder, DatabaseSeeder>(); // configure security services services.AddScoped <IAuthenticationContextFactory, AuthenticationContextFactory>(); services.AddScoped <IClaimsInjector, ClaimsInjector>(); services.AddSingleton <IIdentityCache, IdentityCache>(); services.AddSingleton <ICryptographySuite, CryptographySuite>(); services.AddSingleton <ITokenFactory, TokenFactory>(); services.AddSingleton <IPasswordHasher <Models.User>, PasswordHasher <Models.User> >(); // configure platform specific services if (platformIdentifier.IsWindows) { if (generalConfiguration.UseBasicWatchdogOnWindows) { services.AddSingleton <IWatchdogFactory, WatchdogFactory>(); } else { services.AddSingleton <IWatchdogFactory, WindowsWatchdogFactory>(); } services.AddSingleton <ISystemIdentityFactory, WindowsSystemIdentityFactory>(); services.AddSingleton <ISymlinkFactory, WindowsSymlinkFactory>(); services.AddSingleton <IByondInstaller, WindowsByondInstaller>(); services.AddSingleton <IPostWriteHandler, WindowsPostWriteHandler>(); services.AddSingleton <WindowsNetworkPromptReaper>(); services.AddSingleton <INetworkPromptReaper>(x => x.GetRequiredService <WindowsNetworkPromptReaper>()); services.AddSingleton <IHostedService>(x => x.GetRequiredService <WindowsNetworkPromptReaper>()); } else { services.AddSingleton <IWatchdogFactory, WatchdogFactory>(); services.AddSingleton <ISystemIdentityFactory, PosixSystemIdentityFactory>(); services.AddSingleton <ISymlinkFactory, PosixSymlinkFactory>(); services.AddSingleton <IByondInstaller, PosixByondInstaller>(); services.AddSingleton <IPostWriteHandler, PosixPostWriteHandler>(); services.AddSingleton <INetworkPromptReaper, PosixNetworkPromptReaper>(); } // configure misc services services.AddSingleton <ISynchronousIOManager, SynchronousIOManager>(); services.AddSingleton <IGitHubClientFactory, GitHubClientFactory>(); services.AddSingleton <IProcessExecutor, ProcessExecutor>(); services.AddSingleton <IByondTopicSender>(new ByondTopicSender { ReceiveTimeout = generalConfiguration.ByondTopicTimeout, SendTimeout = generalConfiguration.ByondTopicTimeout }); // configure component services services.AddSingleton <ICredentialsProvider, CredentialsProvider>(); services.AddSingleton <IProviderFactory, ProviderFactory>(); services.AddSingleton <IChatFactory, ChatFactory>(); services.AddSingleton <IInstanceFactory, InstanceFactory>(); // configure root services services.AddSingleton <InstanceManager>(); services.AddSingleton <IInstanceManager>(x => x.GetRequiredService <InstanceManager>()); services.AddSingleton <IHostedService>(x => x.GetRequiredService <InstanceManager>()); services.AddSingleton <IJobManager, JobManager>(); }