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);
185 await console.WriteAsync(
"Checking MySQL/MariaDB version...",
true, cancellationToken).ConfigureAwait(
false);
186 using var command = testConnection.CreateCommand();
187 command.CommandText =
"SELECT VERSION()";
188 var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(
false);
189 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Found {0}", fullVersion),
true, cancellationToken).ConfigureAwait(
false);
190 var splits = fullVersion.Split(
'-');
194 if (!isSqliteDB && !dbExists)
196 await console.WriteAsync(
"Testing create DB permission...",
true, cancellationToken).ConfigureAwait(
false);
197 using (var command = testConnection.CreateCommand())
200 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities 201 command.CommandText = $
"CREATE DATABASE {databaseName}";
202 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities 203 await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(
false);
206 await console.WriteAsync(
"Success!",
true, cancellationToken).ConfigureAwait(
false);
207 await console.WriteAsync(
"Dropping test database...",
true, cancellationToken).ConfigureAwait(
false);
208 using (var command = testConnection.CreateCommand())
210 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities 211 command.CommandText = $
"DROP DATABASE {databaseName}";
212 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities 215 await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(
false);
217 catch (OperationCanceledException)
223 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
224 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
225 await console.WriteAsync(
"This should be okay, but you may want to manually drop the database before continuing!",
true, cancellationToken).ConfigureAwait(
false);
226 await console.WriteAsync(
"Press any key to continue...",
true, cancellationToken).ConfigureAwait(
false);
227 await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(
false);
233 if (isSqliteDB && !dbExists)
235 console.WriteAsync(
"Deleting test database file...",
true, cancellationToken),
236 ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(
false);
241 var resolvedPath = ioManager.ResolvePath(databaseName);
244 var directoryName = ioManager.GetDirectoryName(resolvedPath);
245 bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(
false);
246 await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(
false);
249 await ioManager.WriteAllBytes(resolvedPath, Array.Empty<byte>(), cancellationToken).ConfigureAwait(
false);
253 if (!directoryExisted)
254 await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(
false);
263 if (!Path.IsPathRooted(databaseName))
265 await console.WriteAsync(
"Note, this relative path (currently) resolves to the following:",
true, cancellationToken).ConfigureAwait(
false);
266 await console.WriteAsync(resolvedPath,
true, cancellationToken).ConfigureAwait(
false);
267 bool writeResolved = await PromptYesNo(
268 "Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
270 .ConfigureAwait(
false);
273 databaseName = resolvedPath;
276 await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(
false);
290 await console.WriteAsync(String.Empty,
true, cancellationToken).ConfigureAwait(
false);
291 await console.WriteAsync(
292 "NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
295 .ConfigureAwait(
false);
296 await console.WriteAsync(
297 "Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
300 .ConfigureAwait(
false);
301 await console.WriteAsync(
302 "This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
305 .ConfigureAwait(
false);
306 await console.WriteAsync(
307 "Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
310 .ConfigureAwait(
false);
311 await console.WriteAsync(String.Empty,
true, cancellationToken).ConfigureAwait(
false);
313 await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(
false);
316 await console.WriteAsync(
"What SQL database type will you be using?",
true, cancellationToken).ConfigureAwait(
false);
319 await console.WriteAsync(
321 CultureInfo.InvariantCulture,
322 "Please enter one of {0}, {1}, {2}, or {3}: ",
325 #pragma warning disable SA1515
328 #pragma warning restore SA1515
332 .ConfigureAwait(
false);
333 var databaseTypeString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
334 if (Enum.TryParse<
DatabaseType>(databaseTypeString, out var databaseType))
337 await console.WriteAsync(
"Invalid database type!",
true, cancellationToken).ConfigureAwait(
false);
347 #pragma warning disable CA1502 // TODO: Decomplexify 350 bool firstTime =
true;
353 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
357 DatabaseType = await PromptDatabaseType(firstTime, cancellationToken).ConfigureAwait(
false)
361 string serverAddress = null;
362 ushort? serverPort = null;
368 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
369 await console.WriteAsync(
"Enter the server's address and port [<server>:<port> or <server>] (blank for local): ",
false, cancellationToken).ConfigureAwait(
false);
370 serverAddress = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
371 if (String.IsNullOrWhiteSpace(serverAddress))
372 serverAddress = null;
373 else if (databaseConfiguration.DatabaseType ==
DatabaseType.SqlServer)
375 var match = Regex.Match(serverAddress,
@"^(?<server>.+):(?<port>.+)$");
378 serverAddress = match.Groups[
"server"].Value;
379 var portString = match.Groups[
"port"].Value;
380 if (UInt16.TryParse(portString, out var port))
384 await console.WriteAsync($
"Failed to parse port \"{portString}\", please try again.",
true, cancellationToken).ConfigureAwait(
false);
394 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
395 await console.WriteAsync($
"Enter the database {(isSqliteDB ? "file path
" : "name
")} (Can be from previous installation. Otherwise, should not exist): ",
false, cancellationToken).ConfigureAwait(
false);
398 bool dbExists =
false;
401 databaseName = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
402 if (!String.IsNullOrWhiteSpace(databaseName))
406 dbExists = await ioManager.FileExists(databaseName, cancellationToken).ConfigureAwait(
false);
408 databaseName = await ValidateNonExistantSqliteDBName(databaseName, cancellationToken).ConfigureAwait(
false);
411 dbExists = await PromptYesNo(
"Does this database already exist? If not, we will attempt to CREATE it. (y/n): ", cancellationToken).ConfigureAwait(
false);
414 if (String.IsNullOrWhiteSpace(databaseName))
415 await console.WriteAsync(
"Invalid database name!",
true, cancellationToken).ConfigureAwait(
false);
422 if (databaseConfiguration.DatabaseType ==
DatabaseType.SqlServer && platformIdentifier.IsWindows)
423 useWinAuth = await PromptYesNo(
"Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(
false);
427 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
429 string username = null;
430 string password = null;
434 await console.WriteAsync(
"Enter username: ",
false, cancellationToken).ConfigureAwait(
false);
435 username = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
436 await console.WriteAsync(
"Enter password: ",
false, cancellationToken).ConfigureAwait(
false);
437 password = await console.ReadLineAsync(
true, cancellationToken).ConfigureAwait(
false);
441 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);
442 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);
443 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);
446 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
448 DbConnection testConnection;
449 void CreateTestConnection(
string connectionString) =>
450 testConnection = dbConnectionFactory.CreateConnection(
452 databaseConfiguration.DatabaseType);
454 switch (databaseConfiguration.DatabaseType)
458 var csb =
new SqlConnectionStringBuilder
460 ApplicationName = assemblyInformationProvider.VersionPrefix,
461 DataSource = serverAddress ??
"(local)" 465 csb.IntegratedSecurity =
true;
468 csb.UserID = username;
469 csb.Password = password;
472 CreateTestConnection(csb.ConnectionString);
473 csb.InitialCatalog = databaseName;
474 databaseConfiguration.ConnectionString = csb.ConnectionString;
482 var csb =
new MySqlConnectionStringBuilder
484 Server = serverAddress ??
"127.0.0.1",
489 if (serverPort.HasValue)
490 csb.Port = serverPort.Value;
492 CreateTestConnection(csb.ConnectionString);
493 csb.Database = databaseName;
494 databaseConfiguration.ConnectionString = csb.ConnectionString;
500 var csb =
new SqliteConnectionStringBuilder
502 DataSource = databaseName,
503 Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate
506 CreateTestConnection(csb.ConnectionString);
507 databaseConfiguration.ConnectionString = csb.ConnectionString;
513 var csb =
new NpgsqlConnectionStringBuilder
515 ApplicationName = assemblyInformationProvider.VersionPrefix,
516 Host = serverAddress ??
"127.0.0.1",
521 if (serverPort.HasValue)
522 csb.Port = serverPort.Value;
524 CreateTestConnection(csb.ConnectionString);
525 csb.Database = databaseName;
526 databaseConfiguration.ConnectionString = csb.ConnectionString;
531 throw new InvalidOperationException(
"Invalid DatabaseType!");
536 await TestDatabaseConnection(testConnection, databaseConfiguration, databaseName, dbExists, cancellationToken).ConfigureAwait(
false);
538 return databaseConfiguration;
540 catch (OperationCanceledException)
546 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
547 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
548 await console.WriteAsync(
"Retrying database configuration...",
true, cancellationToken).ConfigureAwait(
false);
553 #pragma warning restore CA1502 569 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
570 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength),
false, cancellationToken).ConfigureAwait(
false);
571 var passwordLengthString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
572 if (String.IsNullOrWhiteSpace(passwordLengthString))
574 if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
580 await console.WriteAsync(
"Please enter a positive integer!",
true, cancellationToken).ConfigureAwait(
false);
586 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
587 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);
588 var topicTimeoutString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
589 if (String.IsNullOrWhiteSpace(topicTimeoutString))
591 if (Int32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
593 newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
597 await console.WriteAsync(
"Please enter a positive integer!",
true, cancellationToken).ConfigureAwait(
false);
601 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
602 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);
603 await console.WriteAsync(
"GitHub personal access token: ",
false, cancellationToken).ConfigureAwait(
false);
604 newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(
true, cancellationToken).ConfigureAwait(
false);
605 if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
606 newGeneralConfiguration.GitHubAccessToken = null;
609 return newGeneralConfiguration;
620 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
621 fileLoggingConfiguration.Disable = !await PromptYesNo(
"Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(
false);
623 if (!fileLoggingConfiguration.Disable)
627 await console.WriteAsync(
"Log file directory path (leave blank for default): ",
false, cancellationToken).ConfigureAwait(
false);
628 fileLoggingConfiguration.Directory = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
629 if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
631 fileLoggingConfiguration.Directory = null;
636 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
637 await console.WriteAsync(
"Testing directory access...",
true, cancellationToken).ConfigureAwait(
false);
640 await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(
false);
641 var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture,
"WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
642 await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(
false);
645 await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(
false);
647 catch (OperationCanceledException)
653 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Error deleting test log file: {0}", testFile),
true, cancellationToken).ConfigureAwait(
false);
654 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
655 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
660 catch (OperationCanceledException)
666 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
667 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
668 await console.WriteAsync(
"Please verify the path is valid and you have access to it!",
true, cancellationToken).ConfigureAwait(
false);
673 async Task<LogLevel?> PromptLogLevel(
string question)
677 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
678 await console.WriteAsync(question,
true, cancellationToken).ConfigureAwait(
false);
679 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);
680 var responseString = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
681 if (String.IsNullOrWhiteSpace(responseString))
683 if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
685 await console.WriteAsync(
"Invalid log level!",
true, cancellationToken).ConfigureAwait(
false);
690 fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture,
"Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(
false) ?? fileLoggingConfiguration.LogLevel;
691 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;
694 return fileLoggingConfiguration;
706 Enable = await PromptYesNo(
"Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(
false),
707 AllowAnyOrigin = await PromptYesNo(
"Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(
false)
710 if (!config.AllowAnyOrigin)
712 await console.WriteAsync(
"Enter a comma seperated list of CORS allowed origins (optional): ",
false, cancellationToken).ConfigureAwait(
false);
713 var commaSeperatedOrigins = await console.ReadLineAsync(
false, cancellationToken).ConfigureAwait(
false);
714 if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
716 var splits = commaSeperatedOrigins.Split(
',');
717 config.
AllowedOrigins =
new List<string>(splits.Select(x => x.Trim()));
737 await console.WriteAsync(String.Format(CultureInfo.InvariantCulture,
"Configuration complete! Saving to {0}", userConfigFileName),
true, cancellationToken).ConfigureAwait(
false);
740 var map =
new Dictionary<string, object>()
748 var json = JsonConvert.SerializeObject(map, Formatting.Indented);
749 var configBytes = Encoding.UTF8.GetBytes(json);
753 await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(
false);
755 catch (OperationCanceledException)
761 await console.WriteAsync(e.Message,
true, cancellationToken).ConfigureAwait(
false);
762 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
763 await console.WriteAsync(
"For your convienence, here's the json we tried to write out:",
true, cancellationToken).ConfigureAwait(
false);
764 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
765 await console.WriteAsync(json,
true, cancellationToken).ConfigureAwait(
false);
766 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
767 await console.WriteAsync(
"Press any key to exit...",
true, cancellationToken).ConfigureAwait(
false);
768 await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(
false);
769 throw new OperationCanceledException();
779 async Task
RunWizard(
string userConfigFileName, CancellationToken cancellationToken)
782 await console.WriteAsync(
"Welcome to tgstation-server 4!",
true, cancellationToken).ConfigureAwait(
false);
783 await console.WriteAsync(
"This wizard will help you configure your server.",
true, cancellationToken).ConfigureAwait(
false);
785 var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(
false);
787 var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(
false);
789 var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(
false);
791 var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(
false);
793 var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(
false);
795 await console.WriteAsync(null,
true, cancellationToken).ConfigureAwait(
false);
797 await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(
false);
807 var setupWizardMode = generalConfiguration.SetupWizardMode;
812 if (!console.Available)
815 throw new InvalidOperationException(
"Asked to run setup wizard with no console avaliable!");
819 var userConfigFileName = String.Format(CultureInfo.InvariantCulture,
"appsettings.{0}.json", hostingEnvironment.EnvironmentName);
821 async Task HandleSetupCancel()
823 await console.WriteAsync(String.Empty,
true,
default).ConfigureAwait(
false);
824 await console.WriteAsync(
"Aborting setup!",
true,
default).ConfigureAwait(
false);
828 Task finalTask = Task.CompletedTask;
829 using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
830 using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
833 var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(
false);
835 bool shouldRunBasedOnAutodetect;
838 var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(
false);
839 var contents = Encoding.UTF8.GetString(bytes);
840 var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() ==
"{}";
841 shouldRunBasedOnAutodetect = existingConfigIsEmpty;
844 shouldRunBasedOnAutodetect =
true;
846 if (!shouldRunBasedOnAutodetect)
850 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);
852 forceRun = await PromptYesNo(
"Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(
false);
860 await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(
false);
862 await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(
false);
866 await finalTask.ConfigureAwait(
false);
871 public async Task
StartAsync(CancellationToken cancellationToken)
873 await CheckRunWizard(cancellationToken).ConfigureAwait(
false);
874 applicationLifetime.StopApplication();
878 public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
List< string > AllowedOrigins
Origins allowed for CORS requests
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SetupWizard
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
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
string MySqlServerVersion
The string form of the global::System.Version of a target MySQL/MariaDB server
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
readonly IDatabaseConnectionFactory dbConnectionFactory
The IDatabaseConnectionFactory for the SetupWizard