1 using Microsoft.Data.Sqlite;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
5 using MySql.Data.MySqlClient;
9 using System.Collections.Generic;
11 using System.Data.SqlClient;
12 using System.Globalization;
16 using System.Text.RegularExpressions;
18 using System.Threading.Tasks;
90 IHostEnvironment hostingEnvironment,
95 IHostApplicationLifetime applicationLifetime,
96 IOptions<GeneralConfiguration> generalConfigurationOptions)
98 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
99 this.console = console ??
throw new ArgumentNullException(nameof(console));
100 this.hostingEnvironment = hostingEnvironment ??
throw new ArgumentNullException(nameof(hostingEnvironment));
101 this.assemblyInformationProvider = assemblyInformationProvider ??
throw new ArgumentNullException(nameof(assemblyInformationProvider));
102 this.dbConnectionFactory = dbConnectionFactory ??
throw new ArgumentNullException(nameof(dbConnectionFactory));
103 this.platformIdentifier = platformIdentifier ??
throw new ArgumentNullException(nameof(platformIdentifier));
104 this.asyncDelayer = asyncDelayer ??
throw new ArgumentNullException(nameof(asyncDelayer));
105 this.applicationLifetime = applicationLifetime ??
throw new ArgumentNullException(nameof(applicationLifetime));
106 generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
115 async Task<bool>
PromptYesNo(
string question, CancellationToken cancellationToken)
119 await console.WriteAsync(question,
false, cancellationToken).ConfigureAwait(
false);
120 var responseString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
121 var upperResponse = responseString.ToUpperInvariant();
122 if (upperResponse ==
"Y" || upperResponse ==
"YES")
124 else if (upperResponse ==
"N" || upperResponse ==
"NO")
126 await console.WriteAsync(
"Invalid response!",
true, cancellationToken).ConfigureAwait(
false);
138 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
139 await console.WriteAsync(
"What port would you like to connect to TGS on?",
true, cancellationToken).ConfigureAwait(
false);
140 await console.WriteAsync(
"Note: If this is a docker container with the default port already mapped, use the default.",
true, cancellationToken).ConfigureAwait(
false);
144 await console.WriteAsync(
145 $
"API Port (leave blank for default of {GeneralConfiguration.DefaultApiPort}): ",
148 .ConfigureAwait(
false);
149 var portString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
150 if (String.IsNullOrWhiteSpace(portString))
152 if (UInt16.TryParse(portString, out var port) && port != 0)
154 await console.WriteAsync(
"Invalid port! Please enter a value between 1 and 65535",
true, cancellationToken).ConfigureAwait(
false);
169 DbConnection testConnection,
173 CancellationToken cancellationToken)
176 using (testConnection)
178 await console.WriteAsync(
"Testing connection...",
true, cancellationToken).ConfigureAwait(
false);
179 await testConnection.OpenAsync(cancellationToken).ConfigureAwait(
false);
180 await console.WriteAsync(
"Connection successful!",
true, cancellationToken).ConfigureAwait(
false);
186 await console.WriteAsync($
"Checking {databaseConfiguration.DatabaseType} version...",
true, cancellationToken).ConfigureAwait(
false);
187 using var command = testConnection.CreateCommand();
188 command.CommandText =
"SELECT VERSION()";
189 var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(
false);
190 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Found {0}", fullVersion),
true, cancellationToken).ConfigureAwait(
false);
194 var splits = fullVersion.Split(
' ');
195 databaseConfiguration.
ServerVersion = splits[1].TrimEnd(
',');
199 var splits = fullVersion.Split(
'-');
204 if (!isSqliteDB && !dbExists)
206 await console.WriteAsync(
"Testing create DB permission...",
true, cancellationToken).ConfigureAwait(
false);
207 using (var command = testConnection.CreateCommand())
210 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities 211 command.CommandText = $
"CREATE DATABASE {databaseName}";
212 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities 213 await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(
false);
216 await console.WriteAsync(
"Success!",
true, cancellationToken).ConfigureAwait(
false);
217 await console.WriteAsync(
"Dropping test database...",
true, cancellationToken).ConfigureAwait(
false);
218 using (var command = testConnection.CreateCommand())
220 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities 221 command.CommandText = $
"DROP DATABASE {databaseName}";
222 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities 225 await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(
false);
227 catch (OperationCanceledException)
233 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
234 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
235 await console.WriteAsync(
"This should be okay, but you may want to manually drop the database before continuing!",
true, cancellationToken).ConfigureAwait(
false);
236 await console.WriteAsync(
"Press any key to continue...",
true, cancellationToken).ConfigureAwait(
false);
237 await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(
false);
243 if (isSqliteDB && !dbExists)
245 console.WriteAsync(
"Deleting test database file...",
true, cancellationToken),
246 ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(
false);
251 var resolvedPath = ioManager.ResolvePath(databaseName);
254 var directoryName = ioManager.GetDirectoryName(resolvedPath);
255 bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(
false);
256 await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(
false);
259 await ioManager.WriteAllBytes(resolvedPath, Array.Empty<byte>(), cancellationToken).ConfigureAwait(
false);
263 if (!directoryExisted)
264 await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(
false);
273 if (!Path.IsPathRooted(databaseName))
275 await console.WriteAsync(
"Note, this relative path (currently) resolves to the following:",
true, cancellationToken).ConfigureAwait(
false);
276 await console.WriteAsync(resolvedPath,
true, cancellationToken).ConfigureAwait(
false);
277 bool writeResolved = await PromptYesNo(
278 "Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
280 .ConfigureAwait(
false);
283 databaseName = resolvedPath;
286 await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(
false);
300 await console.WriteAsync(String.Empty,
true, cancellationToken).ConfigureAwait(
false);
301 await console.WriteAsync(
302 "NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
305 .ConfigureAwait(
false);
306 await console.WriteAsync(
307 "Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
310 .ConfigureAwait(
false);
311 await console.WriteAsync(
312 "This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
315 .ConfigureAwait(
false);
316 await console.WriteAsync(
317 "Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
320 .ConfigureAwait(
false);
321 await console.WriteAsync(String.Empty,
true, cancellationToken).ConfigureAwait(
false);
323 await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(
false);
326 await console.WriteAsync(
"What SQL database type will you be using?",
true, cancellationToken).ConfigureAwait(
false);
329 await console.WriteAsync(
331 CultureInfo.InvariantCulture,
332 "Please enter one of {0}, {1}, {2}, {3} or {4}: ",
340 .ConfigureAwait(
false);
341 var databaseTypeString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
342 if (Enum.TryParse<
DatabaseType>(databaseTypeString, out var databaseType))
345 await console.WriteAsync(
"Invalid database type!",
true, cancellationToken).ConfigureAwait(
false);
355 #pragma warning disable CA1502 // TODO: Decomplexify 358 bool firstTime =
true;
361 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
365 DatabaseType = await PromptDatabaseType(firstTime, cancellationToken).ConfigureAwait(
false)
369 string serverAddress = null;
370 ushort? serverPort = null;
376 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
377 await console.WriteAsync(
"Enter the server's address and port [<server>:<port> or <server>] (blank for local): ",
false, cancellationToken).ConfigureAwait(
false);
378 serverAddress = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
379 if (String.IsNullOrWhiteSpace(serverAddress))
380 serverAddress = null;
381 else if (databaseConfiguration.DatabaseType ==
DatabaseType.SqlServer)
383 var match = Regex.Match(serverAddress,
@"^(?<server>.+):(?<port>.+)$");
386 serverAddress = match.Groups[
"server"].Value;
387 var portString = match.Groups[
"port"].Value;
388 if (UInt16.TryParse(portString, out var port))
392 await console.WriteAsync($
"Failed to parse port \"{portString}\", please try again.",
true, cancellationToken).ConfigureAwait(
false);
402 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
403 await console.WriteAsync($
"Enter the database {(isSqliteDB ? "file path
" : "name
")} (Can be from previous installation. Otherwise, should not exist): ",
false, cancellationToken).ConfigureAwait(
false);
406 bool dbExists =
false;
409 databaseName = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
410 if (!String.IsNullOrWhiteSpace(databaseName))
414 dbExists = await ioManager.FileExists(databaseName, cancellationToken).ConfigureAwait(
false);
416 databaseName = await ValidateNonExistantSqliteDBName(databaseName, cancellationToken).ConfigureAwait(
false);
419 dbExists = await PromptYesNo(
"Does this database already exist? If not, we will attempt to CREATE it. (y/n): ", cancellationToken).ConfigureAwait(
false);
422 if (String.IsNullOrWhiteSpace(databaseName))
423 await console.WriteAsync(
"Invalid database name!",
true, cancellationToken).ConfigureAwait(
false);
430 if (databaseConfiguration.DatabaseType ==
DatabaseType.SqlServer && platformIdentifier.IsWindows)
431 useWinAuth = await PromptYesNo(
"Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(
false);
435 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
437 string username = null;
438 string password = null;
442 await console.WriteAsync(
"Enter username: ",
false, cancellationToken).ConfigureAwait(
false);
443 username = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
444 await console.WriteAsync(
"Enter password: ",
false, cancellationToken).ConfigureAwait(
false);
445 password = await console.ReadLineAsync(
true, cancellationToken).ConfigureAwait(
false);
449 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);
450 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);
451 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);
454 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
456 DbConnection testConnection;
457 void CreateTestConnection(
string connectionString) =>
458 testConnection = dbConnectionFactory.CreateConnection(
460 databaseConfiguration.DatabaseType);
462 switch (databaseConfiguration.DatabaseType)
466 var csb =
new SqlConnectionStringBuilder
468 ApplicationName = assemblyInformationProvider.VersionPrefix,
469 DataSource = serverAddress ??
"(local)" 473 csb.IntegratedSecurity =
true;
476 csb.UserID = username;
477 csb.Password = password;
480 CreateTestConnection(csb.ConnectionString);
481 csb.InitialCatalog = databaseName;
482 databaseConfiguration.ConnectionString = csb.ConnectionString;
490 var csb =
new MySqlConnectionStringBuilder
492 Server = serverAddress ??
"127.0.0.1",
497 if (serverPort.HasValue)
498 csb.Port = serverPort.Value;
500 CreateTestConnection(csb.ConnectionString);
501 csb.Database = databaseName;
502 databaseConfiguration.ConnectionString = csb.ConnectionString;
508 var csb =
new SqliteConnectionStringBuilder
510 DataSource = databaseName,
511 Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate
514 CreateTestConnection(csb.ConnectionString);
515 databaseConfiguration.ConnectionString = csb.ConnectionString;
521 var csb =
new NpgsqlConnectionStringBuilder
523 ApplicationName = assemblyInformationProvider.VersionPrefix,
524 Host = serverAddress ??
"127.0.0.1",
529 if (serverPort.HasValue)
530 csb.Port = serverPort.Value;
532 CreateTestConnection(csb.ConnectionString);
533 csb.Database = databaseName;
534 databaseConfiguration.ConnectionString = csb.ConnectionString;
539 throw new InvalidOperationException(
"Invalid DatabaseType!");
544 await TestDatabaseConnection(testConnection, databaseConfiguration, databaseName, dbExists, cancellationToken).ConfigureAwait(
false);
546 return databaseConfiguration;
548 catch (OperationCanceledException)
554 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
555 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
556 await console.WriteAsync(
"Retrying database configuration...",
true, cancellationToken).ConfigureAwait(
false);
561 #pragma warning restore CA1502 577 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
578 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength),
false, cancellationToken).ConfigureAwait(
false);
579 var passwordLengthString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
580 if (String.IsNullOrWhiteSpace(passwordLengthString))
582 if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
588 await console.WriteAsync(
"Please enter a positive integer!",
true, cancellationToken).ConfigureAwait(
false);
594 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
595 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Default timeout for sending and receiving BYOND topics (ms, 0 for infinite, leave blank for default of {0}): ", newGeneralConfiguration.ByondTopicTimeout),
false, cancellationToken).ConfigureAwait(
false);
596 var topicTimeoutString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
597 if (String.IsNullOrWhiteSpace(topicTimeoutString))
599 if (UInt32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
601 newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
605 await console.WriteAsync(
"Please enter a positive integer!",
true, cancellationToken).ConfigureAwait(
false);
609 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
610 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);
611 await console.WriteAsync(
"GitHub personal access token: ",
false, cancellationToken).ConfigureAwait(
false);
612 newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(
true, cancellationToken).ConfigureAwait(
false);
613 if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
614 newGeneralConfiguration.GitHubAccessToken = null;
616 return newGeneralConfiguration;
627 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
628 fileLoggingConfiguration.Disable = !await PromptYesNo(
"Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(
false);
630 if (!fileLoggingConfiguration.Disable)
634 await console.WriteAsync(
"Log file directory path (leave blank for default): ",
false, cancellationToken).ConfigureAwait(
false);
635 fileLoggingConfiguration.Directory = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
636 if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
638 fileLoggingConfiguration.Directory = null;
643 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
644 await console.WriteAsync(
"Testing directory access...",
true, cancellationToken).ConfigureAwait(
false);
647 await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(
false);
648 var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture,
"WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
649 await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(
false);
652 await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(
false);
654 catch (OperationCanceledException)
660 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Error deleting test log file: {0}", testFile),
true, cancellationToken).ConfigureAwait(
false);
661 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
662 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
667 catch (OperationCanceledException)
673 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
674 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
675 await console.WriteAsync(
"Please verify the path is valid and you have access to it!",
true, cancellationToken).ConfigureAwait(
false);
680 async Task<LogLevel?> PromptLogLevel(
string question)
684 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
685 await console.WriteAsync(question,
true, cancellationToken).ConfigureAwait(
false);
686 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);
687 var responseString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
688 if (String.IsNullOrWhiteSpace(responseString))
690 if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
692 await console.WriteAsync(
"Invalid log level!",
true, cancellationToken).ConfigureAwait(
false);
697 fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture,
"Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(
false) ?? fileLoggingConfiguration.LogLevel;
698 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;
701 return fileLoggingConfiguration;
713 Enable = await PromptYesNo(
"Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(
false),
714 AllowAnyOrigin = await PromptYesNo(
"Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(
false)
717 if (!config.AllowAnyOrigin)
719 await console.WriteAsync(
"Enter a comma seperated list of CORS allowed origins (optional): ",
false, cancellationToken).ConfigureAwait(
false);
720 var commaSeperatedOrigins = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
721 if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
723 var splits = commaSeperatedOrigins.Split(
',');
724 config.
AllowedOrigins =
new List<string>(splits.Select(x => x.Trim()));
744 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Configuration complete! Saving to {0}", userConfigFileName),
true, cancellationToken).ConfigureAwait(
false);
748 var map =
new Dictionary<string, object>()
756 var json = JsonConvert.SerializeObject(map, Formatting.Indented);
757 var configBytes = Encoding.UTF8.GetBytes(json);
761 await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(
false);
763 catch (OperationCanceledException)
769 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
770 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
771 await console.WriteAsync(
"For your convienence, here's the json we tried to write out:",
true, cancellationToken).ConfigureAwait(
false);
772 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
773 await console.WriteAsync(json,
true, cancellationToken).ConfigureAwait(
false);
774 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
775 await console.WriteAsync(
"Press any key to exit...",
true, cancellationToken).ConfigureAwait(
false);
776 await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(
false);
777 throw new OperationCanceledException();
787 async Task
RunWizard(
string userConfigFileName, CancellationToken cancellationToken)
790 await console.WriteAsync(
"Welcome to tgstation-server 4!",
true, cancellationToken).ConfigureAwait(
false);
791 await console.WriteAsync(
"This wizard will help you configure your server.",
true, cancellationToken).ConfigureAwait(
false);
793 var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(
false);
795 var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(
false);
797 var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(
false);
799 var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(
false);
801 var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(
false);
803 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
805 await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(
false);
815 var setupWizardMode = generalConfiguration.SetupWizardMode;
820 if (!console.Available)
823 throw new InvalidOperationException(
"Asked to run setup wizard with no console avaliable!");
827 var userConfigFileName = String.Format(CultureInfo.InvariantCulture,
"appsettings.{0}.json", hostingEnvironment.EnvironmentName);
829 async Task HandleSetupCancel()
831 await console.WriteAsync(String.Empty,
true,
default).ConfigureAwait(
false);
832 await console.WriteAsync(
"Aborting setup!",
true,
default).ConfigureAwait(
false);
836 Task finalTask = Task.CompletedTask;
837 using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
838 using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
841 var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(
false);
843 bool shouldRunBasedOnAutodetect;
846 var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(
false);
847 var contents = Encoding.UTF8.GetString(bytes);
848 var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() ==
"{}";
849 shouldRunBasedOnAutodetect = existingConfigIsEmpty;
852 shouldRunBasedOnAutodetect =
true;
854 if (!shouldRunBasedOnAutodetect)
858 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);
860 forceRun = await PromptYesNo(
"Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(
false);
868 await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(
false);
870 await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(
false);
874 await finalTask.ConfigureAwait(
false);
879 public async Task
StartAsync(CancellationToken cancellationToken)
881 await CheckRunWizard(cancellationToken).ConfigureAwait(
false);
882 applicationLifetime.StopApplication();
886 public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
List< string > AllowedOrigins
Origins allowed for CORS requests
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SetupWizard
static readonly Version CurrentConfigVersion
The current ConfigVersion.
readonly IIOManager ioManager
The IIOManager for the SetupWizard
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the SetupWizard
Use server authentication
async Task TestDatabaseConnection(DbConnection testConnection, DatabaseConfiguration databaseConfiguration, string databaseName, bool dbExists, CancellationToken cancellationToken)
Ensure a given testConnection works.
SetupWizardMode
Determines if the Setup.SetupWizard will run
Abstraction for global::System.Console
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the GeneralConfiguration res...
async Task< string > ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
async Task StartAsync(CancellationToken cancellationToken)
async Task< GeneralConfiguration > ConfigureGeneral(CancellationToken cancellationToken)
Prompts the user to create a GeneralConfiguration
For waiting asynchronously
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the DatabaseConfiguration re...
DatabaseType DatabaseType
The Configuration.DatabaseType to create
async Task< DatabaseType > PromptDatabaseType(bool firstTime, CancellationToken cancellationToken)
Prompt the user for the DatabaseType.
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
Runs the SetupWizard
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the ControlPanelConfiguratio...
ushort ApiPort
The port the TGS API listens on.
async Task< ushort?> PromptForHostingPort(CancellationToken cancellationToken)
Prompts the user to enter the port to host TGS on
Configuration options for the web control panel
async Task< DatabaseConfiguration > ConfigureDatabase(CancellationToken cancellationToken)
Prompts the user to create a DatabaseConfiguration
string ServerVersion
The string form of the global::System.Version of the target server
File logging configuration options
readonly IHostApplicationLifetime applicationLifetime
The IHostApplicationLifetime for the SetupWizard.
const ushort DefaultApiPort
The default value of ApiPort.
async Task< ControlPanelConfiguration > ConfigureControlPanel(CancellationToken cancellationToken)
Prompts the user to create a ControlPanelConfiguration
SetupWizard(IIOManager ioManager, IConsole console, IHostEnvironment hostingEnvironment, IAssemblyInformationProvider assemblyInformationProvider, IDatabaseConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, IHostApplicationLifetime applicationLifetime, IOptions< GeneralConfiguration > generalConfigurationOptions)
Construct a SetupWizard
For creating raw DbConnections.
async Task< bool > PromptYesNo(string question, CancellationToken cancellationToken)
A prompt for a yes or no value
readonly IConsole console
The IConsole for the SetupWizard
async Task CheckRunWizard(CancellationToken cancellationToken)
Check if it should and run the SetupWizard if necessary.
Configuration options for the Database.DatabaseContext
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the SetupWizard
General configuration options
async Task< FileLoggingConfiguration > ConfigureLogging(CancellationToken cancellationToken)
Prompts the user to create a FileLoggingConfiguration
DatabaseType
Type of database to user
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the SetupWizard
async Task SaveConfiguration(string userConfigFileName, ushort?hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
Saves a given Configuration set to userConfigFileName
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the FileLoggingConfiguration...
Interface for using filesystems
readonly IHostEnvironment hostingEnvironment
The IHostEnvironment for the SetupWizard
Version ConfigVersion
The Version the file says it is.
readonly IDatabaseConnectionFactory dbConnectionFactory
The IDatabaseConnectionFactory for the SetupWizard