From 58411bdad48f3293611edf1613c6a724fa30cb88 Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 2 Apr 2024 21:20:57 -0400 Subject: [PATCH 01/13] Configuration revamp pt 1 --- .../Configuration/IServerConfiguration.cs | 68 ++++++++++ .../Configuration/NetworkConfiguration.cs | 22 ++++ .../RconConfiguration.cs} | 6 +- Obsidian.API/_Interfaces/IServer.cs | 1 + .../_Interfaces/IServerConfiguration.cs | 122 ------------------ Obsidian/Client.cs | 2 +- Obsidian/Hosting/DependencyInjection.cs | 1 + Obsidian/Net/ClientHandler.cs | 1 + Obsidian/Net/Rcon/RconServer.cs | 1 + Obsidian/Plugins/PluginManager.cs | 1 + Obsidian/Server.cs | 3 +- Obsidian/Utilities/ServerConfiguration.cs | 55 ++------ Obsidian/Utilities/ServerStatus.cs | 1 + Obsidian/WorldData/World.cs | 1 + 14 files changed, 113 insertions(+), 172 deletions(-) create mode 100644 Obsidian.API/Configuration/IServerConfiguration.cs create mode 100644 Obsidian.API/Configuration/NetworkConfiguration.cs rename Obsidian.API/{_Types/Config/RconConfig.cs => Configuration/RconConfiguration.cs} (80%) delete mode 100644 Obsidian.API/_Interfaces/IServerConfiguration.cs diff --git a/Obsidian.API/Configuration/IServerConfiguration.cs b/Obsidian.API/Configuration/IServerConfiguration.cs new file mode 100644 index 000000000..abb2dca25 --- /dev/null +++ b/Obsidian.API/Configuration/IServerConfiguration.cs @@ -0,0 +1,68 @@ +namespace Obsidian.API.Configuration; + +public interface IServerConfiguration +{ + public bool? Baah { get; set; } + + /// + /// Allows the server to advertise itself as a LAN server to devices on your network. + /// + public bool AllowLan { get; set; } + + /// + /// Server description. + /// + public string Motd { get; set; } + + /// + /// The port on which to listen for incoming connection attempts. + /// + public int Port { get; set; } + + /// + /// Whether the server uses MojangAPI for loading skins etc. + /// + public bool OnlineMode { get; set; } + + /// + /// Maximum amount of players that is allowed to be connected at the same time. + /// + public int MaxPlayers { get; set; } + public int PregenerateChunkRange { get; set; } + + /// + /// The speed at which world time & rain time go by. + /// + public int TimeTickSpeedMultiplier { get; set; } + + /// + /// Allow people to requests to become an operator. + /// + public bool AllowOperatorRequests { get; set; } + + /// + /// Enabled Remote Console operation. + /// + /// See more at https://wiki.vg/RCON + public bool EnableRcon => Rcon is not null; + + public bool Whitelist { get; set; } + + /// + /// Network Configuration + /// + public NetworkConfiguration Network { get; set; } + + /// + /// Remote Console configuration + /// + public RconConfiguration? Rcon { get; set; } + + /// + /// The view distance of the server. + /// + /// + /// Players with higher view distance will use the server's view distance. + /// + public byte ViewDistance { get; set; } +} diff --git a/Obsidian.API/Configuration/NetworkConfiguration.cs b/Obsidian.API/Configuration/NetworkConfiguration.cs new file mode 100644 index 000000000..7381ef2f1 --- /dev/null +++ b/Obsidian.API/Configuration/NetworkConfiguration.cs @@ -0,0 +1,22 @@ +namespace Obsidian.API.Configuration; +public sealed record class NetworkConfiguration +{ + /// + /// Returns true if has a value greater than 0. + /// + public bool ShouldThrottle => this.ConnectionThrottle > 0; + + public long KeepAliveInterval { get; set; } = 10_000; + + public long KeepAliveTimeoutInterval { get; set; } = 30_000; + + /// + /// The time in milliseconds to wait before an ip is allowed to try and connect again. + /// + public long ConnectionThrottle { get; set; } = 15_000; + + /// + /// If true, each login/client gets a random username where multiple connections from the same host will be allowed. + /// + public bool MulitplayerDebugMode { get; set; } = false; +} diff --git a/Obsidian.API/_Types/Config/RconConfig.cs b/Obsidian.API/Configuration/RconConfiguration.cs similarity index 80% rename from Obsidian.API/_Types/Config/RconConfig.cs rename to Obsidian.API/Configuration/RconConfiguration.cs index a4e6e5b04..a85984c81 100644 --- a/Obsidian.API/_Types/Config/RconConfig.cs +++ b/Obsidian.API/Configuration/RconConfiguration.cs @@ -1,11 +1,11 @@ -namespace Obsidian.API.Config; +namespace Obsidian.API.Configuration; -public sealed class RconConfig +public sealed record class RconConfiguration { /// /// Password to access the RCON. /// - public string Password { get; set; } + public string? Password { get; set; } /// /// Port on which RCON server listens. diff --git a/Obsidian.API/_Interfaces/IServer.cs b/Obsidian.API/_Interfaces/IServer.cs index fb5aeaaaa..0abfc124c 100644 --- a/Obsidian.API/_Interfaces/IServer.cs +++ b/Obsidian.API/_Interfaces/IServer.cs @@ -1,4 +1,5 @@ using Obsidian.API.Boss; +using Obsidian.API.Configuration; using Obsidian.API.Crafting; namespace Obsidian.API; diff --git a/Obsidian.API/_Interfaces/IServerConfiguration.cs b/Obsidian.API/_Interfaces/IServerConfiguration.cs deleted file mode 100644 index 1595c041e..000000000 --- a/Obsidian.API/_Interfaces/IServerConfiguration.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Obsidian.API.Config; - -namespace Obsidian.API; - -public interface IServerConfiguration -{ - public bool? Baah { get; set; } - - /// - /// Returns true if has a value greater than 0. - /// - public bool CanThrottle => this.ConnectionThrottle > 0; - - /// - /// Allows the server to advertise itself as a LAN server to devices on your network. - /// - public bool AllowLan { get; set; } - - public bool IpWhitelistEnabled { get; set; } - - /// - /// The time in milliseconds to wait before an ip is allowed to try and connect again. - /// - public long ConnectionThrottle { get; set; } - - /// - /// Server description. - /// - public string Motd { get; set; } - - /// - /// The port on which to listen for incoming connection attempts. - /// - public int Port { get; set; } - - /// - /// Message, that is sent to the chat when player successfully joins the server. - /// - public string JoinMessage { get; set; } - - /// - /// Message, that is sent to the chat when player leaves the server. - /// - public string LeaveMessage { get; set; } - - /// - /// Whether the server uses MojangAPI for loading skins etc. - /// - public bool OnlineMode { get; set; } - - /// - /// Maximum amount of players that is allowed to be connected at the same time. - /// - public int MaxPlayers { get; set; } - public int PregenerateChunkRange { get; set; } - - /// - /// The speed at which world time & rain time go by. - /// - public int TimeTickSpeedMultiplier { get; set; } - - /// - /// Allow people to requests to become an operator. - /// - public bool AllowOperatorRequests { get; set; } - - public bool WhitelistEnabled { get; set; } - - /// - /// Whether each login/client gets a random username where multiple connections from the same host will be allowed. - /// - public bool MulitplayerDebugMode { get; set; } - - /// - /// Upper text in the in-game TAB menu. - /// - public string Header { get; set; } - - /// - /// Lower text in the in-game TAB menu. - /// - public string Footer { get; set; } - - /// - /// Interval between KeepAlive packets send by the server. - /// - public long KeepAliveInterval { get; set; } - - /// - /// How long it should take for the server to kick an inactive client. KeepAlive Timeout. - /// - public long KeepAliveTimeoutInterval { get; set; } - - /// - /// Paths of plugins that are loaded at the starttime. - /// - public string[] DownloadPlugins { get; set; } - - /// - /// Enabled Remote Console operation. - /// - /// See more at https://wiki.vg/RCON - public bool EnableRcon => Rcon is not null; - - public bool VerboseExceptionLogging { get; set; } - - /// - /// Remote Console configuration - /// - public RconConfig? Rcon { get; set; } - - /// - /// The view distance of the server. - /// - /// - /// Players with higher view distance will use the server's view distance. - /// - public byte ViewDistance { get; set; } - - public List WhitelistedIPs { get; set; } - public List Whitelisted { get; set; } -} diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index c7943a33d..f85c513b3 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -270,7 +270,7 @@ public async Task StartConnectionAsync() { case 0x00: { - if (this.server.Configuration.CanThrottle) + if (this.server.Configuration.ShouldThrottle) { string ip = ((IPEndPoint)connectionContext.RemoteEndPoint!).Address.ToString(); diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index 4709a93f0..d35412792 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Obsidian.API.Configuration; using Obsidian.Commands.Framework; using Obsidian.Net.Rcon; using Obsidian.Services; diff --git a/Obsidian/Net/ClientHandler.cs b/Obsidian/Net/ClientHandler.cs index ba6952153..4bfe97a52 100644 --- a/Obsidian/Net/ClientHandler.cs +++ b/Obsidian/Net/ClientHandler.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Obsidian.API.Configuration; using Obsidian.API.Logging; using Obsidian.Net.Packets; using Obsidian.Net.Packets.Configuration; diff --git a/Obsidian/Net/Rcon/RconServer.cs b/Obsidian/Net/Rcon/RconServer.cs index 61c1d2573..2295b6760 100644 --- a/Obsidian/Net/Rcon/RconServer.cs +++ b/Obsidian/Net/Rcon/RconServer.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Obsidian.API.Configuration; using Obsidian.Commands.Framework; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; diff --git a/Obsidian/Plugins/PluginManager.cs b/Obsidian/Plugins/PluginManager.cs index 4b9b0cafa..1800f75a3 100644 --- a/Obsidian/Plugins/PluginManager.cs +++ b/Obsidian/Plugins/PluginManager.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Obsidian.API.Configuration; using Obsidian.API.Logging; using Obsidian.API.Plugins; using Obsidian.Commands.Framework; diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 409308781..f0298252f 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Obsidian.API.Boss; using Obsidian.API.Builders; +using Obsidian.API.Configuration; using Obsidian.API.Crafting; using Obsidian.API.Events; using Obsidian.API.Utilities; @@ -362,7 +363,7 @@ private async Task AcceptClientsAsync() return; } - if (this.Configuration.CanThrottle) + if (this.Configuration.ShouldThrottle) { if (throttler.TryGetValue(ip, out var time) && time <= DateTimeOffset.UtcNow) { diff --git a/Obsidian/Utilities/ServerConfiguration.cs b/Obsidian/Utilities/ServerConfiguration.cs index 5c0e5dfc2..83d20ea31 100644 --- a/Obsidian/Utilities/ServerConfiguration.cs +++ b/Obsidian/Utilities/ServerConfiguration.cs @@ -1,50 +1,31 @@ -using Microsoft.Extensions.Logging; -using Obsidian.API.Config; +using Obsidian.API.Configuration; using System.Text.Json.Serialization; namespace Obsidian.Utilities; public sealed class ServerConfiguration : IServerConfiguration { - public string Motd { get; set; } = $"§k||||§r §5Obsidian §cPre§r-§cRelease §r§k||||§r \n§r§lRunning on .NET §l§c{Environment.Version} §r§l<3"; + private byte viewDistance = 10; - public int Port { get; set; } = 25565; + // Anything lower than 3 will cause weird artifacts on the client. + private const byte MinimumViewDistance = 3; - public string JoinMessage { get; set; } = "§e{0} joined the game"; + public string Motd { get; set; } = $"§k||||§r §5Obsidian §cPre§r-§cRelease §r§k||||§r \n§r§lRunning on .NET §l§c{Environment.Version} §r§l<3"; - public string LeaveMessage { get; set; } = "§e{0} left the game"; + public int Port { get; set; } = 25565; public bool OnlineMode { get; set; } = true; public int MaxPlayers { get; set; } = 25; public bool AllowOperatorRequests { get; set; } = true; - /// - /// If true, each login/client gets a random username where multiple connections from the same host will be allowed. - /// - public bool MulitplayerDebugMode { get; set; } = false; - - public string Header { get; set; } = "§dObsidian-powered minecraft server"; - - public string Footer { get; set; } = "§l( §cO §dw §cO §r§l)"; - public bool? Baah { get; set; } - public bool WhitelistEnabled { get; set; } - - public bool IpWhitelistEnabled { get; set; } + public bool Whitelist { get; set; } - public List WhitelistedIPs { get; set; } = new(); - public List Whitelisted { get; set; } = new(); + public NetworkConfiguration Network { get; set; } = new(); - public long KeepAliveInterval { get; set; } = 10_000; // 10 seconds per KeepAlive - - public long KeepAliveTimeoutInterval { get; set; } = 30_000; // No response after 30s? Timeout - - public long ConnectionThrottle { get; set; } = 15_000; - - public string[] DownloadPlugins { get; set; } = []; - public RconConfig? Rcon { get; set; } + public RconConfiguration? Rcon { get; set; } public bool AllowLan { get; set; } = true; // Enabled because it's super useful for debugging tbh @@ -54,27 +35,11 @@ public byte ViewDistance set => viewDistance = value >= MinimumViewDistance ? value : MinimumViewDistance; } - public int PregenerateChunkRange { get; set; } = 15; // by default, pregenerate range from -15 to 15 - - [JsonConverter(typeof(JsonStringEnumConverter))] -#if DEBUG - public LogLevel LogLevel { get; set; } = LogLevel.Debug; -#else - public LogLevel LogLevel { get; set; } = LogLevel.Information; -#endif - - public bool DebugMode; - - public bool VerboseExceptionLogging { get; set; } = false; + public int PregenerateChunkRange { get; set; } = 15; // by default, pregenerate range from -15 to 15; public ServerListQuery ServerListQuery { get; set; } = ServerListQuery.Full; public int TimeTickSpeedMultiplier { get; set; } = 1; - - private byte viewDistance = 10; - - // Anything lower than 3 will cause weird artifacts on the client. - private const byte MinimumViewDistance = 3; } public sealed class ServerWorld diff --git a/Obsidian/Utilities/ServerStatus.cs b/Obsidian/Utilities/ServerStatus.cs index f0f9f5e12..4b860e2f9 100644 --- a/Obsidian/Utilities/ServerStatus.cs +++ b/Obsidian/Utilities/ServerStatus.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Obsidian.API.Configuration; using Obsidian.API.Logging; using Obsidian.API.Utilities; using Obsidian.Entities; diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 7086b33d0..3b98d7740 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Obsidian.API.Configuration; using Obsidian.API.Registry.Codecs.Dimensions; using Obsidian.API.Utilities; using Obsidian.Blocks; From 47f823f427439e8cb3f7a9ffe4437efa3a6bc559 Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 19:37:31 -0400 Subject: [PATCH 02/13] Refactor configuration --- .../Configuration/IServerConfiguration.cs | 4 ++ .../Configuration/MessagesConfiguration.cs | 15 +++++ .../Configuration/WhitelistConfiguration.cs | 7 ++ Obsidian.API/_Interfaces/IServer.cs | 2 +- Obsidian.ConsoleApp/Program.cs | 19 ++---- Obsidian/Hosting/DefaultServerEnvironment.cs | 65 ++++--------------- Obsidian/Hosting/DependencyInjection.cs | 58 ++++++++++------- Obsidian/Hosting/IServerEnvironment.cs | 11 ---- Obsidian/Hosting/ObsidianHostingService.cs | 32 ++++----- Obsidian/Server.cs | 48 +++++++++----- Obsidian/Utilities/ServerConfiguration.cs | 4 ++ 11 files changed, 131 insertions(+), 134 deletions(-) create mode 100644 Obsidian.API/Configuration/MessagesConfiguration.cs create mode 100644 Obsidian.API/Configuration/WhitelistConfiguration.cs diff --git a/Obsidian.API/Configuration/IServerConfiguration.cs b/Obsidian.API/Configuration/IServerConfiguration.cs index abb2dca25..5736a223c 100644 --- a/Obsidian.API/Configuration/IServerConfiguration.cs +++ b/Obsidian.API/Configuration/IServerConfiguration.cs @@ -2,6 +2,8 @@ public interface IServerConfiguration { + public bool ServerShutdownStopsProgram { get; set; } + public bool? Baah { get; set; } /// @@ -58,6 +60,8 @@ public interface IServerConfiguration /// public RconConfiguration? Rcon { get; set; } + public MessagesConfiguration Messages { get; set; } + /// /// The view distance of the server. /// diff --git a/Obsidian.API/Configuration/MessagesConfiguration.cs b/Obsidian.API/Configuration/MessagesConfiguration.cs new file mode 100644 index 000000000..fea7e5d86 --- /dev/null +++ b/Obsidian.API/Configuration/MessagesConfiguration.cs @@ -0,0 +1,15 @@ +namespace Obsidian.API.Configuration; +public sealed record class MessagesConfiguration +{ + public string Join { get; set; } = "&e{0} joined the game"; + + public string Leave { get; set; } = "&e{0} left the game"; + + public string NotWhitelisted { get; set; } = "You are not whitelisted on this server!"; + + public string ServerFull { get; set; } = "The server is full!"; + + public string OutdatedClient { get; set; } = "Outdated client! Please use {0}"; + public string OutdatedServer { get; set; } = "Outdated server! I'm still on {0}"; +} + diff --git a/Obsidian.API/Configuration/WhitelistConfiguration.cs b/Obsidian.API/Configuration/WhitelistConfiguration.cs new file mode 100644 index 000000000..369771e1e --- /dev/null +++ b/Obsidian.API/Configuration/WhitelistConfiguration.cs @@ -0,0 +1,7 @@ +namespace Obsidian.API.Configuration; +public sealed class WhitelistConfiguration +{ + public List WhitelistedPlayers { get; set; } = []; + + public List WhitelistedIps { get; set; } = []; +} diff --git a/Obsidian.API/_Interfaces/IServer.cs b/Obsidian.API/_Interfaces/IServer.cs index 0abfc124c..5bfb7e615 100644 --- a/Obsidian.API/_Interfaces/IServer.cs +++ b/Obsidian.API/_Interfaces/IServer.cs @@ -4,7 +4,7 @@ namespace Obsidian.API; -public interface IServer +public interface IServer : IDisposable { public string Version { get; } public int Port { get; } diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index d1c753815..cbdb24108 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -1,8 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; using Obsidian; -using Obsidian.API.Logging; using Obsidian.Hosting; // Cool startup console logo because that's cool @@ -22,23 +23,17 @@ Console.WriteLine(asciilogo); Console.ResetColor(); -var env = await IServerEnvironment.CreateDefaultAsync(); - var builder = Host.CreateApplicationBuilder(); +builder.ConfigureObsidian(); + builder.Services.AddLogging(loggingBuilder => { loggingBuilder.ClearProviders(); - loggingBuilder.AddProvider(new LoggerProvider(env.Configuration.LogLevel)); - loggingBuilder.SetMinimumLevel(env.Configuration.LogLevel); -}); - -builder.Logging.AddFilter((provider, category, logLevel) => -{ - return !category.Contains("Microsoft") || logLevel != LogLevel.Debug; + loggingBuilder.AddSimpleConsole(x => x.ColorBehavior = LoggerColorBehavior.Enabled); }); -builder.Services.AddObsidian(env); +builder.AddObsidian(); // Give the server some time to shut down after CTRL-C or SIGTERM. builder.Services.Configure(opts => diff --git a/Obsidian/Hosting/DefaultServerEnvironment.cs b/Obsidian/Hosting/DefaultServerEnvironment.cs index 2d4ea7099..2f6d5af5d 100644 --- a/Obsidian/Hosting/DefaultServerEnvironment.cs +++ b/Obsidian/Hosting/DefaultServerEnvironment.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; -using System.Diagnostics; +using Microsoft.Extensions.Options; +using Obsidian.API.Configuration; using System.IO; using System.Threading; @@ -12,17 +13,14 @@ namespace Obsidian.Hosting; /// /// Use the method to create an instance. /// -public sealed class DefaultServerEnvironment : IServerEnvironment +public sealed class DefaultServerEnvironment : IServerEnvironment, IDisposable { - public bool ServerShutdownStopsProgram { get; } = true; - public ServerConfiguration Configuration { get; } - public List ServerWorlds { get; } + public IOptionsMonitor ServerConfig { get; } + public List ServerWorlds { get; } = default!; - private DefaultServerEnvironment(bool serverShutdownStopsProgram, ServerConfiguration configuration, List serverWorlds) + internal DefaultServerEnvironment(IOptionsMonitor serverConfig) { - ServerShutdownStopsProgram = serverShutdownStopsProgram; - Configuration = configuration; - ServerWorlds = serverWorlds; + ServerConfig = serverConfig; } /// @@ -68,51 +66,8 @@ Task IServerEnvironment.OnServerCrashAsync(ILogger logger, Exception e) return Task.CompletedTask; } - /// - /// Create a asynchronously. - /// - /// - public static async Task CreateAsync() - { - var config = await LoadServerConfigurationAsync(); - var worlds = await LoadServerWorldsAsync(); - return new DefaultServerEnvironment(true, config, worlds); - } - private static async Task LoadServerConfigurationAsync() - { - if (!Directory.Exists("config")) - Directory.CreateDirectory("config"); - - var configFile = new FileInfo(Path.Combine("config", "main.json")); - - if (configFile.Exists) - { - await using var configFileStream = configFile.OpenRead(); - return await configFileStream.FromJsonAsync() - ?? throw new Exception("Server config file exists, but is invalid. Is it corrupt?"); - } - - var config = new ServerConfiguration(); - - await using var fileStream = configFile.Create(); - - await config.ToJsonAsync(fileStream); - await fileStream.FlushAsync(); - - Console.WriteLine($"Created new configuration file for Server"); - Console.WriteLine($"Please fill in your config with the values you wish to use for your server."); - Console.WriteLine(configFile.FullName); - - Console.ReadKey(); - Environment.Exit(0); - - throw new UnreachableException(); - } private static async Task> LoadServerWorldsAsync() { - if (!Directory.Exists("config")) - Directory.CreateDirectory("config"); - var worldsFile = new FileInfo(Path.Combine("config", "worlds.json")); if (worldsFile.Exists) @@ -140,6 +95,10 @@ private static async Task> LoadServerWorldsAsync() return worlds; } - + public void Dispose() + { + GC.SuppressFinalize(this); + + } } diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index d35412792..0889a08b8 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -1,37 +1,49 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Obsidian.API.Configuration; using Obsidian.Commands.Framework; using Obsidian.Net.Rcon; using Obsidian.Services; using Obsidian.WorldData; +using System.IO; namespace Obsidian.Hosting; public static class DependencyInjection { - public static IServiceCollection AddObsidian(this IServiceCollection services, IServerEnvironment env) + public static IHostApplicationBuilder ConfigureObsidian(this IHostApplicationBuilder builder) { - services.AddSingleton(env); - services.AddSingleton(env.Configuration); - services.AddSingleton(f => f.GetRequiredService()); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddHttpClient(); - - services.AddHostedService(sp => sp.GetRequiredService()); - services.AddHostedService(); - services.AddHostedService(sp => sp.GetRequiredService()); - - services.AddSingleton(sp => sp.GetRequiredService()); - services.AddSingleton(sp => sp.GetRequiredService()); + builder.Configuration.AddJsonFile(Path.Combine("config", "server.json"), optional: false, reloadOnChange: true); + builder.Configuration.AddJsonFile(Path.Combine("config", "whitelist.json"), optional: false, reloadOnChange: true); + builder.Configuration.AddEnvironmentVariables(); + + return builder; + } + + public static IHostApplicationBuilder AddObsidian(this IHostApplicationBuilder builder) + { + builder.Services.Configure(builder.Configuration); + builder.Services.Configure(builder.Configuration); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + builder.Services.AddHttpClient(); + + builder.Services.AddHostedService(sp => sp.GetRequiredService()); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(sp => sp.GetRequiredService()); + + builder.Services.AddSingleton(sp => sp.GetRequiredService()); + builder.Services.AddSingleton(sp => sp.GetRequiredService()); - return services; + return builder; } } diff --git a/Obsidian/Hosting/IServerEnvironment.cs b/Obsidian/Hosting/IServerEnvironment.cs index 2baa36ac1..bb0d909b5 100644 --- a/Obsidian/Hosting/IServerEnvironment.cs +++ b/Obsidian/Hosting/IServerEnvironment.cs @@ -10,11 +10,6 @@ namespace Obsidian.Hosting; /// public interface IServerEnvironment { - /// - /// If set to true, after the server shuts down, the application will stop running as well. - /// - public bool ServerShutdownStopsProgram { get; } - public ServerConfiguration Configuration { get; } public List ServerWorlds { get; } /// @@ -39,10 +34,4 @@ public interface IServerEnvironment /// public Task OnServerCrashAsync(ILogger logger, Exception e); - /// - /// Create a asynchronously, which is aimed for use in Console Applications. - /// - /// - public static Task CreateDefaultAsync() => DefaultServerEnvironment.CreateAsync(); - } diff --git a/Obsidian/Hosting/ObsidianHostingService.cs b/Obsidian/Hosting/ObsidianHostingService.cs index c6e8b3a33..807354c9d 100644 --- a/Obsidian/Hosting/ObsidianHostingService.cs +++ b/Obsidian/Hosting/ObsidianHostingService.cs @@ -1,26 +1,22 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Obsidian.API.Configuration; using System.Threading; namespace Obsidian.Hosting; -internal sealed class ObsidianHostingService : BackgroundService +internal sealed class ObsidianHostingService( + IHostApplicationLifetime lifetime, + IServer server, + IServerEnvironment env, + ILogger logger, + IOptionsMonitor serverConfiguration) : BackgroundService { - private readonly IHostApplicationLifetime _lifetime; - private readonly IServerEnvironment _environment; - private readonly IServer _server; - private readonly ILogger _logger; - - public ObsidianHostingService( - IHostApplicationLifetime lifetime, - IServer server, - IServerEnvironment env, - ILogger logger) - { - _server = server; - _lifetime = lifetime; - _environment = env; - _logger = logger; - } + private readonly IHostApplicationLifetime _lifetime = lifetime; + private readonly IServerEnvironment _environment = env; + private readonly IServer _server = server; + private readonly ILogger _logger = logger; + private readonly IOptionsMonitor serverConfiguration = serverConfiguration; protected async override Task ExecuteAsync(CancellationToken cToken) { @@ -34,7 +30,7 @@ protected async override Task ExecuteAsync(CancellationToken cToken) await _environment.OnServerCrashAsync(_logger, e); } - if (_environment.ServerShutdownStopsProgram) + if (serverConfiguration.CurrentValue.ServerShutdownStopsProgram) _lifetime.StopApplication(); } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index f0298252f..108356a3f 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Connections; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Obsidian.API.Boss; using Obsidian.API.Builders; using Obsidian.API.Configuration; @@ -14,7 +14,6 @@ using Obsidian.Concurrency; using Obsidian.Entities; using Obsidian.Events; -using Obsidian.Hosting; using Obsidian.Net; using Obsidian.Net.Packets; using Obsidian.Net.Packets.Play.Clientbound; @@ -62,12 +61,14 @@ public static string VERSION private readonly ConcurrentQueue _chatMessagesQueue = new(); private readonly ConcurrentHashSet _clients = new(); + private readonly IOptionsMonitor whitelistConfiguration; private readonly ILoggerFactory loggerFactory; private readonly RconServer _rconServer; private readonly IUserCache userCache; internal readonly ILogger _logger; private readonly IServiceProvider serviceProvider; + private IDisposable? configWatcher; private IConnectionListener? _tcpListener; public ProtocolVersion Protocol => DefaultProtocol; @@ -86,7 +87,7 @@ public static string VERSION public HashSet RegisteredChannels { get; } = new(); public CommandHandler CommandsHandler { get; } - public IServerConfiguration Configuration { get; } + public IServerConfiguration Configuration { get; set; } public string Version => VERSION; public string Brand { get; } = "obsidian"; @@ -94,12 +95,15 @@ public static string VERSION public IWorld DefaultWorld => WorldManager.DefaultWorld; public IEnumerable Players => GetPlayers(); + + /// /// Creates a new instance of . /// public Server( IHostApplicationLifetime lifetime, - IServerEnvironment environment, + IOptionsMonitor configuration, + IOptionsMonitor whitelistConfiguration, ILoggerFactory loggerFactory, IWorldManager worldManager, RconServer rconServer, @@ -108,7 +112,6 @@ public Server( CommandHandler commandHandler, IServiceProvider serviceProvider) { - Configuration = environment.Configuration; _logger = loggerFactory.CreateLogger(); _logger.LogInformation("SHA / Version: {VERSION}", VERSION); _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(lifetime.ApplicationStopping); @@ -116,8 +119,12 @@ public Server( _rconServer = rconServer; this.serviceProvider = serviceProvider; + this.configWatcher = configuration.OnChange(this.ConfigChanged); + + var config = configuration.CurrentValue; - Port = Configuration.Port; + Configuration = config; + Port = config.Port; Operators = new OperatorList(this, loggerFactory); ScoreboardManager = new ScoreboardManager(this, loggerFactory); @@ -137,13 +144,15 @@ public Server( this.userCache = playerCache; this.EventDispatcher = eventDispatcher; + this.whitelistConfiguration = whitelistConfiguration; this.loggerFactory = loggerFactory; this.WorldManager = worldManager; Directory.CreateDirectory(PermissionPath); Directory.CreateDirectory(PersistentDataPath); - if (Configuration.AllowLan) + //TODO turn this into a hosted service + if (config.AllowLan) { _ = Task.Run(async () => { @@ -153,10 +162,10 @@ public Server( byte[] bytes = []; // Cached motd as utf-8 bytes while (await timer.WaitForNextTickAsync(_cancelTokenSource.Token)) { - if (Configuration.Motd != lastMotd) + if (config.Motd != lastMotd) { - lastMotd = Configuration.Motd; - bytes = Encoding.UTF8.GetBytes($"[MOTD]{Configuration.Motd.Replace('[', '(').Replace(']', ')')}[/MOTD][AD]{Configuration.Port}[/AD]"); + lastMotd = config.Motd; + bytes = Encoding.UTF8.GetBytes($"[MOTD]{config.Motd.Replace('[', '(').Replace(']', ')')}[/MOTD][AD]{config.Port}[/AD]"); } await udpClient.SendAsync(bytes, bytes.Length); } @@ -164,6 +173,8 @@ public Server( } } + private void ConfigChanged(IServerConfiguration configuration) => this.Configuration = configuration; + // TODO make sure to re-send recipes public void RegisterRecipes(params IRecipe[] recipes) { @@ -235,7 +246,7 @@ public async Task RunAsync() var loadTimeStopwatch = Stopwatch.StartNew(); // Check if MPDM and OM are enabled, if so, we can't handle connections - if (Configuration.MulitplayerDebugMode && Configuration.OnlineMode) + if (Configuration.Network.MulitplayerDebugMode && Configuration.OnlineMode) { _logger.LogError("Incompatible Config: Multiplayer debug mode can't be enabled at the same time as online mode since usernames will be overwritten"); await StopAsync(); @@ -260,8 +271,6 @@ public async Task RunAsync() PluginManager.DirectoryWatcher.Filters = new[] { ".cs", ".dll" }; PluginManager.DirectoryWatcher.Watch("plugins"); - await Task.WhenAll(Configuration.DownloadPlugins.Select(path => PluginManager.LoadPluginAsync(path))); - if (!Configuration.OnlineMode) _logger.LogInformation("Starting in offline mode..."); @@ -356,14 +365,14 @@ private async Task AcceptClientsAsync() string ip = ((IPEndPoint)connection.RemoteEndPoint!).Address.ToString(); - if (Configuration.IpWhitelistEnabled && !Configuration.WhitelistedIPs.Contains(ip)) + if (Configuration.Whitelist && !whitelistConfiguration.CurrentValue.WhitelistedIps.Contains(ip)) { _logger.LogInformation("{ip} is not whitelisted. Closing connection", ip); connection.Abort(); return; } - if (this.Configuration.ShouldThrottle) + if (this.Configuration.Network.ShouldThrottle) { if (throttler.TryGetValue(ip, out var time) && time <= DateTimeOffset.UtcNow) { @@ -531,7 +540,7 @@ private async Task LoopAsync() while (await timer.WaitForNextTickAsync()) { keepAliveTicks++; - if (keepAliveTicks > (Configuration.KeepAliveInterval / 50)) // to clarify: one tick is 50 milliseconds. 50 * 200 = 10000 millis means 10 seconds + if (keepAliveTicks > (Configuration.Network.KeepAliveInterval / 50)) // to clarify: one tick is 50 milliseconds. 50 * 200 = 10000 millis means 10 seconds { var keepAliveTime = DateTimeOffset.Now; @@ -589,4 +598,11 @@ internal void UpdateStatusConsole() var status = $" tps:{Tps} c:{WorldManager.GeneratingChunkCount}/{WorldManager.LoadedChunkCount} r:{WorldManager.RegionCount}"; ConsoleIO.UpdateStatusLine(status); } + + public void Dispose() + { + GC.SuppressFinalize(this); + + this.configWatcher?.Dispose(); + } } diff --git a/Obsidian/Utilities/ServerConfiguration.cs b/Obsidian/Utilities/ServerConfiguration.cs index 83d20ea31..9b4afbefd 100644 --- a/Obsidian/Utilities/ServerConfiguration.cs +++ b/Obsidian/Utilities/ServerConfiguration.cs @@ -19,6 +19,8 @@ public sealed class ServerConfiguration : IServerConfiguration public bool AllowOperatorRequests { get; set; } = true; + public bool ServerShutdownStopsProgram { get; set; } = true; + public bool? Baah { get; set; } public bool Whitelist { get; set; } @@ -27,6 +29,8 @@ public sealed class ServerConfiguration : IServerConfiguration public RconConfiguration? Rcon { get; set; } + public MessagesConfiguration Messages { get; set; } = new(); + public bool AllowLan { get; set; } = true; // Enabled because it's super useful for debugging tbh public byte ViewDistance From fe299311aa13b0a2ef1f4fec9f2436dad794e95f Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 19:37:39 -0400 Subject: [PATCH 03/13] Add default config files --- .../Obsidian.ConsoleApp.csproj | 37 ++++++++++--------- Obsidian.ConsoleApp/config/server.json | 30 +++++++++++++++ Obsidian.ConsoleApp/config/whitelist.json | 5 +++ 3 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 Obsidian.ConsoleApp/config/server.json create mode 100644 Obsidian.ConsoleApp/config/whitelist.json diff --git a/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj b/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj index b3ccf86ef..1f7f92fc8 100644 --- a/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj +++ b/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj @@ -1,24 +1,25 @@  - - Exe - net8.0 - disable - enable - true - en - + + Exe + net8.0 + disable + enable + true + en + - - - + + + - - - - - - - + + + + + + PreserveNewest + + diff --git a/Obsidian.ConsoleApp/config/server.json b/Obsidian.ConsoleApp/config/server.json new file mode 100644 index 000000000..d018e0968 --- /dev/null +++ b/Obsidian.ConsoleApp/config/server.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://api.tides.cc/files/obsidian-schemas/server.json", + + "allowLan": true, + "allowOperatorRequests": true, + + "maxPlayers": 25, + "motd": "�k||||�r �5Obsidian �cPre�r-�cRelease �r�k||||�r \n�r�lRunning on .NET �l�c8 �r�l<3", + + "onlineMode": true, + "port": 25565, + "pregenerateChunkRange": 15, + "serverListQuery": "Full", + "timeTickSpeedMultiplier": 1, + "whitelist": false, + + "network": { + "connectionThrottle": 15000, + "keepAliveInterval": 10000, + "keepAliveTimeoutInterval": 30000, + "mulitplayerDebugMode": false + }, + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning" + } + } +} \ No newline at end of file diff --git a/Obsidian.ConsoleApp/config/whitelist.json b/Obsidian.ConsoleApp/config/whitelist.json new file mode 100644 index 000000000..5c0805ab8 --- /dev/null +++ b/Obsidian.ConsoleApp/config/whitelist.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://api.tides.cc/files/obsidian-schemas/whitelist.json", + "whitelistedPlayers": [], + "whitelistedIps": [] +} \ No newline at end of file From 6291003f2ac1b0c7b710e0b990f0e765ab782dd4 Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 19:57:39 -0400 Subject: [PATCH 04/13] Configuration revamp pt 2 --- Obsidian/Client.cs | 21 +++++---------- Obsidian/Events/MainEventHandler.cs | 4 +-- Obsidian/Net/ClientHandler.cs | 35 ++++++++++++------------- Obsidian/Plugins/PluginManager.cs | 18 +++++++------ Obsidian/Server.cs | 11 +++++--- Obsidian/WorldData/WorldManager.cs | 40 ++++++++++++++++------------- 6 files changed, 63 insertions(+), 66 deletions(-) diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index f85c513b3..8e5fc9cd5 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -270,7 +270,7 @@ public async Task StartConnectionAsync() { case 0x00: { - if (this.server.Configuration.ShouldThrottle) + if (this.server.Configuration.Network.ShouldThrottle) { string ip = ((IPEndPoint)connectionContext.RemoteEndPoint!).Address.ToString(); @@ -285,7 +285,7 @@ public async Task StartConnectionAsync() } else { - Server.throttler.TryAdd(ip, DateTimeOffset.UtcNow.AddMilliseconds(this.server.Configuration.ConnectionThrottle)); + Server.throttler.TryAdd(ip, DateTimeOffset.UtcNow.AddMilliseconds(this.server.Configuration.Network.ConnectionThrottle)); } } @@ -411,7 +411,7 @@ private async Task HandleHandshakeAsync(byte[] data) private async Task HandleLoginStartAsync(byte[] data) { var loginStart = LoginStart.Deserialize(data); - var username = this.server.Configuration.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; + var username = this.server.Configuration.Network.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; var world = (World)this.server.DefaultWorld; Logger.LogDebug("Received login request from user {Username}", username); @@ -426,7 +426,7 @@ private async Task HandleLoginStartAsync(byte[] data) await DisconnectAsync("Account not found in the Mojang database"); return; } - else if (this.server.Configuration.WhitelistEnabled && !this.server.Configuration.Whitelisted.Any(x => x.Id == cachedUser.Uuid)) + else if (this.server.Configuration.Whitelist && !this.server.WhitelistConfiguration.CurrentValue.WhitelistedPlayers.Any(x => x.Id == cachedUser.Uuid)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); return; @@ -445,7 +445,7 @@ private async Task HandleLoginStartAsync(byte[] data) VerifyToken = randomToken }); } - else if (this.server.Configuration.WhitelistEnabled && !this.server.Configuration.Whitelisted.Any(x => x.Name == username)) + else if (this.server.Configuration.Whitelist && !this.server.WhitelistConfiguration.CurrentValue.WhitelistedPlayers.Any(x => x.Name == username)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); } @@ -551,7 +551,6 @@ await QueuePacketAsync(new UpdateRecipeBookPacket SecondRecipeIds = RecipesRegistry.Recipes.Keys.ToList() }); - await SendPlayerListDecoration(); await SendPlayerInfoAsync(); await this.QueuePacketAsync(new GameEventPacket(ChangeGameStateReason.StartWaitingForLevelChunks)); @@ -621,7 +620,7 @@ internal void SendKeepAlive(DateTimeOffset time) { long keepAliveId = time.ToUnixTimeMilliseconds(); // first, check if there's any KeepAlives that are older than 30 seconds - if (missedKeepAlives.Any(x => keepAliveId - x > this.server.Configuration.KeepAliveTimeoutInterval)) + if (missedKeepAlives.Any(x => keepAliveId - x > this.server.Configuration.Network.KeepAliveTimeoutInterval)) { // kick player, failed to respond within 30s cancellationSource.Cancel(); @@ -772,14 +771,6 @@ private async Task SendServerBrand() Logger.LogDebug("Sent server brand."); } - private async Task SendPlayerListDecoration() - { - var header = string.IsNullOrWhiteSpace(this.server.Configuration.Header) ? null : ChatMessage.Simple(this.server.Configuration.Header); - var footer = string.IsNullOrWhiteSpace(this.server.Configuration.Footer) ? null : ChatMessage.Simple(this.server.Configuration.Footer); - - await QueuePacketAsync(new SetTabListHeaderAndFooterPacket(header, footer)); - Logger.LogDebug("Sent player list decoration"); - } #endregion Packet sending internal void Disconnect() diff --git a/Obsidian/Events/MainEventHandler.cs b/Obsidian/Events/MainEventHandler.cs index e5389dcd4..d093eef1a 100644 --- a/Obsidian/Events/MainEventHandler.cs +++ b/Obsidian/Events/MainEventHandler.cs @@ -340,7 +340,7 @@ public async Task OnPlayerLeave(PlayerLeaveEventArgs e) await other.client.QueuePacketAsync(destroy); } - server.BroadcastMessage(string.Format(server.Configuration.LeaveMessage, e.Player.Username)); + server.BroadcastMessage(string.Format(server.Configuration.Messages.Leave, e.Player.Username)); } [EventPriority(Priority = Priority.Internal)] @@ -353,7 +353,7 @@ public async Task OnPlayerJoin(PlayerJoinEventArgs e) server.BroadcastMessage(new ChatMessage { - Text = string.Format(server.Configuration.JoinMessage, e.Player.Username), + Text = string.Format(server.Configuration.Messages.Join, e.Player.Username), Color = HexColor.Yellow }); diff --git a/Obsidian/Net/ClientHandler.cs b/Obsidian/Net/ClientHandler.cs index 4bfe97a52..fdda6243e 100644 --- a/Obsidian/Net/ClientHandler.cs +++ b/Obsidian/Net/ClientHandler.cs @@ -98,23 +98,22 @@ public async Task HandleConfigurationPackets(int id, byte[] data, Client client) await HandleFromPoolAsync(data, client); break; default: - { - if (!Packets.TryGetValue(id, out var packet)) - return; - - try - { - packet.Populate(data); - await packet.HandleAsync(client.server, client.Player); - } - catch (Exception e) { - if (config.VerboseExceptionLogging) - _logger.LogError(e, e.Message); + if (!Packets.TryGetValue(id, out var packet)) + return; + + try + { + packet.Populate(data); + await packet.HandleAsync(client.server, client.Player); + } + catch (Exception e) + { + _logger.LogCritical(e, "{exceptionMessage}", e.Message); + } + + break; } - - break; - } } } @@ -215,8 +214,7 @@ public async Task HandlePlayPackets(int id, byte[] data, Client client) } catch (Exception e) { - if (config.VerboseExceptionLogging) - _logger.LogError(e, e.Message); + _logger.LogCritical(e, "{exceptionMessage}", e.Message); } break; } @@ -232,8 +230,7 @@ public async Task HandlePlayPackets(int id, byte[] data, Client client) } catch (Exception e) { - if (client.server.Configuration.VerboseExceptionLogging) - _logger.LogError(e, "{message}", e.Message); + _logger.LogCritical(e, "{exceptionMessage}", e.Message); } ObjectPool.Shared.Return(packet); } diff --git a/Obsidian/Plugins/PluginManager.cs b/Obsidian/Plugins/PluginManager.cs index 1800f75a3..2264d76b1 100644 --- a/Obsidian/Plugins/PluginManager.cs +++ b/Obsidian/Plugins/PluginManager.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Obsidian.API.Configuration; using Obsidian.API.Logging; using Obsidian.API.Plugins; @@ -18,7 +20,7 @@ public sealed class PluginManager private const string loadEvent = "OnLoad"; internal readonly ILogger logger; - + private readonly IConfiguration configuration; private readonly List plugins = new(); private readonly List stagedPlugins = new(); private readonly IServiceProvider serverProvider; @@ -47,13 +49,14 @@ public sealed class PluginManager public IServiceProvider PluginServiceProvider { get; private set; } = default!; public PluginManager(IServiceProvider serverProvider, IServer server, - EventDispatcher eventDispatcher, CommandHandler commandHandler, ILogger logger) + EventDispatcher eventDispatcher, CommandHandler commandHandler, ILogger logger, IConfiguration configuration) { var env = serverProvider.GetRequiredService(); this.server = server; this.commandHandler = commandHandler; this.logger = logger; + this.configuration = configuration; this.serverProvider = serverProvider; this.pluginRegistry = new PluginRegistry(this, eventDispatcher, commandHandler, logger); @@ -61,7 +64,7 @@ public PluginManager(IServiceProvider serverProvider, IServer server, PluginProviderSelector.UncompiledPluginProvider = new UncompiledPluginProvider(logger); PluginProviderSelector.CompiledPluginProvider = new CompiledPluginProvider(logger); - ConfigureInitialServices(env); + ConfigureInitialServices(); DirectoryWatcher.FileChanged += async (path) => { @@ -77,15 +80,14 @@ public PluginManager(IServiceProvider serverProvider, IServer server, DirectoryWatcher.FileDeleted += OnPluginSourceDeleted; } - private void ConfigureInitialServices(IServerEnvironment env) + private void ConfigureInitialServices() { this.pluginServiceDescriptors.AddLogging((builder) => { builder.ClearProviders(); - builder.AddProvider(new LoggerProvider(env.Configuration.LogLevel)); - builder.SetMinimumLevel(env.Configuration.LogLevel); + builder.AddConfiguration(this.configuration); }); - this.pluginServiceDescriptors.AddSingleton(x => env.Configuration); + this.pluginServiceDescriptors.AddSingleton(serverProvider.GetRequiredService>()); } /// diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 108356a3f..0a4be2ec6 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Connections; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -61,7 +63,6 @@ public static string VERSION private readonly ConcurrentQueue _chatMessagesQueue = new(); private readonly ConcurrentHashSet _clients = new(); - private readonly IOptionsMonitor whitelistConfiguration; private readonly ILoggerFactory loggerFactory; private readonly RconServer _rconServer; private readonly IUserCache userCache; @@ -71,6 +72,8 @@ public static string VERSION private IDisposable? configWatcher; private IConnectionListener? _tcpListener; + public IOptionsMonitor WhitelistConfiguration { get; } + public ProtocolVersion Protocol => DefaultProtocol; public int Tps { get; private set; } @@ -133,7 +136,7 @@ public Server( CommandsHandler = commandHandler; - PluginManager = new PluginManager(this.serviceProvider, this, eventDispatcher, CommandsHandler, _logger); + PluginManager = new PluginManager(this.serviceProvider, this, eventDispatcher, CommandsHandler, _logger, serviceProvider.GetRequiredService()); _logger.LogDebug("Registering commands..."); CommandsHandler.RegisterCommandClass(null); @@ -144,7 +147,7 @@ public Server( this.userCache = playerCache; this.EventDispatcher = eventDispatcher; - this.whitelistConfiguration = whitelistConfiguration; + this.WhitelistConfiguration = whitelistConfiguration; this.loggerFactory = loggerFactory; this.WorldManager = worldManager; @@ -365,7 +368,7 @@ private async Task AcceptClientsAsync() string ip = ((IPEndPoint)connection.RemoteEndPoint!).Address.ToString(); - if (Configuration.Whitelist && !whitelistConfiguration.CurrentValue.WhitelistedIps.Contains(ip)) + if (Configuration.Whitelist && !WhitelistConfiguration.CurrentValue.WhitelistedIps.Contains(ip)) { _logger.LogInformation("{ip} is not whitelisted. Closing connection", ip); connection.Abort(); diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 0cc80e00a..05306976e 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -1,24 +1,30 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Obsidian.API.Configuration; using Obsidian.Hosting; using Obsidian.Registries; using Obsidian.Services; using Obsidian.WorldData.Generators; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text.Json; using System.Threading; namespace Obsidian.WorldData; -public sealed class WorldManager : BackgroundService, IWorldManager +public sealed class WorldManager(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptionsMonitor configuration, + IServerEnvironment serverEnvironment) : BackgroundService, IWorldManager { - private readonly ILogger logger; + private readonly ILogger logger = loggerFactory.CreateLogger(); private readonly Dictionary worlds = new(); - private readonly List serverWorlds; - private readonly ILoggerFactory loggerFactory; - private readonly IServerEnvironment serverEnvironment; - private readonly IServiceScope serviceScope; + private readonly ILoggerFactory loggerFactory = loggerFactory; + private readonly IServiceProvider serviceProvider = serviceProvider; + private readonly IOptionsMonitor configuration = configuration; + private readonly IServerEnvironment serverEnvironment = serverEnvironment; + private readonly IServiceScope serviceScope = serviceProvider.CreateScope(); public bool ReadyToJoin { get; private set; } @@ -30,15 +36,6 @@ public sealed class WorldManager : BackgroundService, IWorldManager public Dictionary WorldGenerators { get; } = new(); - public WorldManager(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IServerEnvironment serverEnvironment) - { - this.logger = loggerFactory.CreateLogger(); - this.serverWorlds = serverEnvironment.ServerWorlds; - this.loggerFactory = loggerFactory; - this.serverEnvironment = serverEnvironment; - this.serviceScope = serviceProvider.CreateScope(); - } - protected async override Task ExecuteAsync(CancellationToken stoppingToken) { var timer = new BalancingTimer(20, stoppingToken); @@ -59,7 +56,7 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken) } catch (Exception ex) when (ex is not OperationCanceledException) { - await this.serverEnvironment.OnServerCrashAsync(this.logger, ex); + await this.serverEnvironment.OnServerCrashAsync(ex); } } @@ -80,7 +77,14 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken) public async Task LoadWorldsAsync() { - foreach (var serverWorld in this.serverWorlds) + var worlds = new List(); + + await using (var fs = new FileStream(Path.Combine("config", "worlds.json"), FileMode.Open)) + { + worlds = await JsonSerializer.DeserializeAsync>(fs, Globals.JsonOptions) ?? []; + } + + foreach (var serverWorld in worlds) { //var server = (Server)this.server; if (!this.WorldGenerators.TryGetValue(serverWorld.Generator, out var generatorType)) @@ -92,7 +96,7 @@ public async Task LoadWorldsAsync() //TODO fix var world = new World(this.loggerFactory.CreateLogger($"World [{serverWorld.Name}]"), generatorType, this) { - Configuration = this.serverEnvironment.Configuration, + Configuration = this.configuration.CurrentValue, PacketBroadcaster = this.serviceScope.ServiceProvider.GetRequiredService(), Name = serverWorld.Name, Seed = serverWorld.Seed From c79591ccc14174ebf900b7f59197898d620b348a Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 19:57:52 -0400 Subject: [PATCH 05/13] Refactor IServerEnviroment --- Obsidian/Hosting/DefaultServerEnvironment.cs | 9 ++++++--- Obsidian/Hosting/IServerEnvironment.cs | 4 ++-- Obsidian/Hosting/ObsidianHostingService.cs | 6 ++---- Obsidian/Services/PacketBroadcaster.cs | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Obsidian/Hosting/DefaultServerEnvironment.cs b/Obsidian/Hosting/DefaultServerEnvironment.cs index 2f6d5af5d..3343cf6eb 100644 --- a/Obsidian/Hosting/DefaultServerEnvironment.cs +++ b/Obsidian/Hosting/DefaultServerEnvironment.cs @@ -15,12 +15,15 @@ namespace Obsidian.Hosting; /// public sealed class DefaultServerEnvironment : IServerEnvironment, IDisposable { + private readonly ILogger logger; + public IOptionsMonitor ServerConfig { get; } public List ServerWorlds { get; } = default!; - internal DefaultServerEnvironment(IOptionsMonitor serverConfig) + internal DefaultServerEnvironment(IOptionsMonitor serverConfig, ILogger logger) { ServerConfig = serverConfig; + this.logger = logger; } /// @@ -39,12 +42,12 @@ public async Task ProvideServerCommandsAsync(Server server, CancellationToken cT } } - Task IServerEnvironment.OnServerStoppedGracefullyAsync(ILogger logger) + Task IServerEnvironment.OnServerStoppedGracefullyAsync() { logger.LogInformation("Goodbye!"); return Task.CompletedTask; } - Task IServerEnvironment.OnServerCrashAsync(ILogger logger, Exception e) + Task IServerEnvironment.OnServerCrashAsync(Exception e) { // Write crash log somewhere? var byeMessages = new[] diff --git a/Obsidian/Hosting/IServerEnvironment.cs b/Obsidian/Hosting/IServerEnvironment.cs index bb0d909b5..b97b213f5 100644 --- a/Obsidian/Hosting/IServerEnvironment.cs +++ b/Obsidian/Hosting/IServerEnvironment.cs @@ -24,7 +24,7 @@ public interface IServerEnvironment /// /// /// - public Task OnServerStoppedGracefullyAsync(ILogger logger); + public Task OnServerStoppedGracefullyAsync(); /// /// Called when the server stopped due to a crash. @@ -32,6 +32,6 @@ public interface IServerEnvironment /// /// /// - public Task OnServerCrashAsync(ILogger logger, Exception e); + public Task OnServerCrashAsync(Exception e); } diff --git a/Obsidian/Hosting/ObsidianHostingService.cs b/Obsidian/Hosting/ObsidianHostingService.cs index 807354c9d..6811756b2 100644 --- a/Obsidian/Hosting/ObsidianHostingService.cs +++ b/Obsidian/Hosting/ObsidianHostingService.cs @@ -9,13 +9,11 @@ internal sealed class ObsidianHostingService( IHostApplicationLifetime lifetime, IServer server, IServerEnvironment env, - ILogger logger, IOptionsMonitor serverConfiguration) : BackgroundService { private readonly IHostApplicationLifetime _lifetime = lifetime; private readonly IServerEnvironment _environment = env; private readonly IServer _server = server; - private readonly ILogger _logger = logger; private readonly IOptionsMonitor serverConfiguration = serverConfiguration; protected async override Task ExecuteAsync(CancellationToken cToken) @@ -23,11 +21,11 @@ protected async override Task ExecuteAsync(CancellationToken cToken) try { await _server.RunAsync(); - await _environment.OnServerStoppedGracefullyAsync(_logger); + await _environment.OnServerStoppedGracefullyAsync(); } catch (Exception e) { - await _environment.OnServerCrashAsync(_logger, e); + await _environment.OnServerCrashAsync(e); } if (serverConfiguration.CurrentValue.ServerShutdownStopsProgram) diff --git a/Obsidian/Services/PacketBroadcaster.cs b/Obsidian/Services/PacketBroadcaster.cs index dd35faff8..f3ed6dca8 100644 --- a/Obsidian/Services/PacketBroadcaster.cs +++ b/Obsidian/Services/PacketBroadcaster.cs @@ -73,7 +73,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } catch (Exception e) when (e is not OperationCanceledException) { - await this.environment.OnServerCrashAsync(this.logger, e); + await this.environment.OnServerCrashAsync(e); } } From f8315013ffe49c5c01c3e9fa0771f8c0d8c45c27 Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 19:59:20 -0400 Subject: [PATCH 06/13] Move this --- Obsidian/Hosting/DefaultServerEnvironment.cs | 29 ---------------- Obsidian/WorldData/WorldManager.cs | 36 ++++++++++++++++---- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/Obsidian/Hosting/DefaultServerEnvironment.cs b/Obsidian/Hosting/DefaultServerEnvironment.cs index 3343cf6eb..e61b956bc 100644 --- a/Obsidian/Hosting/DefaultServerEnvironment.cs +++ b/Obsidian/Hosting/DefaultServerEnvironment.cs @@ -69,35 +69,6 @@ Task IServerEnvironment.OnServerCrashAsync(Exception e) return Task.CompletedTask; } - private static async Task> LoadServerWorldsAsync() - { - var worldsFile = new FileInfo(Path.Combine("config", "worlds.json")); - - if (worldsFile.Exists) - { - await using var worldsFileStream = worldsFile.OpenRead(); - return await worldsFileStream.FromJsonAsync>() - ?? throw new Exception("A worlds file does exist, but is invalid. Is it corrupt?"); - } - - var worlds = new List() - { - new() - { - ChildDimensions = - { - "minecraft:the_nether", - "minecraft:the_end" - } - } - }; - - await using var fileStream = worldsFile.Create(); - await worlds.ToJsonAsync(fileStream); - - return worlds; - } - public void Dispose() { GC.SuppressFinalize(this); diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 05306976e..6701892f8 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -77,12 +77,7 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken) public async Task LoadWorldsAsync() { - var worlds = new List(); - - await using (var fs = new FileStream(Path.Combine("config", "worlds.json"), FileMode.Open)) - { - worlds = await JsonSerializer.DeserializeAsync>(fs, Globals.JsonOptions) ?? []; - } + var worlds = await LoadServerWorldsAsync(); foreach (var serverWorld in worlds) { @@ -185,4 +180,33 @@ private void RegisterDefaults() this.RegisterGenerator(); this.RegisterGenerator(); } + + private static async Task> LoadServerWorldsAsync() + { + var worldsFile = new FileInfo(Path.Combine("config", "worlds.json")); + + if (worldsFile.Exists) + { + await using var worldsFileStream = worldsFile.OpenRead(); + return await worldsFileStream.FromJsonAsync>() + ?? throw new Exception("A worlds file does exist, but is invalid. Is it corrupt?"); + } + + var worlds = new List() + { + new() + { + ChildDimensions = + { + "minecraft:the_nether", + "minecraft:the_end" + } + } + }; + + await using var fileStream = worldsFile.Create(); + await worlds.ToJsonAsync(fileStream); + + return worlds; + } } From 17272da4ce3e268b47783e9f1ba767ffed5bee0a Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 20:04:43 -0400 Subject: [PATCH 07/13] Fix DI errors --- Obsidian/Hosting/DefaultServerEnvironment.cs | 14 +++----------- Obsidian/Hosting/IServerEnvironment.cs | 2 -- Obsidian/Net/Rcon/RconServer.cs | 11 ++++++----- Obsidian/WorldData/WorldManager.cs | 1 - 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Obsidian/Hosting/DefaultServerEnvironment.cs b/Obsidian/Hosting/DefaultServerEnvironment.cs index e61b956bc..c54ea8abf 100644 --- a/Obsidian/Hosting/DefaultServerEnvironment.cs +++ b/Obsidian/Hosting/DefaultServerEnvironment.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Obsidian.API.Configuration; -using System.IO; using System.Threading; namespace Obsidian.Hosting; @@ -13,18 +12,11 @@ namespace Obsidian.Hosting; /// /// Use the method to create an instance. /// -public sealed class DefaultServerEnvironment : IServerEnvironment, IDisposable +internal sealed class DefaultServerEnvironment(IOptionsMonitor serverConfig, ILogger logger) : IServerEnvironment, IDisposable { - private readonly ILogger logger; + private readonly ILogger logger = logger; - public IOptionsMonitor ServerConfig { get; } - public List ServerWorlds { get; } = default!; - - internal DefaultServerEnvironment(IOptionsMonitor serverConfig, ILogger logger) - { - ServerConfig = serverConfig; - this.logger = logger; - } + public IOptionsMonitor ServerConfig { get; } = serverConfig; /// /// Provide server commands using the Console. diff --git a/Obsidian/Hosting/IServerEnvironment.cs b/Obsidian/Hosting/IServerEnvironment.cs index b97b213f5..62a8fb266 100644 --- a/Obsidian/Hosting/IServerEnvironment.cs +++ b/Obsidian/Hosting/IServerEnvironment.cs @@ -10,8 +10,6 @@ namespace Obsidian.Hosting; /// public interface IServerEnvironment { - public List ServerWorlds { get; } - /// /// Execute commands on the server. This task will run for the lifetime of the server. /// diff --git a/Obsidian/Net/Rcon/RconServer.cs b/Obsidian/Net/Rcon/RconServer.cs index 2295b6760..41ac32491 100644 --- a/Obsidian/Net/Rcon/RconServer.cs +++ b/Obsidian/Net/Rcon/RconServer.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Obsidian.API.Configuration; using Obsidian.Commands.Framework; using Org.BouncyCastle.Crypto; @@ -16,11 +17,11 @@ public sealed class RconServer private const int CERTAINTY = 5; private readonly ILogger _logger; - private readonly IServerConfiguration _config; + private readonly IOptions _config; private readonly CommandHandler _cmdHandler; private readonly List _connections; - public RconServer(ILogger logger, IServerConfiguration config, CommandHandler commandHandler) + public RconServer(ILogger logger, IOptions config, CommandHandler commandHandler) { _logger = logger; _config = config; @@ -34,7 +35,7 @@ public async Task RunAsync(Server server, CancellationToken cToken) var data = GenerateKeys(server); _logger.LogInformation("Done generating keys for RCON"); - var tcpListener = TcpListener.Create(_config.Rcon?.Port ?? 25575); + var tcpListener = TcpListener.Create(_config.Value.Rcon?.Port ?? 25575); _ = Task.Run(async () => { @@ -72,7 +73,7 @@ public async Task RunAsync(Server server, CancellationToken cToken) private InitData GenerateKeys(Server server) { - string password = _config.Rcon?.Password ?? + string password = _config.Value.Rcon?.Password ?? throw new InvalidOperationException("You can't start a RconServer without setting a password in the configuration."); @@ -88,7 +89,7 @@ private InitData GenerateKeys(Server server) return new InitData(server, _cmdHandler, password, - _config.Rcon.RequireEncryption, + _config.Value.Rcon.RequireEncryption, dhParameters, keyPair); } diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 6701892f8..803196b61 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -10,7 +10,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Text.Json; using System.Threading; namespace Obsidian.WorldData; From 7ff51b16c1633695b71f1a0f3deab2e4d1521cbb Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 20:13:36 -0400 Subject: [PATCH 08/13] Remove IServerConfiguration --- .../Configuration/IServerConfiguration.cs | 72 ------------------- .../Configuration}/ServerConfiguration.cs | 49 ++++++++++++- Obsidian.API/_Interfaces/IServer.cs | 2 +- Obsidian/Hosting/DefaultServerEnvironment.cs | 4 +- Obsidian/Hosting/DependencyInjection.cs | 2 +- Obsidian/Hosting/ObsidianHostingService.cs | 5 +- Obsidian/Net/ClientHandler.cs | 4 +- Obsidian/Net/Rcon/RconServer.cs | 18 ++--- Obsidian/Plugins/PluginManager.cs | 3 +- Obsidian/Server.cs | 6 +- Obsidian/Utilities/ServerStatus.cs | 6 +- Obsidian/WorldData/World.cs | 2 +- Obsidian/WorldData/WorldManager.cs | 4 +- 13 files changed, 68 insertions(+), 109 deletions(-) delete mode 100644 Obsidian.API/Configuration/IServerConfiguration.cs rename {Obsidian/Utilities => Obsidian.API/Configuration}/ServerConfiguration.cs (56%) diff --git a/Obsidian.API/Configuration/IServerConfiguration.cs b/Obsidian.API/Configuration/IServerConfiguration.cs deleted file mode 100644 index 5736a223c..000000000 --- a/Obsidian.API/Configuration/IServerConfiguration.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace Obsidian.API.Configuration; - -public interface IServerConfiguration -{ - public bool ServerShutdownStopsProgram { get; set; } - - public bool? Baah { get; set; } - - /// - /// Allows the server to advertise itself as a LAN server to devices on your network. - /// - public bool AllowLan { get; set; } - - /// - /// Server description. - /// - public string Motd { get; set; } - - /// - /// The port on which to listen for incoming connection attempts. - /// - public int Port { get; set; } - - /// - /// Whether the server uses MojangAPI for loading skins etc. - /// - public bool OnlineMode { get; set; } - - /// - /// Maximum amount of players that is allowed to be connected at the same time. - /// - public int MaxPlayers { get; set; } - public int PregenerateChunkRange { get; set; } - - /// - /// The speed at which world time & rain time go by. - /// - public int TimeTickSpeedMultiplier { get; set; } - - /// - /// Allow people to requests to become an operator. - /// - public bool AllowOperatorRequests { get; set; } - - /// - /// Enabled Remote Console operation. - /// - /// See more at https://wiki.vg/RCON - public bool EnableRcon => Rcon is not null; - - public bool Whitelist { get; set; } - - /// - /// Network Configuration - /// - public NetworkConfiguration Network { get; set; } - - /// - /// Remote Console configuration - /// - public RconConfiguration? Rcon { get; set; } - - public MessagesConfiguration Messages { get; set; } - - /// - /// The view distance of the server. - /// - /// - /// Players with higher view distance will use the server's view distance. - /// - public byte ViewDistance { get; set; } -} diff --git a/Obsidian/Utilities/ServerConfiguration.cs b/Obsidian.API/Configuration/ServerConfiguration.cs similarity index 56% rename from Obsidian/Utilities/ServerConfiguration.cs rename to Obsidian.API/Configuration/ServerConfiguration.cs index 9b4afbefd..3855b192f 100644 --- a/Obsidian/Utilities/ServerConfiguration.cs +++ b/Obsidian.API/Configuration/ServerConfiguration.cs @@ -1,22 +1,44 @@ using Obsidian.API.Configuration; using System.Text.Json.Serialization; -namespace Obsidian.Utilities; +namespace Obsidian.API.Configuration; -public sealed class ServerConfiguration : IServerConfiguration +public sealed class ServerConfiguration { private byte viewDistance = 10; // Anything lower than 3 will cause weird artifacts on the client. private const byte MinimumViewDistance = 3; + /// + /// Enabled Remote Console operation. + /// + /// See more at https://wiki.vg/RCON + public bool EnableRcon => Rcon is not null; + + /// + /// Server description. + /// public string Motd { get; set; } = $"§k||||§r §5Obsidian §cPre§r-§cRelease §r§k||||§r \n§r§lRunning on .NET §l§c{Environment.Version} §r§l<3"; + /// + /// The port on which to listen for incoming connection attempts. + /// public int Port { get; set; } = 25565; + /// + /// Whether the server uses MojangAPI for loading skins etc. + /// public bool OnlineMode { get; set; } = true; + + /// + /// Maximum amount of players that is allowed to be connected at the same time. + /// public int MaxPlayers { get; set; } = 25; + /// + /// Allow people to requests to become an operator. + /// public bool AllowOperatorRequests { get; set; } = true; public bool ServerShutdownStopsProgram { get; set; } = true; @@ -25,14 +47,32 @@ public sealed class ServerConfiguration : IServerConfiguration public bool Whitelist { get; set; } + /// + /// Network Configuration + /// public NetworkConfiguration Network { get; set; } = new(); + /// + /// Remote Console configuration + /// public RconConfiguration? Rcon { get; set; } + /// + /// Messages that the server will use by default for various actions. + /// public MessagesConfiguration Messages { get; set; } = new(); + /// + /// Allows the server to advertise itself as a LAN server to devices on your network. + /// public bool AllowLan { get; set; } = true; // Enabled because it's super useful for debugging tbh + /// + /// The view distance of the server. + /// + /// + /// Players with higher view distance will use the server's view distance. + /// public byte ViewDistance { get => viewDistance; @@ -43,6 +83,9 @@ public byte ViewDistance public ServerListQuery ServerListQuery { get; set; } = ServerListQuery.Full; + /// + /// The speed at which world time & rain time go by. + /// public int TimeTickSpeedMultiplier { get; set; } = 1; } @@ -51,7 +94,7 @@ public sealed class ServerWorld public string Name { get; set; } = "overworld"; public string Generator { get; set; } = "overworld"; - public string Seed { get; set; } = Globals.Random.Next().ToString(); + public string Seed { get; set; } = default!; public bool Default { get; set; } diff --git a/Obsidian.API/_Interfaces/IServer.cs b/Obsidian.API/_Interfaces/IServer.cs index 5bfb7e615..fdcce658b 100644 --- a/Obsidian.API/_Interfaces/IServer.cs +++ b/Obsidian.API/_Interfaces/IServer.cs @@ -14,7 +14,7 @@ public interface IServer : IDisposable public IEnumerable Players { get; } public IOperatorList Operators { get; } public IWorld DefaultWorld { get; } - public IServerConfiguration Configuration { get; } + public ServerConfiguration Configuration { get; } public IScoreboardManager ScoreboardManager { get; } diff --git a/Obsidian/Hosting/DefaultServerEnvironment.cs b/Obsidian/Hosting/DefaultServerEnvironment.cs index c54ea8abf..cd0ec62e7 100644 --- a/Obsidian/Hosting/DefaultServerEnvironment.cs +++ b/Obsidian/Hosting/DefaultServerEnvironment.cs @@ -12,11 +12,11 @@ namespace Obsidian.Hosting; /// /// Use the method to create an instance. /// -internal sealed class DefaultServerEnvironment(IOptionsMonitor serverConfig, ILogger logger) : IServerEnvironment, IDisposable +internal sealed class DefaultServerEnvironment(IOptionsMonitor serverConfig, ILogger logger) : IServerEnvironment, IDisposable { private readonly ILogger logger = logger; - public IOptionsMonitor ServerConfig { get; } = serverConfig; + public IOptionsMonitor ServerConfig { get; } = serverConfig; /// /// Provide server commands using the Console. diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index 0889a08b8..1d0a0519f 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -22,7 +22,7 @@ public static IHostApplicationBuilder ConfigureObsidian(this IHostApplicationBui public static IHostApplicationBuilder AddObsidian(this IHostApplicationBuilder builder) { - builder.Services.Configure(builder.Configuration); + builder.Services.Configure(builder.Configuration); builder.Services.Configure(builder.Configuration); builder.Services.AddSingleton(); diff --git a/Obsidian/Hosting/ObsidianHostingService.cs b/Obsidian/Hosting/ObsidianHostingService.cs index 6811756b2..e8dc6e93a 100644 --- a/Obsidian/Hosting/ObsidianHostingService.cs +++ b/Obsidian/Hosting/ObsidianHostingService.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Obsidian.API.Configuration; using System.Threading; @@ -9,12 +8,12 @@ internal sealed class ObsidianHostingService( IHostApplicationLifetime lifetime, IServer server, IServerEnvironment env, - IOptionsMonitor serverConfiguration) : BackgroundService + IOptionsMonitor serverConfiguration) : BackgroundService { private readonly IHostApplicationLifetime _lifetime = lifetime; private readonly IServerEnvironment _environment = env; private readonly IServer _server = server; - private readonly IOptionsMonitor serverConfiguration = serverConfiguration; + private readonly IOptionsMonitor serverConfiguration = serverConfiguration; protected async override Task ExecuteAsync(CancellationToken cToken) { diff --git a/Obsidian/Net/ClientHandler.cs b/Obsidian/Net/ClientHandler.cs index fdda6243e..fba6ef58d 100644 --- a/Obsidian/Net/ClientHandler.cs +++ b/Obsidian/Net/ClientHandler.cs @@ -13,10 +13,10 @@ namespace Obsidian.Net; public sealed class ClientHandler { private ConcurrentDictionary Packets { get; } = new ConcurrentDictionary(); - private IServerConfiguration config; + private ServerConfiguration config; private readonly ILogger _logger; - public ClientHandler(IServerConfiguration config) + public ClientHandler(ServerConfiguration config) { this.config = config; var loggerProvider = new LoggerProvider(LogLevel.Error); diff --git a/Obsidian/Net/Rcon/RconServer.cs b/Obsidian/Net/Rcon/RconServer.cs index 41ac32491..c5d9a34f4 100644 --- a/Obsidian/Net/Rcon/RconServer.cs +++ b/Obsidian/Net/Rcon/RconServer.cs @@ -11,23 +11,15 @@ using System.Threading; namespace Obsidian.Net.Rcon; -public sealed class RconServer +public sealed class RconServer(ILogger logger, IOptions config, CommandHandler commandHandler) { private const int KEY_SIZE = 256; private const int CERTAINTY = 5; - private readonly ILogger _logger; - private readonly IOptions _config; - private readonly CommandHandler _cmdHandler; - private readonly List _connections; - - public RconServer(ILogger logger, IOptions config, CommandHandler commandHandler) - { - _logger = logger; - _config = config; - _cmdHandler = commandHandler; - _connections = new(); - } + private readonly ILogger _logger = logger; + private readonly IOptions _config = config; + private readonly CommandHandler _cmdHandler = commandHandler; + private readonly List _connections = new(); public async Task RunAsync(Server server, CancellationToken cToken) { diff --git a/Obsidian/Plugins/PluginManager.cs b/Obsidian/Plugins/PluginManager.cs index 2264d76b1..78c031203 100644 --- a/Obsidian/Plugins/PluginManager.cs +++ b/Obsidian/Plugins/PluginManager.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Obsidian.API.Configuration; -using Obsidian.API.Logging; using Obsidian.API.Plugins; using Obsidian.Commands.Framework; using Obsidian.Hosting; @@ -87,7 +86,7 @@ private void ConfigureInitialServices() builder.ClearProviders(); builder.AddConfiguration(this.configuration); }); - this.pluginServiceDescriptors.AddSingleton(serverProvider.GetRequiredService>()); + this.pluginServiceDescriptors.AddSingleton(serverProvider.GetRequiredService>()); } /// diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 0a4be2ec6..eb4ed6da2 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -90,7 +90,7 @@ public static string VERSION public HashSet RegisteredChannels { get; } = new(); public CommandHandler CommandsHandler { get; } - public IServerConfiguration Configuration { get; set; } + public ServerConfiguration Configuration { get; set; } public string Version => VERSION; public string Brand { get; } = "obsidian"; @@ -105,7 +105,7 @@ public static string VERSION /// public Server( IHostApplicationLifetime lifetime, - IOptionsMonitor configuration, + IOptionsMonitor configuration, IOptionsMonitor whitelistConfiguration, ILoggerFactory loggerFactory, IWorldManager worldManager, @@ -176,7 +176,7 @@ public Server( } } - private void ConfigChanged(IServerConfiguration configuration) => this.Configuration = configuration; + private void ConfigChanged(ServerConfiguration configuration) => this.Configuration = configuration; // TODO make sure to re-send recipes public void RegisterRecipes(params IRecipe[] recipes) diff --git a/Obsidian/Utilities/ServerStatus.cs b/Obsidian/Utilities/ServerStatus.cs index 4b860e2f9..2514f464f 100644 --- a/Obsidian/Utilities/ServerStatus.cs +++ b/Obsidian/Utilities/ServerStatus.cs @@ -110,15 +110,13 @@ public void AddPlayer(string username, Guid uuid) => Sample.Add(new }); } -public sealed class ServerDescription : IServerDescription +public sealed class ServerDescription(ServerConfiguration configuration) : IServerDescription { [JsonIgnore] public string Text { get => text; set => text = FormatText(value); } [JsonInclude] - private string text; - - public ServerDescription(IServerConfiguration configuration) => this.text = FormatText(configuration.Motd); + private string text = FormatText(configuration.Motd); private static string FormatText(string text) => text.Replace('&', '§'); } diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 3b98d7740..d03f9d784 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -56,7 +56,7 @@ public sealed class World : IWorld public int LoadedChunkCount => this.Regions.Values.Sum(x => x.LoadedChunkCount); public required IPacketBroadcaster PacketBroadcaster { get; init; } - public required IServerConfiguration Configuration { get; init; } + public required ServerConfiguration Configuration { get; init; } public Gamemode DefaultGamemode => LevelData.DefaultGamemode; diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 803196b61..613a97fd0 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -14,14 +14,14 @@ namespace Obsidian.WorldData; -public sealed class WorldManager(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptionsMonitor configuration, +public sealed class WorldManager(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IOptionsMonitor configuration, IServerEnvironment serverEnvironment) : BackgroundService, IWorldManager { private readonly ILogger logger = loggerFactory.CreateLogger(); private readonly Dictionary worlds = new(); private readonly ILoggerFactory loggerFactory = loggerFactory; private readonly IServiceProvider serviceProvider = serviceProvider; - private readonly IOptionsMonitor configuration = configuration; + private readonly IOptionsMonitor configuration = configuration; private readonly IServerEnvironment serverEnvironment = serverEnvironment; private readonly IServiceScope serviceScope = serviceProvider.CreateScope(); From 0de9668a711415136242e749b1f344cd17cd436b Mon Sep 17 00:00:00 2001 From: Tides Date: Tue, 9 Apr 2024 20:27:46 -0400 Subject: [PATCH 09/13] Update WorldManager.cs --- Obsidian/WorldData/WorldManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 613a97fd0..3a22f7881 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -199,7 +199,8 @@ private static async Task> LoadServerWorldsAsync() { "minecraft:the_nether", "minecraft:the_end" - } + }, + Seed = Globals.Random.Next().ToString() } }; From f475584478dc3e37d329db6519a5a7c0883b14cd Mon Sep 17 00:00:00 2001 From: Tides Date: Sat, 4 May 2024 08:51:50 -0400 Subject: [PATCH 10/13] Fix errors --- Obsidian.API/Configuration/ServerConfiguration.cs | 5 +++++ Obsidian/Plugins/PluginManager.cs | 4 +--- Obsidian/Server.cs | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Obsidian.API/Configuration/ServerConfiguration.cs b/Obsidian.API/Configuration/ServerConfiguration.cs index 3855b192f..9304fa2b6 100644 --- a/Obsidian.API/Configuration/ServerConfiguration.cs +++ b/Obsidian.API/Configuration/ServerConfiguration.cs @@ -43,6 +43,11 @@ public sealed class ServerConfiguration public bool ServerShutdownStopsProgram { get; set; } = true; + /// + /// Whether to allow the server to load untrusted(unsigned) plugins + /// + public bool AllowUntrustedPlugins { get; set; } = true; + public bool? Baah { get; set; } public bool Whitelist { get; set; } diff --git a/Obsidian/Plugins/PluginManager.cs b/Obsidian/Plugins/PluginManager.cs index bf78b7985..dc8926d7a 100644 --- a/Obsidian/Plugins/PluginManager.cs +++ b/Obsidian/Plugins/PluginManager.cs @@ -22,8 +22,6 @@ public sealed class PluginManager { internal readonly ILogger logger; private readonly IConfiguration configuration; - private readonly List plugins = new(); - private readonly List stagedPlugins = new(); internal readonly IServer server; private static PackedPluginProvider packedPluginProvider = default!; @@ -226,7 +224,7 @@ public async ValueTask OnServerReadyAsync() public PluginContainer GetPluginContainerByAssembly(Assembly? assembly = null) => this.Plugins.First(x => x.PluginAssembly == (assembly ?? Assembly.GetCallingAssembly())); - private void ConfigureInitialServices(IServerEnvironment env) + private void ConfigureInitialServices() { this.pluginServiceDescriptors.AddLogging((builder) => { diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 6f7b215d7..4d726aa8f 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -136,7 +136,8 @@ public Server( CommandsHandler = commandHandler; - PluginManager = new PluginManager(this.serviceProvider, this, eventDispatcher, CommandsHandler, loggerFactory.CreateLogger()); + PluginManager = new PluginManager(this.serviceProvider, this, eventDispatcher, CommandsHandler, loggerFactory.CreateLogger(), + serviceProvider.GetRequiredService()); _logger.LogDebug("Registering commands..."); CommandsHandler.RegisterCommandClass(null); From 002eca492ae53cbaf3a69a86354cf09a2a42533d Mon Sep 17 00:00:00 2001 From: Tides Date: Sun, 12 May 2024 08:44:34 -0400 Subject: [PATCH 11/13] Add schemas directly to repo lets not use my domain for that :sweat_smile: --- .schema/server.json | 279 ++++++++++++++++++++++ .schema/whitelist.json | 35 +++ Obsidian.ConsoleApp/config/server.json | 2 +- Obsidian.ConsoleApp/config/whitelist.json | 2 +- 4 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 .schema/server.json create mode 100644 .schema/whitelist.json diff --git a/.schema/server.json b/.schema/server.json new file mode 100644 index 000000000..aaf6a7ca7 --- /dev/null +++ b/.schema/server.json @@ -0,0 +1,279 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "IServerConfiguration", + "type": "object", + "x-abstract": true, + "additionalProperties": false, + "properties": { + "Logging": { + "$ref": "#/definitions/logging" + }, + "baah": { + "type": [ + "boolean", + "null" + ] + }, + "allowLan": { + "type": "boolean" + }, + "motd": { + "type": "string" + }, + "port": { + "type": "integer", + "format": "int32" + }, + "address": { + "type": [ + "null", + "string" + ] + }, + "onlineMode": { + "type": "boolean" + }, + "maxPlayers": { + "type": "integer", + "format": "int32" + }, + "pregenerateChunkRange": { + "type": "integer", + "format": "int32" + }, + "serverListQuery": { + "$ref": "#/definitions/ServerListQuery" + }, + "timeTickSpeedMultiplier": { + "type": "integer", + "format": "int32" + }, + "allowOperatorRequests": { + "type": "boolean" + }, + "enableRcon": { + "type": "boolean" + }, + "whitelist": { + "type": "boolean" + }, + "network": { + "$ref": "#/definitions/NetworkConfiguration" + }, + "messages": { + "$ref": "#/definitions/MessagesConfiguration" + }, + "rcon": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/definitions/RconConfiguration" + } + ] + }, + "viewDistance": { + "type": "integer", + "format": "byte" + } + }, + "definitions": { + "logLevelThreshold": { + "description": "Log level threshold.", + "type": "string", + "enum": [ + "Trace", + "Debug", + "Information", + "Warning", + "Error", + "Critical", + "None" + ] + }, + "logLevel": { + "title": "logging level options", + "description": "Log level configurations used when creating logs. Only logs that exceeds its matching log level will be enabled. Each log level configuration has a category specified by its JSON property name. For more information about configuring log levels, see https://docs.microsoft.com/aspnet/core/fundamentals/logging/#configure-logging.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/logLevelThreshold" + } + }, + "logging": { + "title": "logging options", + "type": "object", + "description": "Configuration for Microsoft.Extensions.Logging.", + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + }, + "Console": { + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + }, + "FormatterName": { + "description": "Name of the log message formatter to use. Defaults to 'simple'.", + "type": "string", + "default": "simple" + }, + "FormatterOptions": { + "title": "formatter options", + "description": "Log message formatter options. Additional properties are available on the options depending on the configured formatter. The formatter is specified by FormatterName.", + "type": "object", + "properties": { + "IncludeScopes": { + "description": "Include scopes when true. Defaults to false.", + "type": "boolean", + "default": false + }, + "TimestampFormat": { + "description": "Format string used to format timestamp in logging messages. Defaults to null.", + "type": "string" + }, + "UseUtcTimestamp": { + "description": "Indication whether or not UTC timezone should be used to for timestamps in logging messages. Defaults to false.", + "type": "boolean", + "default": false + } + } + }, + "LogToStandardErrorThreshold": { + "$ref": "#/definitions/logLevelThreshold", + "description": "The minimum level of messages are written to Console.Error." + } + } + }, + "EventSource": { + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + } + } + }, + "Debug": { + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + } + } + }, + "EventLog": { + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + } + } + }, + "ElmahIo": { + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + } + } + }, + "ElmahIoBreadcrumbs": { + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + } + } + } + }, + "additionalProperties": { + "title": "provider logging settings", + "type": "object", + "description": "Logging configuration for a provider. The provider name must match the configuration's JSON property property name.", + "properties": { + "LogLevel": { + "$ref": "#/definitions/logLevel" + } + } + } + }, + "ServerListQuery": { + "type": "string", + "description": "", + "x-enumNames": [ + "Full", + "Anonymized", + "Disabled" + ], + "enum": [ + "Full", + "Anonymized", + "Disabled" + ] + }, + "NetworkConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "shouldThrottle": { + "type": "boolean" + }, + "keepAliveInterval": { + "type": "integer", + "format": "int64" + }, + "keepAliveTimeoutInterval": { + "type": "integer", + "format": "int64" + }, + "connectionThrottle": { + "type": "integer", + "format": "int64" + }, + "mulitplayerDebugMode": { + "type": "boolean" + } + } + }, + "MessagesConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "join": { + "type": "string" + }, + "leave": { + "type": "string" + }, + "notWhitelisted": { + "type": "string" + }, + "serverFull": { + "type": "string" + }, + "outdatedClient": { + "type": "string" + }, + "outdatedServer": { + "type": "string" + } + } + }, + "RconConfiguration": { + "type": "object", + "additionalProperties": false, + "properties": { + "password": { + "type": [ + "null", + "string" + ] + }, + "port": { + "type": "integer" + }, + "broadcastToOps": { + "type": "boolean" + }, + "requireEncryption": { + "type": "boolean" + } + } + } + } +} \ No newline at end of file diff --git a/.schema/whitelist.json b/.schema/whitelist.json new file mode 100644 index 000000000..56a2653cb --- /dev/null +++ b/.schema/whitelist.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "WhitelistConfiguration", + "type": "object", + "additionalProperties": false, + "properties": { + "whitelistedPlayers": { + "type": "array", + "items": { + "$ref": "#/definitions/WhitelistedPlayer" + } + }, + "whitelistedIps": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "definitions": { + "WhitelistedPlayer": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string", + "format": "guid" + } + } + } + } +} \ No newline at end of file diff --git a/Obsidian.ConsoleApp/config/server.json b/Obsidian.ConsoleApp/config/server.json index d018e0968..5b519c857 100644 --- a/Obsidian.ConsoleApp/config/server.json +++ b/Obsidian.ConsoleApp/config/server.json @@ -1,5 +1,5 @@ { - "$schema": "https://api.tides.cc/files/obsidian-schemas/server.json", + "$schema": "https://raw.githubusercontent.com/ObsidianMC/Obsidian/master/.schema/server.json", "allowLan": true, "allowOperatorRequests": true, diff --git a/Obsidian.ConsoleApp/config/whitelist.json b/Obsidian.ConsoleApp/config/whitelist.json index 5c0805ab8..ed3d6011f 100644 --- a/Obsidian.ConsoleApp/config/whitelist.json +++ b/Obsidian.ConsoleApp/config/whitelist.json @@ -1,5 +1,5 @@ { - "$schema": "https://api.tides.cc/files/obsidian-schemas/whitelist.json", + "$schema": "https://raw.githubusercontent.com/ObsidianMC/Obsidian/master/.schema/whitelist.json", "whitelistedPlayers": [], "whitelistedIps": [] } \ No newline at end of file From 37d370100d26fb9297b5f18c02f66f27e1531c34 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 23 May 2024 17:07:54 -0400 Subject: [PATCH 12/13] Make config embedded resources --- .../Obsidian.ConsoleApp.csproj | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj b/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj index 8e4973955..b55ac1bec 100644 --- a/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj +++ b/Obsidian.ConsoleApp/Obsidian.ConsoleApp.csproj @@ -9,29 +9,21 @@ en - - - - - - - - - + + + - - - + + + - - - PreserveNewest - - + + + - - PreserveNewest - + + PreserveNewest + From b16d5ded9cb224926b434c02d21c468f794ece5e Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 23 May 2024 17:08:18 -0400 Subject: [PATCH 13/13] Generate configs before hosting server starts --- Obsidian.ConsoleApp/Program.Functions.cs | 32 ++++++++++++++++++++++++ Obsidian.ConsoleApp/Program.cs | 5 ++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 Obsidian.ConsoleApp/Program.Functions.cs diff --git a/Obsidian.ConsoleApp/Program.Functions.cs b/Obsidian.ConsoleApp/Program.Functions.cs new file mode 100644 index 000000000..7a540873a --- /dev/null +++ b/Obsidian.ConsoleApp/Program.Functions.cs @@ -0,0 +1,32 @@ +using System.Reflection; + +public partial class Program +{ + private static async ValueTask GenerateConfigFiles() + { + const string path = "config"; + + Directory.CreateDirectory(path); + + var serverJsonFile = Path.Combine(path, "server.json"); + var whitelistJsonFile = Path.Combine(path, "whitelist.json"); + + if (!File.Exists(serverJsonFile)) + { + await using var file = File.Create(serverJsonFile); + + await using var embeddedFile = Assembly.GetExecutingAssembly().GetManifestResourceStream("Obsidian.ConsoleApp.config.server.json"); + + await embeddedFile!.CopyToAsync(file); + } + + if (!File.Exists(whitelistJsonFile)) + { + await using var file = File.Create(whitelistJsonFile); + + await using var embeddedFile = Assembly.GetExecutingAssembly().GetManifestResourceStream("Obsidian.ConsoleApp.config.whitelist.json"); + + await embeddedFile!.CopyToAsync(file); + } + } +} diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index cbdb24108..ac1212619 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; @@ -23,6 +22,8 @@ Console.WriteLine(asciilogo); Console.ResetColor(); +await GenerateConfigFiles(); + var builder = Host.CreateApplicationBuilder(); builder.ConfigureObsidian();