1 using Microsoft.AspNetCore.Hosting;
2 using Microsoft.Extensions.Logging;
3 using Microsoft.Extensions.Options;
4 using MySql.Data.MySqlClient;
7 using System.Collections.Generic;
9 using System.Data.SqlClient;
10 using System.Globalization;
14 using System.Threading.Tasks;
61 readonly ILogger<SetupWizard>
logger;
82 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
83 this.console = console ??
throw new ArgumentNullException(nameof(console));
84 this.hostingEnvironment = hostingEnvironment ??
throw new ArgumentNullException(nameof(hostingEnvironment));
85 this.application = application ??
throw new ArgumentNullException(nameof(application));
86 this.dbConnectionFactory = dbConnectionFactory ??
throw new ArgumentNullException(nameof(dbConnectionFactory));
87 this.platformIdentifier = platformIdentifier ??
throw new ArgumentNullException(nameof(platformIdentifier));
88 this.asyncDelayer = asyncDelayer ??
throw new ArgumentNullException(nameof(asyncDelayer));
89 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
90 generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
99 async Task<bool>
PromptYesNo(
string question, CancellationToken cancellationToken)
103 await console.WriteAsync(question,
false, cancellationToken).ConfigureAwait(
false);
104 var responseString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
105 var upperResponse = responseString.ToUpperInvariant();
106 if (upperResponse ==
"Y" || upperResponse ==
"YES")
108 else if (upperResponse ==
"N" || upperResponse ==
"NO")
110 await console.WriteAsync(
"Invalid response!",
true, cancellationToken).ConfigureAwait(
false);
122 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
123 await console.WriteAsync(
"What port would you like to connect to TGS on?",
true, cancellationToken).ConfigureAwait(
false);
124 await console.WriteAsync(
"Note: If this is a docker container with the default port already mapped, use the default.",
true, cancellationToken).ConfigureAwait(
false);
128 await console.WriteAsync(
"API Port (leave blank for default): ",
false, cancellationToken).ConfigureAwait(
false);
129 var portString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
130 if (String.IsNullOrWhiteSpace(portString))
132 if (UInt16.TryParse(portString, out var port) && port != 0)
134 await console.WriteAsync(
"Invalid port! Please enter a value between 1 and 65535",
true, cancellationToken).ConfigureAwait(
false);
148 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
149 await console.WriteAsync(
"What SQL database type will you be using?",
true, cancellationToken).ConfigureAwait(
false);
154 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Please enter one of {0}, {1}, or {2}: ",
DatabaseType.MariaDB,
DatabaseType.SqlServer,
DatabaseType.MySql),
false, cancellationToken).ConfigureAwait(
false);
155 var databaseTypeString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
156 if (Enum.TryParse<
DatabaseType>(databaseTypeString, out var databaseType))
158 databaseConfiguration.DatabaseType = databaseType;
161 await console.WriteAsync(
"Invalid database type!",
true, cancellationToken).ConfigureAwait(
false);
165 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
166 await console.WriteAsync(
"Enter the server's address and port (blank for local): ",
false, cancellationToken).ConfigureAwait(
false);
167 var serverAddress = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
168 if (String.IsNullOrWhiteSpace(serverAddress))
169 serverAddress = null;
171 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
172 await console.WriteAsync(
"Enter the database name (Can be from previous installation. Otherwise, should not exist): ",
false, cancellationToken).ConfigureAwait(
false);
177 databaseName = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
178 if (!String.IsNullOrWhiteSpace(databaseName))
180 await console.WriteAsync(
"Invalid database name!",
true, cancellationToken).ConfigureAwait(
false);
184 var dbExists = await PromptYesNo(
"Does this database already exist? (y/n): ", cancellationToken).ConfigureAwait(
false);
187 if (databaseConfiguration.DatabaseType ==
DatabaseType.SqlServer && platformIdentifier.IsWindows)
188 useWinAuth = await PromptYesNo(
"Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(
false);
192 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
194 string username = null;
195 string password = null;
198 await console.WriteAsync(
"Enter username: ",
false, cancellationToken).ConfigureAwait(
false);
199 username = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
200 await console.WriteAsync(
"Enter password: ",
false, cancellationToken).ConfigureAwait(
false);
201 password = await console.ReadLineAsync(
true, cancellationToken).ConfigureAwait(
false);
205 await console.WriteAsync(
"IMPORTANT: If using the service runner, ensure this computer's LocalSystem account has CREATE DATABASE permissions on the target server!",
true, cancellationToken).ConfigureAwait(
false);
206 await console.WriteAsync(
"The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".",
true, cancellationToken).ConfigureAwait(
false);
207 await console.WriteAsync(
"We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case",
true, cancellationToken).ConfigureAwait(
false);
209 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
211 DbConnection testConnection;
212 void CreateTestConnection(
string connectionString)
214 testConnection = dbConnectionFactory.CreateConnection(connectionString, databaseConfiguration.DatabaseType);
217 if (databaseConfiguration.DatabaseType ==
DatabaseType.SqlServer)
219 var csb =
new SqlConnectionStringBuilder
221 ApplicationName = application.VersionPrefix,
222 DataSource = serverAddress ??
"(local)" 225 csb.IntegratedSecurity =
true;
228 csb.UserID = username;
229 csb.Password = password;
232 CreateTestConnection(csb.ConnectionString);
233 csb.InitialCatalog = databaseName;
234 databaseConfiguration.ConnectionString = csb.ConnectionString;
238 var csb =
new MySqlConnectionStringBuilder
240 Server = serverAddress ??
"127.0.0.1",
245 CreateTestConnection(csb.ConnectionString);
246 csb.Database = databaseName;
247 databaseConfiguration.ConnectionString = csb.ConnectionString;
252 using (testConnection)
254 await console.WriteAsync(
"Testing connection...",
true, cancellationToken).ConfigureAwait(
false);
255 await testConnection.OpenAsync(cancellationToken).ConfigureAwait(
false);
256 await console.WriteAsync(
"Connection successful!",
true, cancellationToken).ConfigureAwait(
false);
258 if (databaseConfiguration.DatabaseType !=
DatabaseType.SqlServer)
260 await console.WriteAsync(
"Checking MySQL/MariaDB version...",
true, cancellationToken).ConfigureAwait(
false);
261 using (var command = testConnection.CreateCommand())
263 command.CommandText =
"SELECT VERSION()";
264 var fullVersion = (string)(await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(
false));
265 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Found {0}", fullVersion),
true, cancellationToken).ConfigureAwait(
false);
266 var splits = fullVersion.Split(
'-');
267 databaseConfiguration.MySqlServerVersion = splits[0];
273 await console.WriteAsync(
"Testing create DB permission...",
true, cancellationToken).ConfigureAwait(
false);
274 using (var command = testConnection.CreateCommand())
276 command.CommandText = String.Format(CultureInfo.InvariantCulture,
"CREATE DATABASE {0}", databaseName);
277 await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(
false);
279 await console.WriteAsync(
"Success!",
true, cancellationToken).ConfigureAwait(
false);
280 await console.WriteAsync(
"Dropping test database...",
true, cancellationToken).ConfigureAwait(
false);
281 using (var command = testConnection.CreateCommand())
283 command.CommandText = String.Format(CultureInfo.InvariantCulture,
"DROP DATABASE {0}", databaseName);
286 await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(
false);
288 catch (OperationCanceledException)
294 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
295 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
296 await console.WriteAsync(
"This should be okay, but you may want to manually drop the database before continuing!",
true, cancellationToken).ConfigureAwait(
false);
297 await console.WriteAsync(
"Press any key to continue...",
true, cancellationToken).ConfigureAwait(
false);
298 await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(
false);
304 return databaseConfiguration;
306 catch (OperationCanceledException)
312 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
313 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
314 await console.WriteAsync(
"Retrying database configuration...",
true, cancellationToken).ConfigureAwait(
false);
333 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
334 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength),
false, cancellationToken).ConfigureAwait(
false);
335 var passwordLengthString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
336 if (String.IsNullOrWhiteSpace(passwordLengthString))
338 if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
343 await console.WriteAsync(
"Please enter a positive integer!",
true, cancellationToken).ConfigureAwait(
false);
349 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
350 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Timeout for sending and receiving BYOND topics (ms, 0 for infinite, leave blank for default of {0}): ", newGeneralConfiguration.ByondTopicTimeout),
false, cancellationToken).ConfigureAwait(
false);
351 var topicTimeoutString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
352 if (String.IsNullOrWhiteSpace(topicTimeoutString))
354 if (Int32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
356 newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
359 await console.WriteAsync(
"Please enter a positive integer!",
true, cancellationToken).ConfigureAwait(
false);
363 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
364 await console.WriteAsync(
"Enter a GitHub personal access token to bypass some rate limits (this is optional and does not require any scopes)",
true, cancellationToken).ConfigureAwait(
false);
365 await console.WriteAsync(
"GitHub personal access token: ",
false, cancellationToken).ConfigureAwait(
false);
366 newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(
true, cancellationToken).ConfigureAwait(
false);
367 if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
368 newGeneralConfiguration.GitHubAccessToken = null;
369 return newGeneralConfiguration;
380 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
381 fileLoggingConfiguration.Disable = !await PromptYesNo(
"Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(
false);
383 if (!fileLoggingConfiguration.Disable)
387 await console.WriteAsync(
"Log file directory path (leave blank for default): ",
false, cancellationToken).ConfigureAwait(
false);
388 fileLoggingConfiguration.Directory = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
389 if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
391 fileLoggingConfiguration.Directory = null;
395 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
396 await console.WriteAsync(
"Testing directory access...",
true, cancellationToken).ConfigureAwait(
false);
399 await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(
false);
400 var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture,
"WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
401 await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(
false);
404 await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(
false);
406 catch (OperationCanceledException)
412 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Error deleting test log file: {0}", testFile),
true, cancellationToken).ConfigureAwait(
false);
413 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
414 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
418 catch (OperationCanceledException)
424 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
425 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
426 await console.WriteAsync(
"Please verify the path is valid and you have access to it!",
true, cancellationToken).ConfigureAwait(
false);
430 async Task<LogLevel?> PromptLogLevel(
string question)
434 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
435 await console.WriteAsync(question,
true, cancellationToken).ConfigureAwait(
false);
436 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)),
false, cancellationToken).ConfigureAwait(
false);
437 var responseString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
438 if (String.IsNullOrWhiteSpace(responseString))
440 if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
442 await console.WriteAsync(
"Invalid log level!",
true, cancellationToken).ConfigureAwait(
false);
446 fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture,
"Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(
false) ?? fileLoggingConfiguration.LogLevel;
447 fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture,
"Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(
false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
449 return fileLoggingConfiguration;
461 Enable = await PromptYesNo(
"Enable the web control panel? (y/n): ", cancellationToken).ConfigureAwait(
false),
462 AllowAnyOrigin = await PromptYesNo(
"Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(
false)
465 if (!config.AllowAnyOrigin)
467 await console.WriteAsync(
"Enter a comma seperated list of CORS allowed origins (optional): ",
false, cancellationToken).ConfigureAwait(
false);
468 var commaSeperatedOrigins = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
469 if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
471 var splits = commaSeperatedOrigins.Split(
',');
472 config.
AllowedOrigins =
new List<string>(splits.Select(x => x.Trim()));
492 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Configuration complete! Saving to {0}", userConfigFileName),
true, cancellationToken).ConfigureAwait(
false);
494 var map =
new Dictionary<string, object>()
502 if (hostingPort.HasValue)
503 map.Add(
"Kestrel",
new 509 Url = String.Format(CultureInfo.InvariantCulture,
"http://0.0.0.0:{0}", hostingPort)
514 var json = JsonConvert.SerializeObject(map, Formatting.Indented);
515 var configBytes = Encoding.UTF8.GetBytes(json);
519 await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(
false);
521 catch (OperationCanceledException)
527 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
528 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
529 await console.WriteAsync(
"For your convienence, here's the json we tried to write out:",
true, cancellationToken).ConfigureAwait(
false);
530 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
531 await console.WriteAsync(json,
true, cancellationToken).ConfigureAwait(
false);
532 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
533 await console.WriteAsync(
"Press any key to exit...",
true, cancellationToken).ConfigureAwait(
false);
534 await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(
false);
535 throw new OperationCanceledException();
538 await console.WriteAsync(
"Waiting for configuration changes to reload...",
true, cancellationToken).ConfigureAwait(
false);
541 await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(
false);
550 async Task
RunWizard(
string userConfigFileName, CancellationToken cancellationToken)
553 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
554 await console.WriteAsync(
"Welcome to tgstation-server 4!",
true, cancellationToken).ConfigureAwait(
false);
555 await console.WriteAsync(
"This wizard will help you configure your server.",
true, cancellationToken).ConfigureAwait(
false);
557 var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(
false);
559 var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(
false);
561 var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(
false);
563 var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(
false);
565 var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(
false);
567 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
569 await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(
false);
575 var setupWizardMode = generalConfiguration.SetupWizardMode;
576 logger.LogTrace(
"Checking if setup wizard should run. SetupWizardMode: {0}", setupWizardMode);
580 logger.LogTrace(
"Skipping due to configuration...");
585 if (!console.Available)
588 throw new InvalidOperationException(
"Asked to run setup wizard with no console avaliable!");
589 logger.LogTrace(
"Skipping due to console not being available...");
593 var userConfigFileName = String.Format(CultureInfo.InvariantCulture,
"appsettings.{0}.json", hostingEnvironment.EnvironmentName);
594 var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(
false);
596 bool shouldRunBasedOnAutodetect;
599 var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(
false);
600 var contents = Encoding.UTF8.GetString(bytes);
601 var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() ==
"{}";
602 logger.LogTrace(
"Configuration json detected. Empty: {0}", existingConfigIsEmpty);
603 shouldRunBasedOnAutodetect = existingConfigIsEmpty;
607 shouldRunBasedOnAutodetect =
true;
608 logger.LogTrace(
"No configuration json detected");
612 if (!shouldRunBasedOnAutodetect)
616 logger.LogTrace(
"Asking user to bypass due to force run request...");
617 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"The configuration settings are requesting the setup wizard be run, but you already appear to have a configuration file ({0})!", userConfigFileName),
true, cancellationToken).ConfigureAwait(
false);
619 forceRun = await PromptYesNo(
"Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(
false);
626 await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(
false);
628 await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(
false);
readonly IConsole console
The IConsole for the SetupWizard
List< string > AllowedOrigins
Origins allowed for CORS requests
async Task< ControlPanelConfiguration > ConfigureControlPanel(CancellationToken cancellationToken)
Prompts the user to create a ControlPanelConfiguration
Configures the ASP.NET Core web application
Use server authentication
SetupWizardMode
Determines if the Core.ISetupWizard will run
Abstraction for System.Console
For creating DbConnection
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the GeneralConfiguration res...
readonly IApplication application
The IApplication for the SetupWizard
For waiting asynchronously
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the DatabaseConfiguration re...
readonly IIOManager ioManager
The IIOManager for the SetupWizard
readonly IDBConnectionFactory dbConnectionFactory
The IDBConnectionFactory for the SetupWizard
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the SetupWizard
SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, ILogger< SetupWizard > logger, IOptions< GeneralConfiguration > generalConfigurationOptions)
Construct a SetupWizard
async Task< bool > PromptYesNo(string question, CancellationToken cancellationToken)
A prompt for a yes or no value
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the ControlPanelConfiguratio...
Configuration options for the web control panel
async Task< DatabaseConfiguration > ConfigureDatabase(CancellationToken cancellationToken)
Prompts the user to create a DatabaseConfiguration
File logging configuration options
async Task< GeneralConfiguration > ConfigureGeneral(CancellationToken cancellationToken)
Prompts the user to create a GeneralConfiguration
async Task SaveConfiguration(string userConfigFileName, ushort?hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
Saves a given Configuration set to userConfigFileName
readonly IHostingEnvironment hostingEnvironment
The IHostingEnvironment for the SetupWizard
async Task< ushort?> PromptForHostingPort(CancellationToken cancellationToken)
Prompts the user to enter the port to host TGS on
Configuration options for the Models.DatabaseContext<TParentContext>
async Task< FileLoggingConfiguration > ConfigureLogging(CancellationToken cancellationToken)
Prompts the user to create a FileLoggingConfiguration
General configuration options
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SetupWizard
async Task< bool > CheckRunWizard(CancellationToken cancellationToken)
Run the setup wizard if necessary
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the SetupWizard
DatabaseType
Type of database to user
The command line Configuration setup wizard
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the FileLoggingConfiguration...
Interface for using filesystems
readonly ILogger< SetupWizard > logger
The ILogger for the SetupWizard
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
Runs the SetupWizard
uint MinimumPasswordLength
Minimum length of database user passwords