diff --git a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj
index 42dc3d1..009d473 100644
--- a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj
+++ b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj
@@ -9,19 +9,16 @@
-
-
+
+
-
-
-
+
-
diff --git a/MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs b/MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs
new file mode 100644
index 0000000..7d6b51d
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs
@@ -0,0 +1,203 @@
+using System.Text;
+using Docker.DotNet;
+using MoonCore.Events;
+using MoonCore.Helpers;
+using MoonlightServers.Daemon.ServerSystem.Interfaces;
+using MoonlightServers.Daemon.ServerSystem.Models;
+
+namespace MoonlightServers.Daemon.ServerSystem.Docker;
+
+public class DockerConsole : IConsole
+{
+ private readonly EventSource StdOutEventSource = new();
+ private readonly ConcurrentList StdOutCache = new();
+ private readonly DockerClient DockerClient;
+ private readonly ServerContext Context;
+ private readonly ILogger Logger;
+
+ private MultiplexedStream? BaseStream;
+ private CancellationTokenSource Cts = new();
+
+ public DockerConsole(DockerClient dockerClient, ServerContext context)
+ {
+ DockerClient = dockerClient;
+ Context = context;
+ }
+
+ public Task InitializeAsync()
+ => Task.CompletedTask;
+
+ public async Task WriteStdInAsync(string content)
+ {
+ if (BaseStream == null)
+ {
+ Logger.LogWarning("Unable to write to stdin as no stream is connected");
+ return;
+ }
+
+ var contextBuffer = Encoding.UTF8.GetBytes(content);
+
+ await BaseStream.WriteAsync(contextBuffer, 0, contextBuffer.Length, Cts.Token);
+ }
+
+ public async Task WriteStdOutAsync(string content)
+ {
+ // Add output cache
+ if (StdOutCache.Count > 250) // TODO: Config
+ StdOutCache.RemoveRange(0, 100);
+
+ StdOutCache.Add(content);
+
+ // Fire event
+ await StdOutEventSource.InvokeAsync(content);
+ }
+
+ public async Task AttachRuntimeAsync()
+ {
+ var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
+
+ await AttachToContainer(containerName);
+ }
+
+ public async Task AttachInstallationAsync()
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, Context.Configuration.Id);
+
+ await AttachToContainer(containerName);
+ }
+
+ private async Task AttachToContainer(string containerName)
+ {
+ // Cancels previous active read task if it exists
+ if (!Cts.IsCancellationRequested)
+ await Cts.CancelAsync();
+
+ // Reset cancellation token
+ Cts = new();
+
+ // Start reading task
+ Task.Run(async () =>
+ {
+ // This loop is here to reconnect to the stream when connection is lost.
+ // This can occur when docker restarts for example
+
+ while (!Cts.IsCancellationRequested)
+ {
+ try
+ {
+ using var stream = await DockerClient.Containers.AttachContainerAsync(
+ containerName,
+ true,
+ new()
+ {
+ Stderr = true,
+ Stdin = true,
+ Stdout = true,
+ Stream = true
+ },
+ Cts.Token
+ );
+
+ BaseStream = stream;
+
+ var buffer = new byte[1024];
+
+ try
+ {
+ // Read while server tasks are not canceled
+ while (!Cts.Token.IsCancellationRequested)
+ {
+ var readResult = await BaseStream.ReadOutputAsync(
+ buffer,
+ 0,
+ buffer.Length,
+ Cts.Token
+ );
+
+ if (readResult.EOF)
+ break;
+
+ var decodedText = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
+
+ await WriteStdOutAsync(decodedText);
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ // Ignored
+ }
+ catch (OperationCanceledException)
+ {
+ // Ignored
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning(e, "An unhandled error occured while reading from container stream");
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ // ignored
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e, "An error occured while attaching to container");
+ }
+ }
+
+ Logger.LogDebug("Disconnected from container stream");
+ });
+ }
+
+ public async Task FetchRuntimeAsync()
+ {
+ var containerName = string.Format(DockerConstants.RuntimeNameTemplate, Context.Configuration.Id);
+
+ await FetchFromContainer(containerName);
+ }
+
+ public async Task FetchInstallationAsync()
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, Context.Configuration.Id);
+
+ await FetchFromContainer(containerName);
+ }
+
+ private async Task FetchFromContainer(string containerName)
+ {
+ var logStream = await DockerClient.Containers.GetContainerLogsAsync(containerName, true, new()
+ {
+ Follow = false,
+ ShowStderr = true,
+ ShowStdout = true
+ });
+
+ var combinedOutput = await logStream.ReadOutputToEndAsync(Cts.Token);
+ var contentToAdd = combinedOutput.stdout + combinedOutput.stderr;
+
+ await WriteStdOutAsync(contentToAdd);
+ }
+
+ public Task ClearCacheAsync()
+ {
+ StdOutCache.Clear();
+ return Task.CompletedTask;
+ }
+
+ public Task> GetCacheAsync()
+ {
+ return Task.FromResult>(StdOutCache);
+ }
+
+ public async Task SubscribeStdOutAsync(Func callback)
+ => await StdOutEventSource.SubscribeAsync(callback);
+
+ public async ValueTask DisposeAsync()
+ {
+ if (!Cts.IsCancellationRequested)
+ await Cts.CancelAsync();
+
+ if (BaseStream != null)
+ BaseStream.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Docker/DockerConstants.cs b/MoonlightServers.Daemon/ServerSystem/Docker/DockerConstants.cs
new file mode 100644
index 0000000..e7efc5b
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/Docker/DockerConstants.cs
@@ -0,0 +1,7 @@
+namespace MoonlightServers.Daemon.ServerSystem.Docker;
+
+public static class DockerConstants
+{
+ public const string RuntimeNameTemplate = "monnlight-runtime-{0}";
+ public const string InstallationNameTemplate = "monnlight-installation-{0}";
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Docker/DockerInstallation.cs b/MoonlightServers.Daemon/ServerSystem/Docker/DockerInstallation.cs
new file mode 100644
index 0000000..7c40d35
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/Docker/DockerInstallation.cs
@@ -0,0 +1,185 @@
+using Docker.DotNet;
+using Docker.DotNet.Models;
+using MoonCore.Events;
+using MoonlightServers.Daemon.Mappers;
+using MoonlightServers.Daemon.ServerSystem.Interfaces;
+using MoonlightServers.Daemon.ServerSystem.Models;
+using MoonlightServers.Daemon.Services;
+using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
+
+namespace MoonlightServers.Daemon.ServerSystem.Docker;
+
+public class DockerInstallation : IInstallation
+{
+ private readonly DockerEventService DockerEventService;
+ private readonly ServerConfigurationMapper Mapper;
+ private readonly DockerImageService ImageService;
+ private readonly ServerContext ServerContext;
+ private readonly DockerClient DockerClient;
+ private readonly IReporter Reporter;
+
+ private readonly EventSource ExitEventSource = new();
+
+ private IAsyncDisposable ContainerEventSubscription;
+ private string ContainerId;
+
+ public DockerInstallation(
+ DockerClient dockerClient,
+ ServerContext serverContext,
+ ServerConfigurationMapper mapper,
+ DockerImageService imageService,
+ IReporter reporter,
+ DockerEventService dockerEventService
+ )
+ {
+ DockerClient = dockerClient;
+ ServerContext = serverContext;
+ Mapper = mapper;
+ ImageService = imageService;
+ Reporter = reporter;
+ DockerEventService = dockerEventService;
+ }
+
+ public async Task InitializeAsync()
+ {
+ ContainerEventSubscription = await DockerEventService.SubscribeContainerAsync(OnContainerEvent);
+ }
+
+ private async ValueTask OnContainerEvent(Message message)
+ {
+ // Only handle events for our own container
+ if (message.ID != ContainerId)
+ return;
+
+ // Only handle die events
+ if (message.Action != "die")
+ return;
+
+ int exitCode;
+
+ if (message.Actor.Attributes.TryGetValue("exitCode", out var exitCodeStr))
+ {
+ if (!int.TryParse(exitCodeStr, out exitCode))
+ exitCode = 0;
+ }
+ else
+ exitCode = 0;
+
+
+ await ExitEventSource.InvokeAsync(exitCode);
+ }
+
+ public async Task CheckExistsAsync()
+ {
+ try
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
+
+ await DockerClient.Containers.InspectContainerAsync(
+ containerName
+ );
+
+ return true;
+ }
+ catch (DockerContainerNotFoundException)
+ {
+ return false;
+ }
+ }
+
+ public async Task CreateAsync(
+ string runtimePath,
+ string hostPath,
+ ServerInstallDataResponse data
+ )
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
+
+ var parameters = Mapper.ToInstallParameters(
+ ServerContext.Configuration,
+ data,
+ runtimePath,
+ hostPath,
+ containerName
+ );
+
+ // Docker image
+ await Reporter.StatusAsync("Downloading docker image");
+
+ await ImageService.Download(data.DockerImage, async status => { await Reporter.StatusAsync(status); });
+
+ await Reporter.StatusAsync("Downloaded docker image");
+
+ // Write install script to install fs
+
+ await File.WriteAllTextAsync(
+ Path.Combine(hostPath, "install.sh"),
+ data.Script
+ );
+
+ //
+
+ await DockerClient.Containers.CreateContainerAsync(parameters);
+
+ await Reporter.StatusAsync("Created container");
+ }
+
+ public async Task StartAsync()
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
+
+ await DockerClient.Containers.StartContainerAsync(containerName, new());
+ }
+
+ public async Task KillAsync()
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
+
+ await DockerClient.Containers.KillContainerAsync(containerName, new());
+ }
+
+ public async Task DestroyAsync()
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
+
+ try
+ {
+ var container = await DockerClient.Containers.InspectContainerAsync(containerName);
+
+ if (container.State.Running)
+ await DockerClient.Containers.KillContainerAsync(containerName, new());
+
+ await DockerClient.Containers.RemoveContainerAsync(containerName, new()
+ {
+ Force = true
+ });
+ }
+ catch (DockerContainerNotFoundException)
+ {
+ // Ignored
+ }
+ }
+
+ public async Task SubscribeExited(Func callback)
+ => await ExitEventSource.SubscribeAsync(callback);
+
+ public async Task RestoreAsync()
+ {
+ try
+ {
+ var containerName = string.Format(DockerConstants.InstallationNameTemplate, ServerContext.Configuration.Id);
+
+ var container = await DockerClient.Containers.InspectContainerAsync(containerName);
+ ContainerId = container.ID;
+ }
+ catch (DockerContainerNotFoundException)
+ {
+ // Ignore
+ }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await ContainerEventSubscription.DisposeAsync();
+ }
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/FileSystems/RawInstallationFs.cs b/MoonlightServers.Daemon/ServerSystem/FileSystems/RawInstallationFs.cs
new file mode 100644
index 0000000..f5ea780
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/FileSystems/RawInstallationFs.cs
@@ -0,0 +1,58 @@
+using MoonlightServers.Daemon.ServerSystem.Interfaces;
+using MoonlightServers.Daemon.ServerSystem.Models;
+
+namespace MoonlightServers.Daemon.ServerSystem.FileSystems;
+
+public class RawInstallationFs : IFileSystem
+{
+ private readonly string BaseDirectory;
+
+ public RawInstallationFs(ServerContext context)
+ {
+ BaseDirectory = Path.Combine(
+ Directory.GetCurrentDirectory(),
+ "storage",
+ "volumes",
+ context.Configuration.Id.ToString()
+ );
+ }
+
+ public Task InitializeAsync()
+ => Task.CompletedTask;
+
+ public Task GetPathAsync()
+ => Task.FromResult(BaseDirectory);
+
+ public Task CheckExistsAsync()
+ {
+ var exists = Directory.Exists(BaseDirectory);
+ return Task.FromResult(exists);
+ }
+
+ public Task CheckMountedAsync()
+ => Task.FromResult(true);
+
+ public Task CreateAsync()
+ {
+ Directory.CreateDirectory(BaseDirectory);
+ return Task.CompletedTask;
+ }
+
+ public Task PerformChecksAsync()
+ => Task.CompletedTask;
+
+ public Task MountAsync()
+ => Task.CompletedTask;
+
+ public Task UnmountAsync()
+ => Task.CompletedTask;
+
+ public Task DestroyAsync()
+ {
+ Directory.Delete(BaseDirectory, true);
+ return Task.CompletedTask;
+ }
+
+ public ValueTask DisposeAsync()
+ => ValueTask.CompletedTask;
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/FileSystems/RawRuntimeFs.cs b/MoonlightServers.Daemon/ServerSystem/FileSystems/RawRuntimeFs.cs
new file mode 100644
index 0000000..6f98ac7
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/FileSystems/RawRuntimeFs.cs
@@ -0,0 +1,58 @@
+using MoonlightServers.Daemon.ServerSystem.Interfaces;
+using MoonlightServers.Daemon.ServerSystem.Models;
+
+namespace MoonlightServers.Daemon.ServerSystem.FileSystems;
+
+public class RawRuntimeFs : IFileSystem
+{
+ private readonly string BaseDirectory;
+
+ public RawRuntimeFs(ServerContext context)
+ {
+ BaseDirectory = Path.Combine(
+ Directory.GetCurrentDirectory(),
+ "storage",
+ "volumes",
+ context.Configuration.Id.ToString()
+ );
+ }
+
+ public Task InitializeAsync()
+ => Task.CompletedTask;
+
+ public Task GetPathAsync()
+ => Task.FromResult(BaseDirectory);
+
+ public Task CheckExistsAsync()
+ {
+ var exists = Directory.Exists(BaseDirectory);
+ return Task.FromResult(exists);
+ }
+
+ public Task CheckMountedAsync()
+ => Task.FromResult(true);
+
+ public Task CreateAsync()
+ {
+ Directory.CreateDirectory(BaseDirectory);
+ return Task.CompletedTask;
+ }
+
+ public Task PerformChecksAsync()
+ => Task.CompletedTask;
+
+ public Task MountAsync()
+ => Task.CompletedTask;
+
+ public Task UnmountAsync()
+ => Task.CompletedTask;
+
+ public Task DestroyAsync()
+ {
+ Directory.Delete(BaseDirectory, true);
+ return Task.CompletedTask;
+ }
+
+ public ValueTask DisposeAsync()
+ => ValueTask.CompletedTask;
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Handlers/StartupHandler.cs b/MoonlightServers.Daemon/ServerSystem/Handlers/StartupHandler.cs
index 3fbfe62..b5e0846 100644
--- a/MoonlightServers.Daemon/ServerSystem/Handlers/StartupHandler.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Handlers/StartupHandler.cs
@@ -51,6 +51,9 @@ public class StartupHandler : IServerStateHandler
// 5. Create runtime
var hostPath = await Server.RuntimeFileSystem.GetPathAsync();
+ if (await Server.Runtime.CheckExistsAsync())
+ await Server.Runtime.DestroyAsync();
+
await Server.Runtime.CreateAsync(hostPath);
if (ExitSubscription == null)
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/RegexOnlineDetector.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/RegexOnlineDetector.cs
new file mode 100644
index 0000000..0f9a238
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/RegexOnlineDetector.cs
@@ -0,0 +1,61 @@
+using System.Text.RegularExpressions;
+using MoonlightServers.Daemon.ServerSystem.Interfaces;
+using MoonlightServers.Daemon.ServerSystem.Models;
+
+namespace MoonlightServers.Daemon.ServerSystem.Implementations;
+
+public class RegexOnlineDetector : IOnlineDetector
+{
+ private readonly ServerContext Context;
+ private readonly ILogger Logger;
+
+ private Regex? Expression;
+
+ public RegexOnlineDetector(ServerContext context, ILogger logger)
+ {
+ Context = context;
+ Logger = logger;
+ }
+
+ public Task InitializeAsync()
+ => Task.CompletedTask;
+
+ public Task CreateAsync()
+ {
+ if(string.IsNullOrEmpty(Context.Configuration.OnlineDetection))
+ return Task.CompletedTask;
+
+ try
+ {
+ Expression = new Regex(Context.Configuration.OnlineDetection, RegexOptions.Compiled);
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e, "An error occured while creating regex. Check the regex expression");
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task HandleOutputAsync(string line)
+ {
+ if (Expression == null)
+ return Task.FromResult(false);
+
+ var result = Expression.Matches(line).Count > 0;
+
+ return Task.FromResult(result);
+ }
+
+ public Task DestroyAsync()
+ {
+ Expression = null;
+ return Task.CompletedTask;
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ Expression = null;
+ return ValueTask.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/ServerReporter.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/ServerReporter.cs
new file mode 100644
index 0000000..7e3d63b
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/ServerReporter.cs
@@ -0,0 +1,45 @@
+using MoonlightServers.Daemon.ServerSystem.Interfaces;
+
+namespace MoonlightServers.Daemon.ServerSystem.Implementations;
+
+public class ServerReporter : IReporter
+{
+ private readonly IConsole Console;
+ private readonly ILogger Logger;
+
+ private const string StatusTemplate =
+ "\x1b[1;38;2;200;90;200mM\x1b[1;38;2;204;110;230mo\x1b[1;38;2;170;130;245mo\x1b[1;38;2;140;150;255mn\x1b[1;38;2;110;180;255ml\x1b[1;38;2;100;200;255mi\x1b[1;38;2;100;220;255mg\x1b[1;38;2;120;235;255mh\x1b[1;38;2;140;250;255mt\x1b[0m \x1b[3;38;2;200;200;200m{0}\x1b[0m\n\r";
+
+ private const string ErrorTemplate =
+ "\x1b[1;38;2;200;90;200mM\x1b[1;38;2;204;110;230mo\x1b[1;38;2;170;130;245mo\x1b[1;38;2;140;150;255mn\x1b[1;38;2;110;180;255ml\x1b[1;38;2;100;200;255mi\x1b[1;38;2;100;220;255mg\x1b[1;38;2;120;235;255mh\x1b[1;38;2;140;250;255mt\x1b[0m \x1b[1;38;2;255;0;0m{0}\x1b[0m\n\r";
+
+ public ServerReporter(IConsole console, ILogger logger)
+ {
+ Console = console;
+ Logger = logger;
+ }
+
+ public Task InitializeAsync()
+ => Task.CompletedTask;
+
+ public async Task StatusAsync(string message)
+ {
+ Logger.LogInformation("Status: {message}", message);
+
+ await Console.WriteStdOutAsync(
+ string.Format(StatusTemplate, message)
+ );
+ }
+
+ public async Task ErrorAsync(string message)
+ {
+ Logger.LogError("Error: {message}", message);
+
+ await Console.WriteStdOutAsync(
+ string.Format(ErrorTemplate, message)
+ );
+ }
+
+ public ValueTask DisposeAsync()
+ => ValueTask.CompletedTask;
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Interfaces/IConsole.cs b/MoonlightServers.Daemon/ServerSystem/Interfaces/IConsole.cs
index fe891e3..ba798ce 100644
--- a/MoonlightServers.Daemon/ServerSystem/Interfaces/IConsole.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Interfaces/IConsole.cs
@@ -7,7 +7,7 @@ public interface IConsole : IServerComponent
/// would write into the containers standard input.
/// This method does not add a newline separator at the end of the content. The caller needs to add this themselves if required
///
- /// The content to write
+ /// Content to write
///
public Task WriteStdInAsync(string content);
///
@@ -15,17 +15,9 @@ public interface IConsole : IServerComponent
/// would write into the containers standard output.
/// This method does not add a newline separator at the end of the content. The caller needs to add this themselves if required
///
- /// The content to write
+ /// Content to write
///
public Task WriteStdOutAsync(string content);
-
- ///
- /// Writes a system message to the standard output with the moonlight console prefix
- /// This method *does* add the newline separator at the end
- ///
- /// The content to write into the standard output
- ///
- public Task WriteMoonlightAsync(string content);
///
/// Attaches the console to the runtime environment
@@ -60,7 +52,7 @@ public interface IConsole : IServerComponent
///
/// Gets the content from the standard output cache
///
- /// The content from the cache
+ /// Content from the cache
public Task> GetCacheAsync();
///
@@ -68,5 +60,5 @@ public interface IConsole : IServerComponent
///
/// Callback which will be invoked whenever a new line is received
/// Subscription disposable to unsubscribe from the event
- public Task SubscribeStdOutAsync(Func callback);
+ public Task SubscribeStdOutAsync(Func callback);
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Interfaces/IInstallation.cs b/MoonlightServers.Daemon/ServerSystem/Interfaces/IInstallation.cs
index 1ac540d..27236dc 100644
--- a/MoonlightServers.Daemon/ServerSystem/Interfaces/IInstallation.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Interfaces/IInstallation.cs
@@ -1,3 +1,5 @@
+using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
+
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
public interface IInstallation : IServerComponent
@@ -11,10 +13,11 @@ public interface IInstallation : IServerComponent
///
/// Creates the installation environment
///
- /// The host path of the runtime storage location
- /// The host path of the installation file system
+ /// Host path of the runtime storage location
+ /// Host path of the installation file system
+ /// Installation data for the server
///
- public Task CreateAsync(string runtimePath, string hostPath);
+ public Task CreateAsync(string runtimePath, string hostPath, ServerInstallDataResponse data);
///
/// Starts the installation
@@ -37,9 +40,9 @@ public interface IInstallation : IServerComponent
///
/// Subscribes to the event when the installation exists
///
- /// The callback to invoke whenever the installation exists
+ /// Callback to invoke whenever the installation exists
/// Subscription disposable to unsubscribe from the event
- public Task SubscribeExited(Func callback);
+ public Task SubscribeExited(Func callback);
///
/// Connects an existing installation to this abstraction in order to restore it.
diff --git a/MoonlightServers.Daemon/ServerSystem/Interfaces/IOnlineDetector.cs b/MoonlightServers.Daemon/ServerSystem/Interfaces/IOnlineDetector.cs
index 21cf894..dc7314e 100644
--- a/MoonlightServers.Daemon/ServerSystem/Interfaces/IOnlineDetector.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Interfaces/IOnlineDetector.cs
@@ -11,7 +11,7 @@ public interface IOnlineDetector : IServerComponent
///
/// Handles the detection of the online state based on the received output
///
- /// The excerpt of the output
+ /// Excerpt of the output
/// True if the detection showed that the server is online. False if the detection didnt find anything
public Task HandleOutputAsync(string line);
diff --git a/MoonlightServers.Daemon/ServerSystem/Interfaces/IReporter.cs b/MoonlightServers.Daemon/ServerSystem/Interfaces/IReporter.cs
index 4ae4552..ba9f2bb 100644
--- a/MoonlightServers.Daemon/ServerSystem/Interfaces/IReporter.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Interfaces/IReporter.cs
@@ -5,14 +5,14 @@ public interface IReporter : IServerComponent
///
/// Writes both in the server logs as well in the server console the provided message as a status update
///
- /// The message to write
+ /// Message to write
///
public Task StatusAsync(string message);
///
/// Writes both in the server logs as well in the server console the provided message as an error
///
- /// The message to write
+ /// Message to write
///
public Task ErrorAsync(string message);
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Interfaces/IRuntime.cs b/MoonlightServers.Daemon/ServerSystem/Interfaces/IRuntime.cs
index 6490d9f..3a27b2d 100644
--- a/MoonlightServers.Daemon/ServerSystem/Interfaces/IRuntime.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Interfaces/IRuntime.cs
@@ -11,7 +11,7 @@ public interface IRuntime : IServerComponent
///
/// Creates the runtime with the specified path as the storage path where the server files should be stored in
///
- ///
+ /// Path where the server files are located
///
public Task CreateAsync(string path);
@@ -42,7 +42,7 @@ public interface IRuntime : IServerComponent
///
/// This subscribes to the exited event of the runtime
///
- /// The callback gets invoked whenever the runtime exites
+ /// Callback gets invoked whenever the runtime exites
/// Subscription disposable to unsubscribe from the event
public Task SubscribeExited(Func callback);
diff --git a/MoonlightServers.Daemon/ServerSystem/ServerFactory.cs b/MoonlightServers.Daemon/ServerSystem/ServerFactory.cs
index 78bac7f..1dc86cd 100644
--- a/MoonlightServers.Daemon/ServerSystem/ServerFactory.cs
+++ b/MoonlightServers.Daemon/ServerSystem/ServerFactory.cs
@@ -1,4 +1,7 @@
using MoonlightServers.Daemon.Models.Cache;
+using MoonlightServers.Daemon.ServerSystem.Docker;
+using MoonlightServers.Daemon.ServerSystem.FileSystems;
+using MoonlightServers.Daemon.ServerSystem.Implementations;
using MoonlightServers.Daemon.ServerSystem.Interfaces;
using MoonlightServers.Daemon.ServerSystem.Models;
@@ -27,7 +30,7 @@ public class ServerFactory
context.ServiceScope = scope;
// Define all required components
-
+
IConsole console;
IFileSystem runtimeFs;
IFileSystem installFs;
@@ -37,10 +40,18 @@ public class ServerFactory
IRestorer restorer;
IRuntime runtime;
IStatistics statistics;
-
+
// Resolve the components
+
+ console = ActivatorUtilities.CreateInstance(scope.ServiceProvider, logger);
+ reporter = ActivatorUtilities.CreateInstance(scope.ServiceProvider, console, logger);
+ runtimeFs = ActivatorUtilities.CreateInstance(scope.ServiceProvider, logger, reporter);
+ installFs = ActivatorUtilities.CreateInstance(scope.ServiceProvider, logger, reporter);
+ installation = ActivatorUtilities.CreateInstance(scope.ServiceProvider, logger, reporter);
+ onlineDetector = ActivatorUtilities.CreateInstance(scope.ServiceProvider, logger, reporter);
+
// TODO: Add a plugin hook for dynamically resolving components and checking if any is unset
-
+
// Resolve server from di
var server = new Server(
logger,
diff --git a/MoonlightServers.Daemon/Services/DockerEventService.cs b/MoonlightServers.Daemon/Services/DockerEventService.cs
index 4bb9f88..1b794dc 100644
--- a/MoonlightServers.Daemon/Services/DockerEventService.cs
+++ b/MoonlightServers.Daemon/Services/DockerEventService.cs
@@ -3,6 +3,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Docker.DotNet;
using Docker.DotNet.Models;
+using MoonCore.Events;
using MoonCore.Observability;
using MoonlightServers.Daemon.Helpers;
@@ -13,13 +14,9 @@ public class DockerEventService : BackgroundService
private readonly ILogger Logger;
private readonly DockerClient DockerClient;
- public IAsyncObservable OnContainerEvent => OnContainerSubject;
- public IAsyncObservable OnImageEvent => OnImageSubject;
- public IAsyncObservable OnNetworkEvent => OnNetworkSubject;
-
- private readonly EventSubject OnContainerSubject = new();
- private readonly EventSubject OnImageSubject = new();
- private readonly EventSubject OnNetworkSubject = new();
+ private readonly EventSource ContainerSource = new();
+ private readonly EventSource ImageSource = new();
+ private readonly EventSource NetworkSource = new();
public DockerEventService(
ILogger logger,
@@ -30,10 +27,19 @@ public class DockerEventService : BackgroundService
DockerClient = dockerClient;
}
+ public async ValueTask SubscribeContainerAsync(Func callback)
+ => await ContainerSource.SubscribeAsync(callback);
+
+ public async ValueTask SubscribeImageAsync(Func callback)
+ => await ImageSource.SubscribeAsync(callback);
+
+ public async ValueTask SubscribeNetworkAsync(Func callback)
+ => await NetworkSource.SubscribeAsync(callback);
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Logger.LogInformation("Starting docker event service");
-
+
while (!stoppingToken.IsCancellationRequested)
{
try
@@ -47,15 +53,15 @@ public class DockerEventService : BackgroundService
switch (message.Type)
{
case "container":
- await OnContainerSubject.OnNextAsync(message);
+ await ContainerSource.InvokeAsync(message);
break;
case "image":
- await OnImageSubject.OnNextAsync(message);
+ await ImageSource.InvokeAsync(message);
break;
case "network":
- await OnNetworkSubject.OnNextAsync(message);
+ await NetworkSource.InvokeAsync(message);
break;
}
}
@@ -76,16 +82,7 @@ public class DockerEventService : BackgroundService
Logger.LogError(e, "An error occured while listening for docker events: {message}", e.Message);
}
}
-
+
Logger.LogInformation("Stopping docker event service");
}
-
- public override void Dispose()
- {
- base.Dispose();
-
- OnContainerSubject.Dispose();
- OnImageSubject.Dispose();
- OnNetworkSubject.Dispose();
- }
}
\ No newline at end of file