internal async Task Init() { var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); XmlConfigurator.Configure(logRepository, new FileInfo("App.config")); //"log4net.config" if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { WriteAndWaitForKey($@"This application must be run as administrator in order to function properly."); return; } //ArkToolkitDomain.Initialize(); //File.WriteAllText(@"ark.json", JsonConvert.SerializeObject(ArkSavegameToolkitNet.ArkToolkitSettings.Instance, Formatting.Indented)); var arkConfigCustom = false; if (File.Exists(Constants.ArkConfigFilePath)) { try { // use custom settings from ark.json ArkToolkitSettings.Instance.Setup(JsonConvert.DeserializeObject <ArkToolkitSettings>(File.ReadAllText(Constants.ArkConfigFilePath))); arkConfigCustom = true; } catch (Exception ex) { Console.AddLogError($@"Error loading 'ark.config'. Using default config. (""{ex.Message}"")"); Logging.LogException("Error loading 'ark.config'. Using default config.", ex, GetType()); } } if (!arkConfigCustom) { // initialize default settings ArkToolkitDomain.Initialize(); } _config = null; string validationMessage = null; string errorMessage = null; if (File.Exists(Constants.ConfigFilePath)) { try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.ConfigFilePath)); if (_config != null) { if (_config.Discord == null) { _config.Discord = new DiscordConfigSection(); } _config.SetupDefaults(); } else { errorMessage = "Config.json is empty. Please delete it and restart the application."; } } catch (Exception ex) { validationMessage = ex.Message; } } var hasValidConfig = _config != null; if (_config == null) { //load defaultconfig if (!File.Exists(Constants.DefaultConfigFilePath)) { WriteAndWaitForKey($@"The required file defaultconfig.json is missing from application directory. Please redownload the application."); return; } try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.DefaultConfigFilePath)); } catch (Exception ex) { } WriteAndWaitForKey( $@"The file config.json is empty or contains errors. Skipping automatic startup...", validationMessage); } Configuration.Config = _config as Config; About.HasValidConfig = hasValidConfig; About.ValidationError = validationMessage; About.ConfigError = errorMessage; if (!hasValidConfig) { About.IsActive = true; return; } else { About.IsVisible = false; Console.IsActive = true; } string errors = ValidateConfig(); if (errors.Length > 0) { WriteAndWaitForKey(errors); return; } IProgress <string> progress = new Progress <string>(message => { Console.AddLog(message); }); var constants = new Shared.Constants(); //if (config.Debug) //{ // //we reset the state so that every run will be the same // if (File.Exists(constants.DatabaseFilePath)) File.Delete(constants.DatabaseFilePath); // if (File.Exists(constants.SavedStateFilePath)) File.Delete(constants.SavedStateFilePath); // //optionally use a saved database state // var databaseStateFilePath = Path.Combine(config.JsonOutputDirPath, "Database.state"); // if (File.Exists(databaseStateFilePath)) File.Copy(databaseStateFilePath, constants.DatabaseFilePath); //} _savedstate = null; try { if (File.Exists(constants.SavedStateFilePath)) { _savedstate = JsonConvert.DeserializeObject <SavedState>(File.ReadAllText(constants.SavedStateFilePath)); _savedstate._Path = constants.SavedStateFilePath; } } catch { /*ignore exceptions */ } _savedstate = _savedstate ?? new SavedState(constants.SavedStateFilePath); var discord = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = _config.Discord.LogLevel }); discord.Log += msg => { Console.AddLog(msg.Message); return(Task.CompletedTask); }; var discordCommands = new CommandService(new CommandServiceConfig { }); var anonymizeData = new ArkBotAnonymizeData(); //setup dependency injection var thisAssembly = Assembly.GetExecutingAssembly(); var builder = new ContainerBuilder(); builder.RegisterType <ArkServerContext>().AsSelf(); builder.RegisterInstance(anonymizeData).AsSelf().As <ArkAnonymizeData>(); if (_config.UseCompatibilityChangeWatcher) { builder.RegisterType <ArkSaveFileWatcherTimer>().As <IArkSaveFileWatcher>(); } else { builder.RegisterType <ArkSaveFileWatcher>().As <IArkSaveFileWatcher>(); } builder.RegisterInstance(discord).AsSelf(); builder.RegisterInstance(discordCommands).AsSelf(); builder.RegisterType <AutofacDiscordServiceProvider>().As <IServiceProvider>().SingleInstance(); builder.RegisterType <ArkDiscordBot>(); builder.RegisterInstance(constants).As <IConstants>(); builder.RegisterInstance(_savedstate).As <ISavedState>(); builder.RegisterInstance(_config as Config).As <IConfig>(); builder.RegisterType <EfDatabaseContext>().AsSelf().As <IEfDatabaseContext>() .WithParameter(new TypedParameter(typeof(string), _config.DatabaseConnectionString)); builder.RegisterType <EfDatabaseContextFactory>(); builder.RegisterType <DatabaseRepo>().As <IDatabaseRepo>().SingleInstance(); builder.RegisterType <ArkServerService>().As <IArkServerService>().SingleInstance(); builder.RegisterType <SavegameBackupService>().As <ISavegameBackupService>().SingleInstance(); builder.RegisterType <PlayerLastActiveService>().As <IPlayerLastActiveService>().SingleInstance(); builder.RegisterType <LogCleanupService>().As <ILogCleanupService>().SingleInstance(); builder.RegisterHubs(Assembly.GetExecutingAssembly()); builder.RegisterType <ArkContextManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <DiscordManager>().AsSelf().SingleInstance(); builder.RegisterType <ScheduledTasksManager>().AsSelf().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).SingleInstance(); builder.RegisterType <NotificationManager>().AsSelf().SingleInstance(); builder.RegisterType <PrometheusManager>().AsSelf().SingleInstance(); builder.RegisterType <AuctionHouseManager>().AsSelf().SingleInstance(); Container = builder.Build(); //update database try { using (var db = Container.Resolve <EfDatabaseContext>()) { db.Database.Migrate(); } } catch (SqlException ex) { Console.AddLogError($@"Error initializing Microsoft SQL Server (""{ex.Message}"")"); Logging.LogException("Error initializing Microsoft SQL Server.", ex, GetType()); return; } _contextManager = Container.Resolve <ArkContextManager>(); //server/cluster contexts if (_config.Clusters?.Count > 0) { foreach (var cluster in _config.Clusters) { var context = new ArkClusterContext(cluster, anonymizeData); _contextManager.AddCluster(context); } } if (_config.Servers?.Count > 0) { var playerLastActiveService = Container.Resolve <IPlayerLastActiveService>(); var backupService = Container.Resolve <ISavegameBackupService>(); var logCleanupService = Container.Resolve <ILogCleanupService>(); foreach (var server in _config.Servers) { var clusterContext = _contextManager.GetCluster(server.ClusterKey); var context = Container.Resolve <ArkServerContext>( new TypedParameter(typeof(ServerConfigSection), server), new TypedParameter(typeof(ArkClusterContext), clusterContext)); var initTask = context.Initialize(); //fire and forget _contextManager.AddServer(context); } // Initialize managers so that they are ready to handle events such as ArkContextManager.InitializationCompleted-event. var scheduledTasksManager = Container.Resolve <ScheduledTasksManager>(); var notificationManager = Container.Resolve <NotificationManager>(); // <GHOST DIVISION> var prometheusManager = Container.Resolve <PrometheusManager>(); var auctionHouseManager = Container.Resolve <AuctionHouseManager>(); // </GHOST DIVISION> // Trigger manual updates for all servers (initialization) foreach (var context in _contextManager.Servers) { ManuallyUpdateServers.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerServerUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateServerManual(context); } // Trigger manual updates for all clusters (initialization) foreach (var context in _contextManager.Clusters) { ManuallyUpdateClusters.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerClusterUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateClusterManual(context); } // <GHOST DIVISION> // run prometheus endpoint if (_config.Prometheus.Enabled) { prometheusManager.Start(); } else { Console.AddLog("Prometheus is disabled."); } // run auction house manager if (_config.AuctionHouse.Enabled) { auctionHouseManager.Start(); } else { Console.AddLog("Auction House monitor is disabled."); } // </GHOST DIVISION> } //run the discord bot if (_config.Discord.Enabled) { _runDiscordBotCts = new CancellationTokenSource(); _runDiscordBotTask = await Task.Factory.StartNew(async() => await RunDiscordBot(), _runDiscordBotCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } else { Console.AddLog("Discord bot is disabled."); } //load the server multipliers data await ArkServerMultipliers.Instance.LoadOrUpdate(); var modIds = _config.Servers?.SelectMany(x => x.ModIds).Distinct().ToArray() ?? new int[] { }; //load the species stats data await ArkSpeciesStats.Instance.LoadOrUpdate(modIds); //load the items data await ArkItems.Instance.LoadOrUpdate(modIds); //ssl if (_config.WebApp.Ssl?.Enabled == true) { var path = $"{_config.WebApp.Ssl.Name}.pfx"; var revoke = false; var renew = false; if (File.Exists(path)) { try { using (var rlt = new X509Certificate2(path, _config.WebApp.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)) { if (DateTime.Now < rlt.NotBefore || DateTime.Now > rlt.NotAfter.AddDays(-31)) { using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); if (store.Certificates.Contains(rlt)) { store.Remove(rlt); } store.Close(); } renew = revoke = true; } } } catch (Exception ex) { Logging.LogException("Failed to remove ssl certificate from store.", ex, GetType()); } } else { renew = true; } if (renew) { var success = false; Console.AddLog(@"SSL Certificate request issued..."); AcmeContext acme = null; try { var pathAccountKey = $"{_config.WebApp.Ssl.Name}-account.pem"; if (File.Exists(pathAccountKey)) { var accountKey = KeyFactory.FromPem(File.ReadAllText(pathAccountKey)); acme = new AcmeContext(WellKnownServers.LetsEncryptV2, accountKey); var account = await acme.Account(); } else { acme = new AcmeContext(WellKnownServers.LetsEncryptV2); var account = await acme.NewAccount(_config.WebApp.Ssl.Email, true); var pemKey = acme.AccountKey.ToPem(); File.WriteAllText(pathAccountKey, pemKey); } var order = await acme.NewOrder(_config.WebApp.Ssl.Domains); var authz = (await order.Authorizations()).First(); var httpChallenge = await authz.Http(); var keyAuthz = httpChallenge.KeyAuthz; using (var webapp = Host.CreateDefaultBuilder() .UseServiceProviderFactory(new AutofacChildLifetimeScopeServiceProviderFactory(Container.BeginLifetimeScope("AspNetCore_IsolatedRoot_SSL_Renew"))) .ConfigureWebHostDefaults(webBuilder => { webBuilder .ConfigureLogging(logging => { logging.ClearProviders(); logging.AddDebug(); logging.AddWebApp(); }) .UseUrls(_config.WebApp.Ssl.ChallengeListenPrefix) .Configure((appBuilder) => { appBuilder.UseRouting(); appBuilder.UseEndpoints(endpoints => { endpoints.MapGet($"/.well-known/acme-challenge/{httpChallenge.Token}", context => { context.Response.ContentType = "application/text"; context.Response.StatusCode = StatusCodes.Status200OK; return(context.Response.WriteAsync(keyAuthz)); }); }); }); }).Build()) { (webapp as IHost).Start(); var result = await httpChallenge.Validate(); while (result.Status == Certes.Acme.Resource.ChallengeStatus.Pending || result.Status == Certes.Acme.Resource.ChallengeStatus.Processing) { await Task.Delay(500); result = await httpChallenge.Resource(); } if (result.Status == Certes.Acme.Resource.ChallengeStatus.Valid) { var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256); var cert = await order.Generate(new CsrInfo { CountryName = new RegionInfo(CultureInfo.CurrentCulture.Name).TwoLetterISORegionName, Locality = "Web", Organization = "ARK Bot", }, privateKey); if (revoke) { //await acme.RevokeCertificate( // File.ReadAllBytes(path), // Certes.Acme.Resource.RevocationReason.Superseded, // KeyFactory.FromPem(File.ReadAllText(pathPrivateKey))); if (File.Exists(path)) { File.Delete(path); } } //File.WriteAllText(pathPrivateKey, privateKey.ToPem()); var pfxBuilder = cert.ToPfx(privateKey); var pfx = pfxBuilder.Build(_config.WebApp.Ssl.Name, _config.WebApp.Ssl.Password); File.WriteAllBytes(path, pfx); success = true; } } if (success) { Console.AddLog(@"SSL Certificate request completed!"); } else { Console.AddLog(@"SSL Certificate challenge failed!"); } } catch (Exception ex) { Console.AddLogError($@"SSL Certificate request failed! (""{ex.Message}"")"); Logging.LogException("Failed to issue ssl certificate.", ex, GetType()); } } // clean up all bindings created by ark bot { var(success, output) = await ProcessHelper.RunCommandLine("netsh http show sslcert", _config); if (success && !string.IsNullOrEmpty(output)) { var r = new Regex($@"(?:(?:\s*IP\:port\s*\:\s*(?<ip>[^\:]+?)\:(?<port>\d+))|(?:\s*Hostname\:port\s*\:\s*(?<hostname>[^\:]+?)\:(?<port>\d+))).*Application ID\s*\:\s*{{{Constants.AppId}}}", RegexOptions.IgnoreCase | RegexOptions.Singleline); var sections = output.Split("\r\n\r\n\r\n", StringSplitOptions.RemoveEmptyEntries); foreach (var section in sections) { var m = r.Match(section); if (!m.Success) { continue; } if (m.Groups["hostname"].Success && m.Groups["port"].Success) { await ProcessHelper.RunCommandLine($"netsh http delete sslcert hostnameport={m.Groups["hostname"].Value}:{m.Groups["port"].Value}", _config); } else if (m.Groups["ip"].Success && m.Groups["port"].Success) { await ProcessHelper.RunCommandLine($"netsh http delete sslcert ipport={m.Groups["ip"].Value}:{m.Groups["port"].Value}", _config); } } } } } // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-3.1 try { _webapp = Host.CreateDefaultBuilder() .UseServiceProviderFactory(new AutofacChildLifetimeScopeServiceProviderFactory(Container.BeginLifetimeScope("AspNetCore_IsolatedRoot"))) .ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureKestrel(options => { options.Listen(IPEndPoint.Parse(_config.WebApp.IPEndpoint), listenOptions => { if (_config.WebApp.Ssl.Enabled) { listenOptions.UseHttps($"../{_config.WebApp.Ssl.Name}.pfx", _config.WebApp.Ssl.Password); } }); }) .ConfigureLogging(logging => { logging.ClearProviders(); logging.AddDebug(); logging.AddWebApp(); }) .UseContentRoot(Path.Combine(AppContext.BaseDirectory, "WebApp")) //Directory.GetCurrentDirectory() .UseWebRoot(Path.Combine(AppContext.BaseDirectory, "WebApp")) .UseStartup <WebAppStartup>(); }).Build(); (_webapp as IHost).Start(); Console.AddLog("Web App started"); } catch (IOException ex) when((ex.HResult & 0x0000FFFF) == 5664) //Failed to bind to address <...>: address already in use. { var portInUseBy = (string)null; try { if (IPEndPoint.TryParse(_config.WebApp.IPEndpoint, out var ipEndpoint)) { // power shell // Get-Process -Id (Get-NetTCPConnection -LocalPort 8600).OwningProcess | Format-List * var(success, output) = await ProcessHelper.RunCommandLine($"(Get-Process -Id (Get-NetTCPConnection -LocalPort {ipEndpoint.Port}).OwningProcess).Name", _config, UseCallOperator : false); if (success && !string.IsNullOrEmpty(output)) { portInUseBy = $@"Port {ipEndpoint.Port} in use by: {output}"; } } } catch (Exception) { /* do nothing */ } Console.AddLogError($@"Failed to start web app! (""{ex.Message}"")"); if (!string.IsNullOrWhiteSpace(portInUseBy)) { Console.AddLogError(portInUseBy); } Logging.LogException("Failed to start web app.", ex, GetType()); } }
internal async Task Init() { System.Console.WriteLine("ARK Discord Bot"); System.Console.WriteLine("------------------------------------------------------"); System.Console.WriteLine(); log4net.Config.XmlConfigurator.Configure(); //load config and check for errors var configPath = @"config.json"; if (!File.Exists(configPath)) { WriteAndWaitForKey($@"The required file config.json is missing from application directory. Please copy defaultconfig.json, set the correct values for your environment and restart the application."); return; } if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { WriteAndWaitForKey($@"This application must be run as administrator in order to function properly."); return; } _config = null; string exceptionMessage = null; try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(configPath)); } catch (Exception ex) { exceptionMessage = ex.Message; } if (_config == null) { WriteAndWaitForKey( $@"The required file config.json is empty or contains errors. Please copy defaultconfig.json, set the correct values for your environment and restart the application.", exceptionMessage); return; } var sb = new StringBuilder(); if (string.IsNullOrWhiteSpace(_config.BotId) || !new Regex(@"^[a-z0-9]+$", RegexOptions.IgnoreCase | RegexOptions.Singleline).IsMatch(_config.BotId)) { sb.AppendLine($@"Error: {nameof(_config.BotId)} is not a valid id."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotId))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotName)) { sb.AppendLine($@"Error: {nameof(_config.BotName)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotName))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotNamespace) || !Uri.IsWellFormedUriString(_config.BotNamespace, UriKind.Absolute)) { sb.AppendLine($@"Error: {nameof(_config.BotNamespace)} is not set or not a valid url."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotNamespace))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SaveFilePath) || !File.Exists(_config.SaveFilePath)) { sb.AppendLine($@"Error: {nameof(_config.SaveFilePath)} is not a valid file path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SaveFilePath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.ClusterSavePath) || !Directory.Exists(_config.ClusterSavePath)) { sb.AppendLine($@"Error: {nameof(_config.ClusterSavePath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.ClusterSavePath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.ArktoolsExecutablePath) || !File.Exists(_config.ArktoolsExecutablePath)) { sb.AppendLine($@"Error: {nameof(_config.ArktoolsExecutablePath)} is not a valid file path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.ArktoolsExecutablePath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.JsonOutputDirPath) || !Directory.Exists(_config.JsonOutputDirPath)) { sb.AppendLine($@"Error: {nameof(_config.JsonOutputDirPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.JsonOutputDirPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.TempFileOutputDirPath) || !Directory.Exists(_config.TempFileOutputDirPath)) { sb.AppendLine($@"Error: {nameof(_config.TempFileOutputDirPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.TempFileOutputDirPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotToken)) { sb.AppendLine($@"Error: {nameof(_config.BotToken)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotToken))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRedirectUri)) { sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRedirectUri)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRedirectUri))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRelyingServiceListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRelyingServiceListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRelyingServiceListenPrefix))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.GoogleApiKey)) { sb.AppendLine($@"Error: {nameof(_config.GoogleApiKey)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.GoogleApiKey))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamApiKey)) { sb.AppendLine($@"Error: {nameof(_config.SteamApiKey)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamApiKey))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.ServerIp)) { sb.AppendLine($@"Error: {nameof(_config.ServerIp)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.ServerIp))}"); sb.AppendLine(); } if (_config.ServerPort <= 0) { sb.AppendLine($@"Error: {nameof(_config.ServerPort)} is not valid."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.ServerPort))}"); sb.AppendLine(); } if (_config.BackupsEnabled && (string.IsNullOrWhiteSpace(_config.BackupsDirectoryPath) || !FileHelper.IsValidDirectoryPath(_config.BackupsDirectoryPath))) { sb.AppendLine($@"Error: {nameof(_config.BackupsDirectoryPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BackupsDirectoryPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.WebApiListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.WebApiListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebApiListenPrefix))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.WebAppListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.WebAppListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebAppListenPrefix))}"); sb.AppendLine(); } var clusterkeys = _config.Clusters?.Select(x => x.Key).ToArray(); var serverkeys = _config.Servers?.Select(x => x.Key).ToArray(); if (serverkeys?.Length > 0 && serverkeys.Length != serverkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count()) { sb.AppendLine($@"Error: {nameof(_config.Servers)} contain non-unique keys."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Servers))}"); sb.AppendLine(); } if (clusterkeys?.Length > 0 && clusterkeys.Length != clusterkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count()) { sb.AppendLine($@"Error: {nameof(_config.Clusters)} contain non-unique keys."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Clusters))}"); sb.AppendLine(); } if (serverkeys?.Length > 0 && (string.IsNullOrEmpty(_config.ServerKey) || !serverkeys.Contains(_config.ServerKey, StringComparer.OrdinalIgnoreCase))) { sb.AppendLine($@"Error: {nameof(_config.ServerKey)} must be set to a value server key if server instances are configured."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.ServerKey))}"); sb.AppendLine(); } if (_config.Servers?.Length > 0) { foreach (var server in _config.Servers) { if (server.Cluster != null && !clusterkeys.Contains(server.Cluster)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Cluster)} reference missing cluster key ""{server.Cluster}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Cluster))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(server.SaveFilePath) || !File.Exists(server.SaveFilePath)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.SaveFilePath)} is not a valid file path for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.SaveFilePath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(server.Ip)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Ip)} is not set for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Ip))}"); sb.AppendLine(); } if (server.Port <= 0) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Port)} is not valid for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Port))}"); sb.AppendLine(); } //if (server.RconPort <= 0) //{ // sb.AppendLine($@"Error: {nameof(config.Servers)}.{nameof(server.RconPort)} is not valid for server instance ""{server.Key}""."); // sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.RconPort))}"); // sb.AppendLine(); //} } } if (_config.Clusters?.Length > 0) { foreach (var cluster in _config.Clusters) { if (string.IsNullOrWhiteSpace(cluster.SavePath) || !Directory.Exists(cluster.SavePath)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(cluster.SavePath)} is not a valid directory path for cluster instance ""{cluster.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(cluster, nameof(cluster.SavePath))}"); sb.AppendLine(); } } } //todo: for now this section is not really needed unless !imprintcheck is used //if (config.ArkMultipliers == null) //{ // sb.AppendLine($@"Error: {nameof(config.ArkMultipliers)} section is missing from config file."); // sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(config, nameof(config.ArkMultipliers))}"); // sb.AppendLine(); //} if (string.IsNullOrWhiteSpace(_config.AdminRoleName)) { _config.AdminRoleName = "admin"; } if (string.IsNullOrWhiteSpace(_config.DeveloperRoleName)) { _config.DeveloperRoleName = "developer"; } if (string.IsNullOrWhiteSpace(_config.MemberRoleName)) { _config.DeveloperRoleName = "ark"; } //load aliases and check integrity var aliases = ArkSpeciesAliases.Instance; if (aliases == null || !aliases.CheckIntegrity) { sb.AppendLine($@"Error: ""{ArkSpeciesAliases._filepath}"" is missing, contains invalid json or duplicate aliases."); if (aliases != null) { foreach (var duplicateAlias in aliases.Aliases?.SelectMany(x => x).GroupBy(x => x) .Where(g => g.Count() > 1) .Select(g => g.Key)) { sb.AppendLine($@"Duplicate alias: ""{duplicateAlias}"""); } } sb.AppendLine(); } var errors = sb.ToString(); if (errors.Length > 0) { WriteAndWaitForKey(errors); return; } IProgress <string> progress = new Progress <string>(message => { Console.AddLog(message); }); var constants = new ArkBot.Constants(); //if (config.Debug) //{ // //we reset the state so that every run will be the same // if (File.Exists(constants.DatabaseFilePath)) File.Delete(constants.DatabaseFilePath); // if (File.Exists(constants.SavedStateFilePath)) File.Delete(constants.SavedStateFilePath); // //optionally use a saved database state // var databaseStateFilePath = Path.Combine(config.JsonOutputDirPath, "Database.state"); // if (File.Exists(databaseStateFilePath)) File.Copy(databaseStateFilePath, constants.DatabaseFilePath); //} _savedstate = null; try { if (File.Exists(constants.SavedStateFilePath)) { _savedstate = JsonConvert.DeserializeObject <SavedState>(File.ReadAllText(constants.SavedStateFilePath)); _savedstate._Path = constants.SavedStateFilePath; } } catch { /*ignore exceptions */ } _savedstate = _savedstate ?? new SavedState(constants.SavedStateFilePath); //var context = new ArkContext(config, constants, progress); var playedTimeWatcher = new PlayedTimeWatcher(_config); var options = new SteamOpenIdOptions { ListenPrefixes = new[] { _config.SteamOpenIdRelyingServiceListenPrefix }, RedirectUri = _config.SteamOpenIdRedirectUri, }; var openId = new BarebonesSteamOpenId(options, new Func <bool, ulong, ulong, Task <string> >(async(success, steamId, discordId) => { var razorConfig = new TemplateServiceConfiguration { DisableTempFileLocking = true, CachingProvider = new DefaultCachingProvider(t => { }) }; using (var service = RazorEngineService.Create(razorConfig)) { var html = await FileHelper.ReadAllTextTaskAsync(constants.OpenidresponsetemplatePath); return(service.RunCompile(html, constants.OpenidresponsetemplatePath, null, new { Success = success, botName = _config.BotName, botUrl = _config.BotUrl })); } })); var discord = new DiscordClient(x => { x.LogLevel = LogSeverity.Warning; x.LogHandler += (s, e) => Console.AddLog(e.Message); x.AppName = _config.BotName; x.AppUrl = !string.IsNullOrWhiteSpace(_config.BotUrl) ? _config.BotUrl : null; }); //setup dependency injection var thisAssembly = Assembly.GetExecutingAssembly(); var builder = new ContainerBuilder(); builder.RegisterType <ArkServerContext>().AsSelf(); if (_config.UseCompatibilityChangeWatcher) { builder.RegisterType <ArkSaveFileWatcherTimer>().As <IArkSaveFileWatcher>(); } else { builder.RegisterType <ArkSaveFileWatcher>().As <IArkSaveFileWatcher>(); } builder.RegisterInstance(discord).AsSelf(); builder.RegisterType <ArkDiscordBot>(); builder.RegisterType <UrlShortenerService>().As <IUrlShortenerService>().SingleInstance(); builder.RegisterInstance(constants).As <IConstants>(); builder.RegisterInstance(_savedstate).As <ISavedState>(); builder.RegisterInstance(_config as Config).As <IConfig>(); builder.RegisterInstance(playedTimeWatcher).As <IPlayedTimeWatcher>(); builder.RegisterType <ArkContext>().As <IArkContext>() .WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).SingleInstance(); builder.RegisterAssemblyTypes(thisAssembly).As <ArkBot.Commands.ICommand>().AsSelf().SingleInstance() .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies); builder.RegisterInstance(openId).As <IBarebonesSteamOpenId>(); builder.RegisterType <EfDatabaseContext>().AsSelf().As <IEfDatabaseContext>() .WithParameter(new TypedParameter(typeof(string), constants.DatabaseConnectionString)); builder.RegisterType <EfDatabaseContextFactory>(); builder.RegisterType <Migrations.Configuration>().PropertiesAutowired(); builder.RegisterType <ArkServerService>().As <IArkServerService>().SingleInstance(); builder.RegisterType <SavegameBackupService>().As <ISavegameBackupService>().SingleInstance(); //register vote handlers builder.RegisterType <BanVoteHandler>().As <IVoteHandler <BanVote> >(); builder.RegisterType <UnbanVoteHandler>().As <IVoteHandler <UnbanVote> >(); builder.RegisterType <RestartServerVoteHandler>().As <IVoteHandler <RestartServerVote> >(); builder.RegisterType <UpdateServerVoteHandler>().As <IVoteHandler <UpdateServerVote> >(); builder.RegisterType <DestroyWildDinosVoteHandler>().As <IVoteHandler <DestroyWildDinosVote> >(); builder.RegisterType <SetTimeOfDayVoteHandler>().As <IVoteHandler <SetTimeOfDayVote> >(); builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); builder.RegisterHubs(Assembly.GetExecutingAssembly()); builder.RegisterType <ArkContextManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <VotingManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <DiscordManager>().AsSelf().SingleInstance(); builder.RegisterType <ScheduledTasksManager>().AsSelf().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).SingleInstance(); builder.RegisterType <NotificationManager>().AsSelf().SingleInstance(); builder.RegisterType <AutofacDependencyResolver>().As <IDependencyResolver>().SingleInstance(); //kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context => // resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients // ).WhenInjectedInto<IStockTicker>(); Container = builder.Build(); //update database System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion <EfDatabaseContext, Migrations.Configuration>(true, Container.Resolve <Migrations.Configuration>())); _contextManager = Container.Resolve <ArkContextManager>(); //server/cluster contexts if (_config.Clusters?.Length > 0) { foreach (var cluster in _config.Clusters) { var context = new ArkClusterContext(cluster); _contextManager.AddCluster(context); } } if (_config.Servers?.Length > 0) { var backupService = Container.Resolve <ISavegameBackupService>(); foreach (var server in _config.Servers) { var context = Container.Resolve <ArkServerContext>(new TypedParameter(typeof(ServerConfigSection), server)); var initTask = context.Initialize(); //fire and forget _contextManager.AddServer(context); } // Initialize managers so that they are ready to handle events such as ArkContextManager.InitializationCompleted-event. var scheduledTasksManager = Container.Resolve <ScheduledTasksManager>(); var votingManager = Container.Resolve <VotingManager>(); var notificationMangager = Container.Resolve <NotificationManager>(); // Trigger manual updates for all servers (initialization) foreach (var context in _contextManager.Servers) { ManuallyUpdateServers.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerServerUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateServerManual(context); } // Trigger manual updates for all clusters (initialization) foreach (var context in _contextManager.Clusters) { ManuallyUpdateClusters.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerClusterUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateClusterManual(context); } } //run the discord bot if (_config.DiscordBotEnabled) { _runDiscordBotCts = new CancellationTokenSource(); _runDiscordBotTask = await Task.Factory.StartNew(async() => await RunDiscordBot(), _runDiscordBotCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } else { Console.AddLog("Discord bot is disabled."); } //load the species stats data await ArkSpeciesStats.Instance.LoadOrUpdate(); //webapi _webapi = Microsoft.Owin.Hosting.WebApp.Start <WebApiStartup>(url: _config.WebApiListenPrefix); Console.AddLog("Web API started"); //webapi _webapp = Microsoft.Owin.Hosting.WebApp.Start <WebApp.WebAppStartup>(url: _config.WebAppListenPrefix); Console.AddLog("Web App started"); }
internal async Task Init() { System.Console.WriteLine("ARK Bot"); System.Console.WriteLine("------------------------------------------------------"); System.Console.WriteLine(); log4net.Config.XmlConfigurator.Configure(); //load config and check for errors if (!File.Exists(Constants.ConfigFilePath)) { WriteAndWaitForKey($@"The required file config.json is missing from application directory. Please copy defaultconfig.json, set the correct values for your environment and restart the application."); return; } if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { WriteAndWaitForKey($@"This application must be run as administrator in order to function properly."); return; } _config = null; string exceptionMessage = null; try { _config = JsonConvert.DeserializeObject <Config>(File.ReadAllText(Constants.ConfigFilePath)); } catch (Exception ex) { exceptionMessage = ex.Message; } if (_config == null) { WriteAndWaitForKey( $@"The required file config.json is empty or contains errors. Please copy defaultconfig.json, set the correct values for your environment and restart the application.", exceptionMessage); return; } var sb = new StringBuilder(); if (string.IsNullOrWhiteSpace(_config.BotId) || !new Regex(@"^[a-z0-9]+$", RegexOptions.IgnoreCase | RegexOptions.Singleline).IsMatch(_config.BotId)) { sb.AppendLine($@"Error: {nameof(_config.BotId)} is not a valid id."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotId))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotName)) { sb.AppendLine($@"Error: {nameof(_config.BotName)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotName))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotNamespace) || !Uri.IsWellFormedUriString(_config.BotNamespace, UriKind.Absolute)) { sb.AppendLine($@"Error: {nameof(_config.BotNamespace)} is not set or not a valid url."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotNamespace))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.TempFileOutputDirPath) || !Directory.Exists(_config.TempFileOutputDirPath)) { sb.AppendLine($@"Error: {nameof(_config.TempFileOutputDirPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.TempFileOutputDirPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.BotToken)) { sb.AppendLine($@"Error: {nameof(_config.BotToken)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BotToken))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRedirectUri)) { sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRedirectUri)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRedirectUri))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamOpenIdRelyingServiceListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.SteamOpenIdRelyingServiceListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamOpenIdRelyingServiceListenPrefix))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.GoogleApiKey)) { sb.AppendLine($@"Error: {nameof(_config.GoogleApiKey)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.GoogleApiKey))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.SteamApiKey)) { sb.AppendLine($@"Error: {nameof(_config.SteamApiKey)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.SteamApiKey))}"); sb.AppendLine(); } if (_config.BackupsEnabled && (string.IsNullOrWhiteSpace(_config.BackupsDirectoryPath) || !FileHelper.IsValidDirectoryPath(_config.BackupsDirectoryPath))) { sb.AppendLine($@"Error: {nameof(_config.BackupsDirectoryPath)} is not a valid directory path."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.BackupsDirectoryPath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.WebApiListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.WebApiListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebApiListenPrefix))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.WebAppListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.WebAppListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.WebAppListenPrefix))}"); sb.AppendLine(); } if (_config.Ssl?.Enabled == true) { if (string.IsNullOrWhiteSpace(_config.Ssl.Name)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Name)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Name))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.Ssl.Password)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Password)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Password))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.Ssl.Email)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Email)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Email))}"); sb.AppendLine(); } if (!(_config.Ssl.Domains?.Length >= 1) || _config.Ssl.Domains.Any(x => string.IsNullOrWhiteSpace(x))) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.Domains)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.Domains))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(_config.Ssl.ChallengeListenPrefix)) { sb.AppendLine($@"Error: {nameof(_config.Ssl)}.{nameof(_config.Ssl.ChallengeListenPrefix)} is not set."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config.Ssl, nameof(_config.Ssl.ChallengeListenPrefix))}"); sb.AppendLine(); } } var clusterkeys = _config.Clusters?.Select(x => x.Key).ToArray(); var serverkeys = _config.Servers?.Select(x => x.Key).ToArray(); if (serverkeys?.Length > 0 && serverkeys.Length != serverkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count()) { sb.AppendLine($@"Error: {nameof(_config.Servers)} contain non-unique keys."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Servers))}"); sb.AppendLine(); } if (clusterkeys?.Length > 0 && clusterkeys.Length != clusterkeys.Distinct(StringComparer.OrdinalIgnoreCase).Count()) { sb.AppendLine($@"Error: {nameof(_config.Clusters)} contain non-unique keys."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(_config, nameof(_config.Clusters))}"); sb.AppendLine(); } if (_config.Servers?.Length > 0) { foreach (var server in _config.Servers) { if (server.Cluster != null && !clusterkeys.Contains(server.Cluster)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Cluster)} reference missing cluster key ""{server.Cluster}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Cluster))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(server.SaveFilePath) || !File.Exists(server.SaveFilePath)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.SaveFilePath)} is not a valid file path for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.SaveFilePath))}"); sb.AppendLine(); } if (string.IsNullOrWhiteSpace(server.Ip)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Ip)} is not set for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Ip))}"); sb.AppendLine(); } if (server.Port <= 0) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(server.Port)} is not valid for server instance ""{server.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.Port))}"); sb.AppendLine(); } //if (server.RconPort <= 0) //{ // sb.AppendLine($@"Error: {nameof(config.Servers)}.{nameof(server.RconPort)} is not valid for server instance ""{server.Key}""."); // sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(server, nameof(server.RconPort))}"); // sb.AppendLine(); //} } } if (_config.Clusters?.Length > 0) { foreach (var cluster in _config.Clusters) { if (string.IsNullOrWhiteSpace(cluster.SavePath) || !Directory.Exists(cluster.SavePath)) { sb.AppendLine($@"Error: {nameof(_config.Servers)}.{nameof(cluster.SavePath)} is not a valid directory path for cluster instance ""{cluster.Key}""."); sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(cluster, nameof(cluster.SavePath))}"); sb.AppendLine(); } } } //todo: for now this section is not really needed unless !imprintcheck is used //if (config.ArkMultipliers == null) //{ // sb.AppendLine($@"Error: {nameof(config.ArkMultipliers)} section is missing from config file."); // sb.AppendLine($@"Expected value: {ValidationHelper.GetDescriptionForMember(config, nameof(config.ArkMultipliers))}"); // sb.AppendLine(); //} if (_config.AnonymizeWebApiData) { System.Console.WriteLine("Anonymizing all data in the WebAPI (anonymizeWebApiData=true)" + Environment.NewLine); } if (string.IsNullOrWhiteSpace(_config.MemberRoleName)) { _config.MemberRoleName = "ark"; } //load aliases and check integrity var aliases = ArkSpeciesAliases.Instance; if (aliases == null || !aliases.CheckIntegrity) { sb.AppendLine($@"Error: ""{ArkSpeciesAliases._filepath}"" is missing, contains invalid json or duplicate aliases."); if (aliases != null) { foreach (var duplicateAlias in aliases.Aliases?.SelectMany(x => x).GroupBy(x => x) .Where(g => g.Count() > 1) .Select(g => g.Key)) { sb.AppendLine($@"Duplicate alias: ""{duplicateAlias}"""); } } sb.AppendLine(); } var errors = sb.ToString(); if (errors.Length > 0) { WriteAndWaitForKey(errors); return; } IProgress <string> progress = new Progress <string>(message => { Console.AddLog(message); }); var constants = new ArkBot.Constants(); //if (config.Debug) //{ // //we reset the state so that every run will be the same // if (File.Exists(constants.DatabaseFilePath)) File.Delete(constants.DatabaseFilePath); // if (File.Exists(constants.SavedStateFilePath)) File.Delete(constants.SavedStateFilePath); // //optionally use a saved database state // var databaseStateFilePath = Path.Combine(config.JsonOutputDirPath, "Database.state"); // if (File.Exists(databaseStateFilePath)) File.Copy(databaseStateFilePath, constants.DatabaseFilePath); //} _savedstate = null; try { if (File.Exists(constants.SavedStateFilePath)) { _savedstate = JsonConvert.DeserializeObject <SavedState>(File.ReadAllText(constants.SavedStateFilePath)); _savedstate._Path = constants.SavedStateFilePath; } } catch { /*ignore exceptions */ } _savedstate = _savedstate ?? new SavedState(constants.SavedStateFilePath); //var context = new ArkContext(config, constants, progress); //var playedTimeWatcher = new PlayedTimeWatcher(_config); var options = new SteamOpenIdOptions { ListenPrefixes = new[] { _config.SteamOpenIdRelyingServiceListenPrefix }, RedirectUri = _config.SteamOpenIdRedirectUri, }; var openId = new BarebonesSteamOpenId(options, new Func <bool, ulong, ulong, Task <string> >(async(success, steamId, discordId) => { var razorConfig = new TemplateServiceConfiguration { DisableTempFileLocking = true, CachingProvider = new DefaultCachingProvider(t => { }) }; using (var service = RazorEngineService.Create(razorConfig)) { var html = await FileHelper.ReadAllTextTaskAsync(constants.OpenidresponsetemplatePath); return(service.RunCompile(html, constants.OpenidresponsetemplatePath, null, new { Success = success, botName = _config.BotName, botUrl = _config.BotUrl })); } })); var discord = new DiscordSocketClient(new DiscordSocketConfig { WebSocketProvider = WS4NetProvider.Instance, //required for Win 7 LogLevel = LogSeverity.Warning }); discord.Log += msg => { Console.AddLog(msg.Message); return(Task.CompletedTask); }; var discordCommands = new CommandService(new CommandServiceConfig { }); var anonymizeData = new ArkBotAnonymizeData(); //setup dependency injection var thisAssembly = Assembly.GetExecutingAssembly(); var builder = new ContainerBuilder(); builder.RegisterType <ArkServerContext>().AsSelf(); builder.RegisterInstance(anonymizeData).AsSelf().As <ArkAnonymizeData>(); if (_config.UseCompatibilityChangeWatcher) { builder.RegisterType <ArkSaveFileWatcherTimer>().As <IArkSaveFileWatcher>(); } else { builder.RegisterType <ArkSaveFileWatcher>().As <IArkSaveFileWatcher>(); } builder.RegisterInstance(discord).AsSelf(); builder.RegisterInstance(discordCommands).AsSelf(); builder.RegisterType <AutofacDiscordServiceProvider>().As <IServiceProvider>().SingleInstance(); builder.RegisterType <ArkDiscordBot>(); builder.RegisterType <UrlShortenerService>().As <IUrlShortenerService>().SingleInstance(); builder.RegisterInstance(constants).As <IConstants>(); builder.RegisterInstance(_savedstate).As <ISavedState>(); builder.RegisterInstance(_config as Config).As <IConfig>(); //builder.RegisterInstance(playedTimeWatcher).As<IPlayedTimeWatcher>(); builder.RegisterInstance(openId).As <IBarebonesSteamOpenId>(); builder.RegisterType <EfDatabaseContext>().AsSelf().As <IEfDatabaseContext>() .WithParameter(new TypedParameter(typeof(string), constants.DatabaseConnectionString)); builder.RegisterType <EfDatabaseContextFactory>(); builder.RegisterType <Migrations.Configuration>().PropertiesAutowired(); builder.RegisterType <ArkServerService>().As <IArkServerService>().SingleInstance(); builder.RegisterType <SavegameBackupService>().As <ISavegameBackupService>().SingleInstance(); builder.RegisterType <PlayerLastActiveService>().As <IPlayerLastActiveService>().SingleInstance(); //register vote handlers builder.RegisterType <BanVoteHandler>().As <IVoteHandler <BanVote> >(); builder.RegisterType <UnbanVoteHandler>().As <IVoteHandler <UnbanVote> >(); builder.RegisterType <RestartServerVoteHandler>().As <IVoteHandler <RestartServerVote> >(); builder.RegisterType <UpdateServerVoteHandler>().As <IVoteHandler <UpdateServerVote> >(); builder.RegisterType <DestroyWildDinosVoteHandler>().As <IVoteHandler <DestroyWildDinosVote> >(); builder.RegisterType <SetTimeOfDayVoteHandler>().As <IVoteHandler <SetTimeOfDayVote> >(); builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); builder.RegisterHubs(Assembly.GetExecutingAssembly()); builder.RegisterType <ArkContextManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <VotingManager>().WithParameter(new TypedParameter(typeof(IProgress <string>), progress)).AsSelf().SingleInstance(); builder.RegisterType <DiscordManager>().AsSelf().SingleInstance(); builder.RegisterType <ScheduledTasksManager>().AsSelf().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies).SingleInstance(); builder.RegisterType <NotificationManager>().AsSelf().SingleInstance(); builder.RegisterType <AutofacDependencyResolver>().As <IDependencyResolver>().SingleInstance(); builder.RegisterType <WebApiStartup>().AsSelf(); builder.RegisterType <WebApp.WebAppStartup>().AsSelf(); var webapiConfig = new System.Web.Http.HttpConfiguration(); var webappConfig = new System.Web.Http.HttpConfiguration(); builder.RegisterInstance(webapiConfig).Keyed <System.Web.Http.HttpConfiguration>("webapi"); builder.RegisterInstance(webappConfig).Keyed <System.Web.Http.HttpConfiguration>("webapp"); builder.RegisterWebApiFilterProvider(webapiConfig); builder.Register(c => new AccessControlAuthorizationFilter(c.Resolve <IConfig>())) .AsWebApiAuthorizationFilterFor <WebApi.Controllers.BaseApiController>() .InstancePerRequest(); //kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context => // resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients // ).WhenInjectedInto<IStockTicker>(); Container = builder.Build(); var dir = Path.GetDirectoryName(constants.DatabaseFilePath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } //update database System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion <EfDatabaseContext, Migrations.Configuration>(true, Container.Resolve <Migrations.Configuration>())); //create database immediately to support direct (non-ef) access in application using (var db = Container.Resolve <IEfDatabaseContext>()) { db.Database.Initialize(false); } _contextManager = Container.Resolve <ArkContextManager>(); //server/cluster contexts if (_config.Clusters?.Length > 0) { foreach (var cluster in _config.Clusters) { var context = new ArkClusterContext(cluster, anonymizeData); _contextManager.AddCluster(context); } } if (_config.Servers?.Length > 0) { var playerLastActiveService = Container.Resolve <IPlayerLastActiveService>(); var backupService = Container.Resolve <ISavegameBackupService>(); foreach (var server in _config.Servers) { var clusterContext = _contextManager.GetCluster(server.Cluster); var context = Container.Resolve <ArkServerContext>( new TypedParameter(typeof(ServerConfigSection), server), new TypedParameter(typeof(ArkClusterContext), clusterContext)); var initTask = context.Initialize(); //fire and forget _contextManager.AddServer(context); } // Initialize managers so that they are ready to handle events such as ArkContextManager.InitializationCompleted-event. var scheduledTasksManager = Container.Resolve <ScheduledTasksManager>(); var votingManager = Container.Resolve <VotingManager>(); var notificationMangager = Container.Resolve <NotificationManager>(); // Trigger manual updates for all servers (initialization) foreach (var context in _contextManager.Servers) { ManuallyUpdateServers.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerServerUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateServerManual(context); } // Trigger manual updates for all clusters (initialization) foreach (var context in _contextManager.Clusters) { ManuallyUpdateClusters.Add(new MenuItemViewModel { Header = context.Config.Key, Command = new DelegateCommand <string>(OnManuallyTriggerClusterUpdate), CommandParameter = context.Config.Key }); _contextManager.QueueUpdateClusterManual(context); } } //run the discord bot if (_config.DiscordBotEnabled) { _runDiscordBotCts = new CancellationTokenSource(); _runDiscordBotTask = await Task.Factory.StartNew(async() => await RunDiscordBot(), _runDiscordBotCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); } else { Console.AddLog("Discord bot is disabled."); } //load the species stats data await ArkSpeciesStats.Instance.LoadOrUpdate(); //ssl if (_config.Ssl?.Enabled == true) { var path = $"{_config.Ssl.Name}.pfx"; var revoke = false; var renew = false; if (File.Exists(path)) { try { using (var rlt = new X509Certificate2(path, _config.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)) { if (DateTime.Now < rlt.NotBefore || DateTime.Now > rlt.NotAfter.AddDays(-31)) { using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); if (store.Certificates.Contains(rlt)) { store.Remove(rlt); } store.Close(); } renew = revoke = true; } } } catch (Exception ex) { Logging.LogException("Failed to remove ssl certificate from store.", ex, this.GetType()); } } else { renew = true; } if (renew) { var success = false; Console.AddLog(@"SSL Certificate request issued..."); try { using (var client = new AcmeClient(WellKnownServers.LetsEncrypt)) { var account = await client.NewRegistraton($"mailto:{_config.Ssl.Email}"); account.Data.Agreement = account.GetTermsOfServiceUri(); account = await client.UpdateRegistration(account); var authz = await client.NewAuthorization(new AuthorizationIdentifier { Type = AuthorizationIdentifierTypes.Dns, Value = _config.Ssl.Domains.First() }); var httpChallengeInfo = authz.Data.Challenges.Where(c => c.Type == ChallengeTypes.Http01).First(); var keyAuthString = client.ComputeKeyAuthorization(httpChallengeInfo); using (var webapp = Microsoft.Owin.Hosting.WebApp.Start(_config.Ssl.ChallengeListenPrefix, (appBuilder) => { var challengePath = new PathString("/.well-known/acme-challenge/"); appBuilder.Use(new Func <AppFunc, AppFunc>((next) => { AppFunc appFunc = async environment => { IOwinContext context = new OwinContext(environment); if (!context.Request.Path.Equals(challengePath)) { await next.Invoke(environment); } context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; context.Response.ContentType = "application/text"; await context.Response.WriteAsync(keyAuthString); }; return(appFunc); })); })) { var httpChallenge = await client.CompleteChallenge(httpChallengeInfo); authz = await client.GetAuthorization(httpChallenge.Location); while (authz.Data.Status == EntityStatus.Pending) { await Task.Delay(500); authz = await client.GetAuthorization(httpChallenge.Location); } if (authz.Data.Status == EntityStatus.Valid) { if (revoke) { //await client.RevokeCertificate(path); //todo: how to revoke a cert when we do not have the AcmeCertificate-object? if (File.Exists(path)) { File.Delete(path); } } var csr = new CertificationRequestBuilder(); foreach (var domain in _config.Ssl.Domains) { csr.AddName("CN", domain); } var cert = await client.NewCertificate(csr); var pfxBuilder = cert.ToPfx(); var pfx = pfxBuilder.Build(_config.Ssl.Name, _config.Ssl.Password); File.WriteAllBytes(path, pfx); success = true; } } } if (success) { Console.AddLog(@"SSL Certificate request completed!"); } else { Console.AddLog(@"SSL Certificate challenge failed!"); } } catch (Exception ex) { Console.AddLog($@"SSL Certificate request failed! (""{ex.Message}"")"); Logging.LogException("Failed to issue ssl certificate.", ex, this.GetType()); } } if (File.Exists(path)) { var hostname = _config.Ssl.Domains.First(); var attribute = (GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), true)[0]; var appId = attribute.Value; try { using (var rlt = new X509Certificate2(path, _config.Ssl.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet)) { using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); var certs = store.Certificates.Find(X509FindType.FindBySubjectName, _config.Ssl.Domains.First(), false); if (!store.Certificates.Contains(rlt)) { store.Add(rlt); } store.Close(); } if (_config.Ssl.UseCompatibilityNonSNIBindings) { Console.AddLog(@"Binding SSL Certificate to ip/port..."); } else { Console.AddLog(@"Binding SSL Certificate to hostname/port..."); } foreach (var port in _config.Ssl.Ports) { var commands = new[] { _config.Ssl.UseCompatibilityNonSNIBindings ? $"netsh http delete sslcert ipport=0.0.0.0:{port}" : $"netsh http delete sslcert hostnameport={hostname}:{port}", _config.Ssl.UseCompatibilityNonSNIBindings ? $"netsh http add sslcert ipport=0.0.0.0:{port} certhash={rlt.Thumbprint} appid={{{appId}}} certstore=my" : $"netsh http add sslcert hostnameport={hostname}:{port} certhash={rlt.Thumbprint} appid={{{appId}}} certstore=my" }; var exitCode = 0; foreach (var cmd in commands) { await Task.Run(() => { using (var proc = Process.Start(new ProcessStartInfo { FileName = "cmd.exe", Arguments = $"/c {cmd}", Verb = "runas", UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true })) { proc.WaitForExit(); exitCode = proc.ExitCode; //only add (last cmd) is interesting } }); } if (_config.Ssl.UseCompatibilityNonSNIBindings) { Console.AddLog("[" + (exitCode == 0 ? "Success" : "Failed") + $"] ipport: 0.0.0.0:{port}, thumbprint={rlt.Thumbprint}, appid={{{appId}}}"); } else { Console.AddLog("[" + (exitCode == 0 ? "Success" : "Failed") + $"] hostnameport: {hostname}:{port}, thumbprint={rlt.Thumbprint}, appid={{{appId}}}"); } } } } catch (CryptographicException ex) { Logging.LogException("Failed to open SSL certificate.", ex, this.GetType(), LogLevel.FATAL, ExceptionLevel.Unhandled); WriteAndWaitForKey("Failed to open SSL certificate (wrong password?)"); return; } } } //webapi _webapi = Microsoft.Owin.Hosting.WebApp.Start(_config.WebApiListenPrefix, app => { var startup = Container.Resolve <WebApiStartup>(); startup.Configuration(app, _config, Container, webapiConfig); }); Console.AddLog("Web API started"); //webapp _webapp = Microsoft.Owin.Hosting.WebApp.Start(_config.WebAppListenPrefix, app => { var startup = Container.Resolve <WebApp.WebAppStartup>(); startup.Configuration(app, _config, Container, webappConfig); }); Console.AddLog("Web App started"); if (_config.WebAppRedirectListenPrefix?.Length > 0) { foreach (var redir in _config.WebAppRedirectListenPrefix) { _webappRedirects.Add(Microsoft.Owin.Hosting.WebApp.Start <WebApp.WebAppRedirectStartup>(url: redir)); Console.AddLog("Web App redirect added"); } } }