From b546a168d2ae8fbd4f2416c60941d80e61d0af2b Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Sat, 26 Jul 2025 23:19:57 +0200 Subject: [PATCH] Implemented restorer, wired up for basic testing. Improved abstractions and fixed observer pattern issues --- .../Helpers/CompositeServiceProvider.cs | 15 +++- .../MoonlightServers.Daemon.csproj | 2 +- .../ServerSys/Abstractions/IConsole.cs | 4 +- .../ServerSys/Abstractions/IInstaller.cs | 3 +- .../ServerSys/Abstractions/IProvisioner.cs | 3 +- .../ServerSys/Abstractions/IStatistics.cs | 2 +- .../ServerSys/Abstractions/Server.cs | 46 +++++++---- .../ServerSys/Abstractions/ServerMeta.cs | 5 +- .../Implementations/DefaultRestorer.cs | 56 +++++++++++++ .../Implementations/DockerConsole.cs | 37 +++++---- .../Implementations/DockerInstaller.cs | 55 +++++++++++++ .../Implementations/DockerProvisioner.cs | 58 +++++++------ .../Implementations/DockerStatistics.cs | 34 ++++++++ .../Implementations/RawFileSystem.cs | 15 ++-- .../ServerSys/ServerFactory.cs | 23 ++---- .../Services/DockerEventService.cs | 12 +-- MoonlightServers.Daemon/Startup.cs | 82 ++++++++++++++++++- 17 files changed, 355 insertions(+), 97 deletions(-) create mode 100644 MoonlightServers.Daemon/ServerSys/Implementations/DefaultRestorer.cs create mode 100644 MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs create mode 100644 MoonlightServers.Daemon/ServerSys/Implementations/DockerStatistics.cs diff --git a/MoonlightServers.Daemon/Helpers/CompositeServiceProvider.cs b/MoonlightServers.Daemon/Helpers/CompositeServiceProvider.cs index c960bcd..ab5316a 100644 --- a/MoonlightServers.Daemon/Helpers/CompositeServiceProvider.cs +++ b/MoonlightServers.Daemon/Helpers/CompositeServiceProvider.cs @@ -13,10 +13,17 @@ public class CompositeServiceProvider : IServiceProvider { foreach (var provider in ServiceProviders) { - var service = provider.GetService(serviceType); - - if (service != null) - return service; + try + { + var service = provider.GetService(serviceType); + + if (service != null) + return service; + } + catch (InvalidOperationException) + { + // Ignored + } } return null; diff --git a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj index bcca05e..3129e27 100644 --- a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj +++ b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj @@ -9,7 +9,7 @@ - + diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs index 7545189..60270a9 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs @@ -2,8 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IConsole : IServerComponent { - public IAsyncObservable OnOutput { get; } - public IAsyncObservable OnInput { get; } + public IObservable OnOutput { get; } + public IObservable OnInput { get; } public Task AttachToRuntime(); public Task AttachToInstallation(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs index 603f435..4b2e43a 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs @@ -2,7 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IInstaller : IServerComponent { - public IAsyncObservable OnExited { get; set; } + public IObservable OnExited { get; } + public bool IsRunning { get; } public Task Start(); public Task Abort(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs index 52029d9..b2c512c 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs @@ -2,7 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IProvisioner : IServerComponent { - public IAsyncObservable OnExited { get; set; } + public IObservable OnExited { get; } + public bool IsProvisioned { get; } public Task Provision(); public Task Start(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IStatistics.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IStatistics.cs index cf355c9..d6411fd 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IStatistics.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IStatistics.cs @@ -2,7 +2,7 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IStatistics : IServerComponent { - public IAsyncObservable Stats { get; } + public IAsyncObservable OnStats { get; } public Task SubscribeToRuntime(); public Task SubscribeToInstallation(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs index 9b398a3..0404b03 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs @@ -1,3 +1,4 @@ +using System.Reactive.Subjects; using MoonlightServers.Daemon.ServerSystem; using Stateless; @@ -12,11 +13,14 @@ public class Server : IAsyncDisposable public IRestorer Restorer { get; private set; } public IStatistics Statistics { get; private set; } public StateMachine StateMachine { get; private set; } + public ServerContext Context { get; private set; } + public IObservable OnState => OnStateSubject; + private readonly Subject OnStateSubject = new(); private readonly ILogger Logger; - - private IAsyncDisposable? ProvisionExitSubscription; - private IAsyncDisposable? InstallerExitSubscription; + + private IDisposable? ProvisionExitSubscription; + private IDisposable? InstallerExitSubscription; public Server( ILogger logger, @@ -26,7 +30,7 @@ public class Server : IAsyncDisposable IProvisioner provisioner, IRestorer restorer, IStatistics statistics, - StateMachine stateMachine + ServerContext context ) { Logger = logger; @@ -36,7 +40,7 @@ public class Server : IAsyncDisposable Provisioner = provisioner; Restorer = restorer; Statistics = statistics; - StateMachine = stateMachine; + Context = context; } public async Task Initialize() @@ -74,20 +78,22 @@ public class Server : IAsyncDisposable CreateStateMachine(restoredState); // Setup event handling - ProvisionExitSubscription = await Provisioner.OnExited.SubscribeAsync(async o => + ProvisionExitSubscription = Provisioner.OnExited.Subscribe(o => { - await StateMachine.FireAsync(ServerTrigger.Exited); + StateMachine.Fire(ServerTrigger.Exited); }); - InstallerExitSubscription = await Installer.OnExited.SubscribeAsync(async o => + InstallerExitSubscription = Installer.OnExited.Subscribe(o => { - await StateMachine.FireAsync(ServerTrigger.Exited); + StateMachine.Fire(ServerTrigger.Exited); }); } private void CreateStateMachine(ServerState initialState) { StateMachine = new StateMachine(initialState, FiringMode.Queued); + + StateMachine.OnTransitioned(transition => OnStateSubject.OnNext(transition.Destination)); // Configure basic state machine flow @@ -120,7 +126,10 @@ public class Server : IAsyncDisposable // Handle transitions StateMachine.Configure(ServerState.Starting) - .OnActivateAsync(HandleStart); + .OnEntryAsync(HandleStart); + + StateMachine.Configure(ServerState.Stopping) + .OnEntryFromAsync(ServerTrigger.Stop, HandleStop); } #region State machine handlers @@ -136,7 +145,7 @@ public class Server : IAsyncDisposable // 4. Provision the container // 5. Attach console to container // 6. Start the container - + // 1. Fetch latest configuration from panel // TODO: Implement @@ -153,14 +162,14 @@ public class Server : IAsyncDisposable await Console.WriteToMoonlight("Mounting storage"); await FileSystem.Mount(); } - + // 4. Provision the container await Console.WriteToMoonlight("Provisioning runtime"); await Provisioner.Provision(); - + // 5. Attach console to container await Console.AttachToRuntime(); - + // 6. Start the container await Provisioner.Start(); } @@ -169,16 +178,21 @@ public class Server : IAsyncDisposable Logger.LogError(e, "An error occured while starting the server"); } } + + private async Task HandleStop() + { + await Provisioner.Stop(); + } #endregion public async ValueTask DisposeAsync() { if (ProvisionExitSubscription != null) - await ProvisionExitSubscription.DisposeAsync(); + ProvisionExitSubscription.Dispose(); if (InstallerExitSubscription != null) - await InstallerExitSubscription.DisposeAsync(); + InstallerExitSubscription.Dispose(); await Console.DisposeAsync(); await FileSystem.DisposeAsync(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/ServerMeta.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/ServerMeta.cs index 04721ca..3c673bc 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/ServerMeta.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/ServerMeta.cs @@ -2,9 +2,8 @@ using MoonlightServers.Daemon.Models.Cache; namespace MoonlightServers.Daemon.ServerSys.Abstractions; -public record ServerMeta +public record ServerContext { public ServerConfiguration Configuration { get; set; } - public IServiceCollection ServiceCollection { get; set; } - public IServiceProvider ServiceProvider { get; set; } + public AsyncServiceScope ServiceScope { get; set; } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DefaultRestorer.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DefaultRestorer.cs new file mode 100644 index 0000000..8f64a7f --- /dev/null +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DefaultRestorer.cs @@ -0,0 +1,56 @@ +using MoonlightServers.Daemon.ServerSys.Abstractions; +using MoonlightServers.Daemon.ServerSystem; + +namespace MoonlightServers.Daemon.ServerSys.Implementations; + +public class DefaultRestorer : IRestorer +{ + private readonly ILogger Logger; + private readonly IConsole Console; + private readonly IProvisioner Provisioner; + private readonly IStatistics Statistics; + + public DefaultRestorer( + ILogger logger, + IConsole console, + IProvisioner provisioner, + IStatistics statistics + ) + { + Logger = logger; + Console = console; + Provisioner = provisioner; + Statistics = statistics; + } + + public Task Initialize() + => Task.CompletedTask; + + public Task Sync() + => Task.CompletedTask; + + public async Task Restore() + { + Logger.LogDebug("Restoring server state"); + + if (Provisioner.IsProvisioned) + { + Logger.LogDebug("Detected runtime to restore"); + + await Console.AttachToRuntime(); + await Statistics.SubscribeToRuntime(); + + // TODO: Read out existing container log in order to search if the server is online + + return ServerState.Online; + } + else + { + Logger.LogDebug("Nothing found to restore"); + return ServerState.Offline; + } + } + + public ValueTask DisposeAsync() + => ValueTask.CompletedTask; +} \ No newline at end of file diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs index 0bc6d7c..e1fa561 100644 --- a/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs @@ -11,23 +11,29 @@ namespace MoonlightServers.Daemon.ServerSys.Implementations; public class DockerConsole : IConsole { - public IAsyncObservable OnOutput => OnOutputSubject.ToAsyncObservable(); - public IAsyncObservable OnInput => OnInputSubject.ToAsyncObservable(); + public IObservable OnOutput => OnOutputSubject; + public IObservable OnInput => OnInputSubject; - private readonly AsyncSubject OnOutputSubject = new(); - private readonly AsyncSubject OnInputSubject = new(); + private readonly Subject OnOutputSubject = new(); + private readonly Subject OnInputSubject = new(); private readonly ConcurrentList OutputCache = new(); private readonly DockerClient DockerClient; private readonly ILogger Logger; - private readonly ServerMeta Meta; - + private readonly ServerContext Context; + private MultiplexedStream? CurrentStream; private CancellationTokenSource Cts = new(); - - public DockerConsole(ServerMeta meta) + + public DockerConsole( + ServerContext context, + DockerClient dockerClient, + ILogger logger + ) { - Meta = meta; + Context = context; + DockerClient = dockerClient; + Logger = logger; } public Task Initialize() @@ -38,13 +44,13 @@ public class DockerConsole : IConsole public async Task AttachToRuntime() { - var containerName = $"moonlight-runtime-{Meta.Configuration.Id}"; + var containerName = $"moonlight-runtime-{Context.Configuration.Id}"; await AttachStream(containerName); } public async Task AttachToInstallation() { - var containerName = $"moonlight-install-{Meta.Configuration.Id}"; + var containerName = $"moonlight-install-{Context.Configuration.Id}"; await AttachStream(containerName); } @@ -108,7 +114,7 @@ public class DockerConsole : IConsole } catch (Exception e) { - Logger.LogWarning("An unhandled error occured while reading from container stream: {e}", e); + Logger.LogWarning(e, "An unhandled error occured while reading from container stream"); } finally { @@ -121,7 +127,7 @@ public class DockerConsole : IConsole } catch (Exception e) { - Logger.LogError("An error occured while attaching to container: {e}", e); + Logger.LogError(e, "An error occured while attaching to container"); } } @@ -131,7 +137,7 @@ public class DockerConsole : IConsole Logger.LogDebug("Disconnected from container stream"); }, Cts.Token); - + return Task.CompletedTask; } @@ -168,7 +174,8 @@ public class DockerConsole : IConsole } public async Task WriteToMoonlight(string content) - => await WriteToOutput($"\x1b[0;38;2;255;255;255;48;2;124;28;230m Moonlight \x1b[0m\x1b[38;5;250m\x1b[3m {content}\x1b[0m\n\r"); + => await WriteToOutput( + $"\x1b[0;38;2;255;255;255;48;2;124;28;230m Moonlight \x1b[0m\x1b[38;5;250m\x1b[3m {content}\x1b[0m\n\r"); public Task ClearOutput() { diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs new file mode 100644 index 0000000..45941ad --- /dev/null +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs @@ -0,0 +1,55 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; +using MoonlightServers.Daemon.ServerSys.Abstractions; + +namespace MoonlightServers.Daemon.ServerSys.Implementations; + +public class DockerInstaller : IInstaller +{ + public IObservable OnExited => OnExitedSubject; + public bool IsRunning { get; private set; } = false; + + private readonly Subject OnExitedSubject = new(); + + private readonly ILogger Logger; + + public DockerInstaller(ILogger logger) + { + Logger = logger; + } + + public Task Initialize() + { + return Task.CompletedTask; + } + + public Task Sync() + { + throw new NotImplementedException(); + } + + public Task Start() + { + throw new NotImplementedException(); + } + + public Task Abort() + { + throw new NotImplementedException(); + } + + public Task Cleanup() + { + throw new NotImplementedException(); + } + + public Task SearchForCrash() + { + throw new NotImplementedException(); + } + + public async ValueTask DisposeAsync() + { + OnExitedSubject.Dispose(); + } +} \ No newline at end of file diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs index 6f28633..407a91c 100644 --- a/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs @@ -1,3 +1,5 @@ +using System.Reactive.Concurrency; +using System.Reactive.Linq; using System.Reactive.Subjects; using Docker.DotNet; using Docker.DotNet.Models; @@ -9,28 +11,29 @@ namespace MoonlightServers.Daemon.ServerSys.Implementations; public class DockerProvisioner : IProvisioner { - public IAsyncObservable OnExited { get; set; } + public IObservable OnExited => OnExitedSubject; + public bool IsProvisioned { get; private set; } private readonly DockerClient DockerClient; private readonly ILogger Logger; private readonly DockerEventService EventService; - private readonly ServerMeta Meta; + private readonly ServerContext Context; private readonly IConsole Console; private readonly DockerImageService ImageService; private readonly ServerConfigurationMapper Mapper; private readonly IFileSystem FileSystem; - private AsyncSubject OnExitedSubject = new(); + private Subject OnExitedSubject = new(); private string? ContainerId; private string ContainerName; - private IAsyncDisposable? ContainerEventSubscription; + private IDisposable? ContainerEventSubscription; public DockerProvisioner( DockerClient dockerClient, ILogger logger, DockerEventService eventService, - ServerMeta meta, + ServerContext context, IConsole console, DockerImageService imageService, ServerConfigurationMapper mapper, @@ -40,7 +43,7 @@ public class DockerProvisioner : IProvisioner DockerClient = dockerClient; Logger = logger; EventService = eventService; - Meta = meta; + Context = context; Console = console; ImageService = imageService; Mapper = mapper; @@ -49,30 +52,39 @@ public class DockerProvisioner : IProvisioner public async Task Initialize() { - ContainerName = $"moonlight-runtime-{Meta.Configuration.Id}"; + ContainerName = $"moonlight-runtime-{Context.Configuration.Id}"; - ContainerEventSubscription = await EventService + ContainerEventSubscription = EventService .OnContainerEvent - .SubscribeAsync(HandleContainerEvent); + .Subscribe(HandleContainerEvent); - // Check for any already existing runtime container - // TODO: Implement a way for restoring the state - // Needs to be able to be consumed by the restorer + // Check for any already existing runtime container to reclaim + Logger.LogDebug("Searching for orphan container to reclaim"); + + try + { + var container = await DockerClient.Containers.InspectContainerAsync(ContainerName); + + ContainerId = container.ID; + IsProvisioned = container.State.Running; + } + catch (DockerContainerNotFoundException) + { + // Ignored + } } - private ValueTask HandleContainerEvent(Message message) + private void HandleContainerEvent(Message message) { // Only handle events for our own container if (message.ID != ContainerId) - return ValueTask.CompletedTask; + return; // Only handle die events if (message.Action != "die") - return ValueTask.CompletedTask; + return; OnExitedSubject.OnNext(message); - - return ValueTask.CompletedTask; } public Task Sync() @@ -111,7 +123,7 @@ public class DockerProvisioner : IProvisioner // 2. Ensure the docker image has been downloaded await Console.WriteToMoonlight("Downloading docker image"); - await ImageService.Download(Meta.Configuration.DockerImage, async message => + await ImageService.Download(Context.Configuration.DockerImage, async message => { try { @@ -127,7 +139,7 @@ public class DockerProvisioner : IProvisioner var hostFsPath = FileSystem.GetExternalPath(); var parameters = Mapper.ToRuntimeParameters( - Meta.Configuration, + Context.Configuration, hostFsPath, ContainerName ); @@ -151,15 +163,15 @@ public class DockerProvisioner : IProvisioner public async Task Stop() { - if (Meta.Configuration.StopCommand.StartsWith('^')) + if (Context.Configuration.StopCommand.StartsWith('^')) { await DockerClient.Containers.KillContainerAsync(ContainerId, new() { - Signal = Meta.Configuration.StopCommand.Substring(1) + Signal = Context.Configuration.StopCommand.Substring(1) }); } else - await Console.WriteToInput(Meta.Configuration.StopCommand); + await Console.WriteToInput(Context.Configuration.StopCommand + "\n\r"); } public async Task Kill() @@ -241,6 +253,6 @@ public class DockerProvisioner : IProvisioner OnExitedSubject.Dispose(); if (ContainerEventSubscription != null) - await ContainerEventSubscription.DisposeAsync(); + ContainerEventSubscription.Dispose(); } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerStatistics.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerStatistics.cs new file mode 100644 index 0000000..c127048 --- /dev/null +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerStatistics.cs @@ -0,0 +1,34 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; +using MoonlightServers.Daemon.ServerSys.Abstractions; + +namespace MoonlightServers.Daemon.ServerSys.Implementations; + +public class DockerStatistics : IStatistics +{ + public IAsyncObservable OnStats => OnStatsSubject.ToAsyncObservable(); + + private readonly Subject OnStatsSubject = new(); + + public Task Initialize() + => Task.CompletedTask; + + public Task Sync() + => Task.CompletedTask; + + public Task SubscribeToRuntime() + => Task.CompletedTask; + + public Task SubscribeToInstallation() + => Task.CompletedTask; + + public ServerStats[] GetStats(int count) + { + return []; + } + + public async ValueTask DisposeAsync() + { + OnStatsSubject.Dispose(); + } +} \ No newline at end of file diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/RawFileSystem.cs b/MoonlightServers.Daemon/ServerSys/Implementations/RawFileSystem.cs index aa2c9ea..9db665f 100644 --- a/MoonlightServers.Daemon/ServerSys/Implementations/RawFileSystem.cs +++ b/MoonlightServers.Daemon/ServerSys/Implementations/RawFileSystem.cs @@ -8,24 +8,29 @@ public class RawFileSystem : IFileSystem public bool IsMounted { get; private set; } public bool Exists { get; private set; } - private readonly ServerMeta Meta; + private readonly ServerContext Context; private readonly AppConfiguration Configuration; - private string HostPath => Path.Combine(Configuration.Storage.Volumes, Meta.Configuration.Id.ToString()); + private string HostPath; - public RawFileSystem(ServerMeta meta, AppConfiguration configuration) + public RawFileSystem(ServerContext context, AppConfiguration configuration) { - Meta = meta; + Context = context; Configuration = configuration; } public Task Initialize() - => Task.CompletedTask; + { + HostPath = Path.Combine(Directory.GetCurrentDirectory(), Configuration.Storage.Volumes, Context.Configuration.Id.ToString()); + + return Task.CompletedTask; + } public Task Sync() => Task.CompletedTask; public Task Create() { + Directory.CreateDirectory(HostPath); return Task.CompletedTask; } diff --git a/MoonlightServers.Daemon/ServerSys/ServerFactory.cs b/MoonlightServers.Daemon/ServerSys/ServerFactory.cs index 0f08e19..1a0e735 100644 --- a/MoonlightServers.Daemon/ServerSys/ServerFactory.cs +++ b/MoonlightServers.Daemon/ServerSys/ServerFactory.cs @@ -1,7 +1,5 @@ -using MoonlightServers.Daemon.Helpers; using MoonlightServers.Daemon.Models.Cache; using MoonlightServers.Daemon.ServerSys.Abstractions; -using MoonlightServers.Daemon.ServerSys.Implementations; namespace MoonlightServers.Daemon.ServerSys; @@ -16,24 +14,13 @@ public class ServerFactory public Server CreateServer(ServerConfiguration configuration) { - var serverMeta = new ServerMeta(); - serverMeta.Configuration = configuration; - - // Build service collection - serverMeta.ServiceCollection = new ServiceCollection(); + var scope = ServiceProvider.CreateAsyncScope(); - // Configure service pipeline for the server components - serverMeta.ServiceCollection.AddSingleton(); - serverMeta.ServiceCollection.AddSingleton(); + var meta = scope.ServiceProvider.GetRequiredService(); - // TODO: Handle implementation configurations (e.g. virtual disk) here - - // Combine both app service provider and our server instance specific one - serverMeta.ServiceProvider = new CompositeServiceProvider([ - serverMeta.ServiceCollection.BuildServiceProvider(), - ServiceProvider - ]); + meta.Configuration = configuration; + meta.ServiceScope = scope; - return serverMeta.ServiceProvider.GetRequiredService(); + return scope.ServiceProvider.GetRequiredService(); } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/Services/DockerEventService.cs b/MoonlightServers.Daemon/Services/DockerEventService.cs index f712385..54627d1 100644 --- a/MoonlightServers.Daemon/Services/DockerEventService.cs +++ b/MoonlightServers.Daemon/Services/DockerEventService.cs @@ -10,13 +10,13 @@ public class DockerEventService : BackgroundService private readonly ILogger Logger; private readonly DockerClient DockerClient; - public IAsyncObservable OnContainerEvent => OnContainerSubject.ToAsyncObservable(); - public IAsyncObservable OnImageEvent => OnImageSubject.ToAsyncObservable(); - public IAsyncObservable OnNetworkEvent => OnNetworkSubject.ToAsyncObservable(); + public IObservable OnContainerEvent => OnContainerSubject; + public IObservable OnImageEvent => OnImageSubject; + public IObservable OnNetworkEvent => OnNetworkSubject; - private readonly AsyncSubject OnContainerSubject = new(); - private readonly AsyncSubject OnImageSubject = new(); - private readonly AsyncSubject OnNetworkSubject = new(); + private readonly Subject OnContainerSubject = new(); + private readonly Subject OnImageSubject = new(); + private readonly Subject OnNetworkSubject = new(); public DockerEventService( ILogger logger, diff --git a/MoonlightServers.Daemon/Startup.cs b/MoonlightServers.Daemon/Startup.cs index 9f914a2..fb91e02 100644 --- a/MoonlightServers.Daemon/Startup.cs +++ b/MoonlightServers.Daemon/Startup.cs @@ -1,3 +1,5 @@ +using System.Reactive.Concurrency; +using System.Reactive.Linq; using System.Text; using System.Text.Json; using Docker.DotNet; @@ -13,9 +15,13 @@ using MoonlightServers.Daemon.Configuration; using MoonlightServers.Daemon.Helpers; using MoonlightServers.Daemon.Http.Hubs; using MoonlightServers.Daemon.Mappers; +using MoonlightServers.Daemon.Models.Cache; using MoonlightServers.Daemon.ServerSys; +using MoonlightServers.Daemon.ServerSys.Abstractions; +using MoonlightServers.Daemon.ServerSys.Implementations; using MoonlightServers.Daemon.ServerSystem; using MoonlightServers.Daemon.Services; +using Server = MoonlightServers.Daemon.ServerSystem.Server; namespace MoonlightServers.Daemon; @@ -65,6 +71,70 @@ public class Startup await MapBase(); await MapHubs(); + Task.Run(async () => + { + try + { + Console.WriteLine("Press enter to create server instance"); + Console.ReadLine(); + + var config = new ServerConfiguration() + { + Allocations = [ + new ServerConfiguration.AllocationConfiguration() + { + IpAddress = "0.0.0.0", + Port = 25565 + } + ], + Cpu = 400, + Disk = 10240, + DockerImage = "ghcr.io/parkervcp/yolks:java_21", + Id = 69, + Memory = 4096, + OnlineDetection = "\\)! For help, type ", + StartupCommand = "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}", + StopCommand = "stop", + Variables = new() + { + { + "SERVER_JARFILE", + "server.jar" + } + } + }; + + var factory = WebApplication.Services.GetRequiredService(); + var server = factory.CreateServer(config); + + using var consoleSub = server.Console.OnOutput + .Subscribe(Console.Write); + + using var stateSub = server.OnState.Subscribe(state => + { + Console.WriteLine($"State: {state}"); + }); + + await server.Initialize(); + + Console.ReadLine(); + + if(server.StateMachine.State == ServerState.Offline) + await server.StateMachine.FireAsync(ServerTrigger.Start); + else + await server.StateMachine.FireAsync(ServerTrigger.Stop); + + Console.ReadLine(); + + await server.Context.ServiceScope.DisposeAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + }); + await WebApplication.RunAsync(); } @@ -72,7 +142,6 @@ public class Startup { Directory.CreateDirectory("storage"); Directory.CreateDirectory(Path.Combine("storage", "logs")); - Directory.CreateDirectory(Path.Combine("storage", "plugins")); return Task.CompletedTask; } @@ -261,6 +330,17 @@ public class Startup WebApplicationBuilder.Services.AddSingleton(); + // Server scoped stuff + WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + + WebApplicationBuilder.Services.AddScoped(); + return Task.CompletedTask; }