diff --git a/MoonlightServers.Daemon/Configuration/DockerOptions.cs b/MoonlightServers.Daemon/Configuration/DockerOptions.cs
new file mode 100644
index 0000000..e527ccd
--- /dev/null
+++ b/MoonlightServers.Daemon/Configuration/DockerOptions.cs
@@ -0,0 +1,6 @@
+namespace MoonlightServers.Daemon.Configuration;
+
+public class DockerOptions
+{
+ public string SocketUri { get; set; } = "unix:///var/run/docker.sock";
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/Configuration/LocalStorageOptions.cs b/MoonlightServers.Daemon/Configuration/LocalStorageOptions.cs
new file mode 100644
index 0000000..a27c4f9
--- /dev/null
+++ b/MoonlightServers.Daemon/Configuration/LocalStorageOptions.cs
@@ -0,0 +1,7 @@
+namespace MoonlightServers.Daemon.Configuration;
+
+public class LocalStorageOptions
+{
+ public string InstallPath { get; set; } = "/data/install";
+ public string RuntimePath { get; set; } = "/data/runtime";
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj
index c55d216..aa8139d 100644
--- a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj
+++ b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj
@@ -23,6 +23,9 @@
Server.cs
+
+ Server.cs
+
diff --git a/MoonlightServers.Daemon/Program.cs b/MoonlightServers.Daemon/Program.cs
index 3267770..b068d23 100644
--- a/MoonlightServers.Daemon/Program.cs
+++ b/MoonlightServers.Daemon/Program.cs
@@ -14,6 +14,7 @@ builder.Logging.AddConsoleFormatter();
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddDockerServices();
builder.Services.AddLocalServices();
@@ -31,23 +32,26 @@ Task.Run(async () =>
try
{
- var factory = app.Services.GetRequiredService();
- var server = await factory.CreateAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
+ var serverService = app.Services.GetRequiredService();
- await server.InitializeAsync();
+ await serverService.SyncAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
+ await serverService.SyncAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
+
+ var server = await serverService.GetAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
+
+ if(server == null)
+ return;
Console.WriteLine($"Server: {server.State}");
- Console.ReadLine();
-
if (server.State == ServerState.Offline)
await server.StartAsync();
else
await server.StopAsync();
Console.ReadLine();
-
- await server.DisposeAsync();
+
+ await serverService.DeleteAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
}
catch (Exception e)
{
diff --git a/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStatistics.cs b/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStatistics.cs
index efc52f4..c564bfb 100644
--- a/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStatistics.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStatistics.cs
@@ -1,6 +1,6 @@
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
-public interface IInstallStatistics
+public interface IInstallStatistics : IAsyncDisposable
{
public event Func? OnStatisticsReceived;
diff --git a/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStorage.cs b/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStorage.cs
index 7fb07ac..e20ad74 100644
--- a/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStorage.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Abstractions/IInstallStorage.cs
@@ -2,5 +2,5 @@ namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
public interface IInstallStorage
{
- public Task GetHostPathAsync();
+ public Task GetBindPathAsync();
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStatistics.cs b/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStatistics.cs
index ab65549..acdb020 100644
--- a/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStatistics.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStatistics.cs
@@ -1,6 +1,6 @@
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
-public interface IRuntimeStatistics
+public interface IRuntimeStatistics : IAsyncDisposable
{
public event Func? OnStatisticsReceived;
diff --git a/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStorage.cs b/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStorage.cs
index d532125..399c8e6 100644
--- a/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStorage.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Abstractions/IRuntimeStorage.cs
@@ -2,5 +2,5 @@ namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
public interface IRuntimeStorage
{
- public Task GetHostPathAsync();
+ public Task GetBindPathAsync();
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/ConfigMapper.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/ContainerConfigMapper.cs
similarity index 96%
rename from MoonlightServers.Daemon/ServerSystem/Implementations/Docker/ConfigMapper.cs
rename to MoonlightServers.Daemon/ServerSystem/Implementations/Docker/ContainerConfigMapper.cs
index aae853c..7497ee5 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/ConfigMapper.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/ContainerConfigMapper.cs
@@ -1,12 +1,11 @@
-using System.ComponentModel;
using Docker.DotNet.Models;
using MoonlightServers.Daemon.Models;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
-public static class ConfigMapper
+public class ContainerConfigMapper
{
- public static CreateContainerParameters GetRuntimeConfig(
+ public CreateContainerParameters GetRuntimeConfig(
string uuid,
string name,
RuntimeConfiguration configuration,
@@ -138,7 +137,7 @@ public static class ConfigMapper
return parameters;
}
- public static CreateContainerParameters GetInstallConfig(
+ public CreateContainerParameters GetInstallConfig(
string uuid,
string name,
RuntimeConfiguration runtimeConfiguration,
@@ -201,7 +200,7 @@ public static class ConfigMapper
return parameters;
}
- private static void ApplySharedOptions(
+ private void ApplySharedOptions(
CreateContainerParameters parameters,
RuntimeConfiguration configuration
)
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerConsole.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerConsole.cs
index 087f69a..1e6c11f 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerConsole.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerConsole.cs
@@ -31,14 +31,15 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
Logger = logger;
}
+
public async Task AttachAsync()
{
- // Fetch initial logs
- Logger.LogTrace("Fetching pre-existing logs from container");
-
- var logResponse = await DockerClient.Containers.GetContainerLogsAsync(
+ // Fetch initial container logs
+ Logger.LogTrace("Fetching initial container logs");
+
+ using var logStream = await DockerClient.Containers.GetContainerLogsAsync(
ContainerId,
- new()
+ new ContainerLogsParameters()
{
Follow = false,
ShowStderr = true,
@@ -46,94 +47,50 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
}
);
- // Append to cache
- var logs = await logResponse.ReadOutputToEndAsync(Cts.Token);
+ // and process it
+ await ProcessStreamAsync(logStream, Cts.Token);
- await CacheLock.WaitAsync(Cts.Token);
-
- try
- {
- Cache.Add(logs.stdout);
- Cache.Add(logs.stderr);
- }
- finally
- {
- CacheLock.Release();
- }
-
- // Stream new logs
- Logger.LogTrace("Starting log streaming");
+ // After that we can actually start streaming the new logs
+ Logger.LogTrace("Attaching to container");
+ Stream = await DockerClient.Containers.AttachContainerAsync(
+ ContainerId,
+ new ContainerAttachParameters()
+ {
+ Stderr = true,
+ Stdin = true,
+ Stdout = true,
+ Stream = true
+ },
+ Cts.Token
+ );
+
Task.Run(async () =>
{
- var capturedCt = Cts.Token;
+ Logger.LogTrace("Entered streaming loop");
- Logger.LogTrace("Starting attach loop");
-
- while (!capturedCt.IsCancellationRequested)
+ while (!Cts.IsCancellationRequested)
{
try
{
- using var stream = await DockerClient.Containers.AttachContainerAsync(
- ContainerId,
- new ContainerAttachParameters()
- {
- Stderr = true,
- Stdin = true,
- Stdout = true,
- Stream = true
- },
- capturedCt
- );
-
- // Make stream accessible from the outside
- Stream = stream;
-
- const int bufferSize = 1024;
- var buffer = ArrayPool.Shared.Rent(bufferSize);
-
- while (!capturedCt.IsCancellationRequested)
+ if (Stream == null) // Triggers when e.g. a connection issue occurs cause the catch clause resets the stream
{
- try
- {
- var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, capturedCt);
+ Logger.LogTrace("Reattaching to container");
- if (readResult.Count > 0)
+ Stream = await DockerClient.Containers.AttachContainerAsync(
+ ContainerId,
+ new ContainerAttachParameters()
{
- var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
-
- await CacheLock.WaitAsync(capturedCt);
-
- try
- {
- if (Cache.Count > 300)
- Cache.RemoveRange(0, 50);
-
- Cache.Add(decodedBuffer);
- }
- finally
- {
- CacheLock.Release();
- }
-
- if (OnOutput != null)
- await OnOutput.Invoke(decodedBuffer);
- }
-
- if (readResult.EOF)
- break;
- }
- catch (OperationCanceledException)
- {
- // Ignored
- }
- catch (Exception e)
- {
- Logger.LogError(e, "An unhandled error occured while processing container stream");
- }
+ Stderr = true,
+ Stdin = true,
+ Stdout = true,
+ Stream = true
+ },
+ Cts.Token
+ );
}
- ArrayPool.Shared.Return(buffer);
+ await ProcessStreamAsync(Stream, Cts.Token);
}
catch (OperationCanceledException)
{
@@ -141,14 +98,57 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
}
catch (Exception e)
{
- Logger.LogError(e, "An unhandled error occured while handling container attaching");
+ Logger.LogError(e, "An unhandled error occured while processing container stream");
+ }
+ finally
+ {
+ Stream?.Dispose();
+ Stream = null;
}
}
- Logger.LogTrace("Attach loop exited");
+ Logger.LogTrace("Exited streaming loop");
});
}
+ private async Task ProcessStreamAsync(MultiplexedStream stream, CancellationToken cancellationToken)
+ {
+ const int bufferSize = 1024;
+ var buffer = ArrayPool.Shared.Rent(bufferSize);
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, cancellationToken);
+
+ if (readResult.Count > 0)
+ {
+ var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
+
+ await CacheLock.WaitAsync(cancellationToken);
+
+ try
+ {
+ if (Cache.Count > 300)
+ Cache.RemoveRange(0, 50);
+
+ Cache.Add(decodedBuffer);
+ }
+ finally
+ {
+ CacheLock.Release();
+ }
+
+ if (OnOutput != null)
+ await OnOutput.Invoke(decodedBuffer);
+ }
+
+ if (readResult.EOF)
+ break;
+ }
+
+ ArrayPool.Shared.Return(buffer);
+ }
+
public async Task WriteInputAsync(string value)
{
if (Stream == null)
@@ -188,6 +188,8 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
public async ValueTask DisposeAsync()
{
+ Logger.LogTrace("Disposing");
+
await Cts.CancelAsync();
Stream?.Dispose();
CacheLock.Dispose();
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnv.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnv.cs
index 6620b6b..009aa35 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnv.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnv.cs
@@ -27,7 +27,7 @@ public class DockerInstallEnv : IInstallEnvironment
Logger = logger;
EventService = eventService;
- InnerStatistics = new DockerStatistics();
+ InnerStatistics = new DockerStatistics(containerId, dockerClient, logger);
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
EventService.OnContainerDied += HandleDieEventAsync;
@@ -59,5 +59,6 @@ public class DockerInstallEnv : IInstallEnvironment
EventService.OnContainerDied -= HandleDieEventAsync;
await InnerConsole.DisposeAsync();
+ await InnerStatistics.DisposeAsync();
}
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnvService.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnvService.cs
index 13262d9..fc860cb 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnvService.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerInstallEnvService.cs
@@ -9,15 +9,21 @@ public class DockerInstallEnvService : IInstallEnvironmentService
private readonly DockerClient DockerClient;
private readonly ILoggerFactory LoggerFactory;
private readonly DockerEventService DockerEventService;
+ private readonly ContainerConfigMapper ConfigMapper;
private const string NameTemplate = "ml-install-{0}";
- public DockerInstallEnvService(DockerClient dockerClient, ILoggerFactory loggerFactory,
- DockerEventService dockerEventService)
+ public DockerInstallEnvService(
+ DockerClient dockerClient,
+ ILoggerFactory loggerFactory,
+ DockerEventService dockerEventService,
+ ContainerConfigMapper configMapper
+ )
{
DockerClient = dockerClient;
LoggerFactory = loggerFactory;
DockerEventService = dockerEventService;
+ ConfigMapper = configMapper;
}
public async Task FindAsync(string id)
@@ -28,8 +34,7 @@ public class DockerInstallEnvService : IInstallEnvironmentService
string.Format(NameTemplate, id)
);
- var logger =
- LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
+ var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
return new DockerInstallEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
}
@@ -63,8 +68,8 @@ public class DockerInstallEnvService : IInstallEnvironmentService
// Ignored
}
- var runtimeStoragePath = await runtimeStorage.GetHostPathAsync();
- var installStoragePath = await installStorage.GetHostPathAsync();
+ var runtimeStoragePath = await runtimeStorage.GetBindPathAsync();
+ var installStoragePath = await installStorage.GetBindPathAsync();
var parameters = ConfigMapper.GetInstallConfig(
id,
@@ -74,7 +79,7 @@ public class DockerInstallEnvService : IInstallEnvironmentService
runtimeStoragePath,
installStoragePath
);
-
+
var container = await DockerClient.Containers.CreateContainerAsync(parameters);
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnv.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnv.cs
index 7638926..8e85d91 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnv.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnv.cs
@@ -25,7 +25,7 @@ public class DockerRuntimeEnv : IRuntimeEnvironment
DockerClient = dockerClient;
EventService = eventService;
- InnerStatistics = new DockerStatistics();
+ InnerStatistics = new DockerStatistics(containerId, dockerClient, logger);
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
EventService.OnContainerDied += HandleDieEventAsync;
@@ -57,5 +57,6 @@ public class DockerRuntimeEnv : IRuntimeEnvironment
EventService.OnContainerDied -= HandleDieEventAsync;
await InnerConsole.DisposeAsync();
+ await InnerStatistics.DisposeAsync();
}
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnvService.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnvService.cs
index 7d1875a..8360991 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnvService.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerRuntimeEnvService.cs
@@ -9,15 +9,21 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
private readonly DockerClient DockerClient;
private readonly ILoggerFactory LoggerFactory;
private readonly DockerEventService DockerEventService;
+ private readonly ContainerConfigMapper ConfigMapper;
private const string NameTemplate = "ml-runtime-{0}";
- public DockerRuntimeEnvService(DockerClient dockerClient, ILoggerFactory loggerFactory,
- DockerEventService dockerEventService)
+ public DockerRuntimeEnvService(
+ DockerClient dockerClient,
+ ILoggerFactory loggerFactory,
+ DockerEventService dockerEventService,
+ ContainerConfigMapper configMapper
+ )
{
DockerClient = dockerClient;
LoggerFactory = loggerFactory;
DockerEventService = dockerEventService;
+ ConfigMapper = configMapper;
}
public async Task FindAsync(string id)
@@ -28,8 +34,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
string.Format(NameTemplate, id)
);
- var logger =
- LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
+ var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
return new DockerRuntimeEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
}
@@ -61,7 +66,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
// Ignored
}
- var storagePath = await storage.GetHostPathAsync();
+ var storagePath = await storage.GetBindPathAsync();
var parameters = ConfigMapper.GetRuntimeConfig(
id,
@@ -78,9 +83,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
}
public Task UpdateAsync(IRuntimeEnvironment environment, RuntimeConfiguration configuration)
- {
- throw new NotImplementedException();
- }
+ => Task.CompletedTask;
public async Task DeleteAsync(IRuntimeEnvironment environment)
{
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerStatistics.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerStatistics.cs
index 0740887..918a135 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerStatistics.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerStatistics.cs
@@ -1,3 +1,5 @@
+using Docker.DotNet;
+using Docker.DotNet.Models;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
@@ -6,9 +8,186 @@ public class DockerStatistics : IRuntimeStatistics, IInstallStatistics
{
public event Func? OnStatisticsReceived;
- public Task AttachAsync() => Task.CompletedTask;
+ private readonly string ContainerId;
+ private readonly DockerClient DockerClient;
+ private readonly ILogger Logger;
- public Task ClearCacheAsync() => Task.CompletedTask;
+ private readonly List Cache = new(102);
+ private readonly SemaphoreSlim CacheLock = new(1, 1);
+ private readonly CancellationTokenSource Cts = new();
- public Task GetCacheAsync() => Task.FromResult([]);
+ public DockerStatistics(string containerId, DockerClient dockerClient, ILogger logger)
+ {
+ ContainerId = containerId;
+ DockerClient = dockerClient;
+ Logger = logger;
+ }
+
+ public Task AttachAsync()
+ {
+ Task.Run(async () =>
+ {
+ Logger.LogTrace("Streaming loop entered");
+
+ while (!Cts.IsCancellationRequested)
+ {
+ try
+ {
+ await DockerClient.Containers.GetContainerStatsAsync(
+ ContainerId,
+ new ContainerStatsParameters()
+ {
+ Stream = true
+ },
+ new Progress(HandleStatsAsync),
+ Cts.Token
+ );
+ }
+ catch (OperationCanceledException)
+ {
+ // Ignored
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e, "An unhandled error occured while processing container stats");
+ }
+ }
+
+ Logger.LogTrace("Streaming loop exited");
+ });
+
+ return Task.CompletedTask;
+ }
+
+ private async void HandleStatsAsync(ContainerStatsResponse statsResponse)
+ {
+ try
+ {
+ var convertedStats = ToServerStatistics(statsResponse);
+
+ await CacheLock.WaitAsync();
+
+ try
+ {
+ if(Cache.Count > 100)
+ Cache.RemoveRange(0, 30);
+
+ Cache.Add(convertedStats);
+ }
+ finally
+ {
+ CacheLock.Release();
+ }
+
+ if (OnStatisticsReceived != null)
+ await OnStatisticsReceived.Invoke(convertedStats);
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e, "An unhandled error occured while handling container stats");
+ }
+ }
+
+ public async Task ClearCacheAsync()
+ {
+ await CacheLock.WaitAsync();
+
+ try
+ {
+ Cache.Clear();
+ }
+ finally
+ {
+ CacheLock.Release();
+ }
+ }
+
+ public async Task GetCacheAsync()
+ {
+ await CacheLock.WaitAsync();
+
+ try
+ {
+ return Cache.ToArray();
+ }
+ finally
+ {
+ CacheLock.Release();
+ }
+ }
+
+ // Helpers
+ public static ServerStatistics ToServerStatistics(ContainerStatsResponse stats)
+ {
+ double cpuUsage = CalculateCpuUsage(stats);
+
+ ulong usedMemory = stats.MemoryStats?.Usage ?? 0;
+ ulong totalMemory = stats.MemoryStats?.Limit ?? 0;
+
+ ulong outgoingNetwork = 0;
+ ulong ingoingNetwork = 0;
+
+ if (stats.Networks != null)
+ {
+ foreach (var network in stats.Networks.Values)
+ {
+ if (network == null)
+ continue;
+
+ outgoingNetwork += network.TxBytes;
+ ingoingNetwork += network.RxBytes;
+ }
+ }
+
+ ulong writeDisk = stats.StorageStats?.WriteSizeBytes ?? 0;
+ ulong readDisk = stats.StorageStats?.ReadSizeBytes ?? 0;
+
+ return new ServerStatistics(
+ CpuUsage: cpuUsage,
+ UsedMemory: usedMemory,
+ TotalMemory: totalMemory,
+ OutgoingNetwork: outgoingNetwork,
+ IngoingNetwork: ingoingNetwork,
+ WriteDisk: writeDisk,
+ ReadDisk: readDisk
+ );
+ }
+
+ private static double CalculateCpuUsage(ContainerStatsResponse stats)
+ {
+ var cpuStats = stats.CPUStats;
+ var preCpuStats = stats.PreCPUStats;
+
+ if (cpuStats == null || preCpuStats == null)
+ return 0;
+
+ var cpuUsage = cpuStats.CPUUsage;
+ var preCpuUsage = preCpuStats.CPUUsage;
+
+ if (cpuUsage == null || preCpuUsage == null)
+ return 0;
+
+ ulong cpuDelta = cpuUsage.TotalUsage - preCpuUsage.TotalUsage;
+ ulong systemDelta = cpuStats.SystemUsage - preCpuStats.SystemUsage;
+
+ if (systemDelta == 0 || cpuDelta == 0)
+ return 0;
+
+ uint onlineCpus = cpuStats.OnlineCPUs;
+
+ if (onlineCpus == 0 && cpuUsage.PercpuUsage != null)
+ {
+ onlineCpus = (uint)cpuUsage.PercpuUsage.Count;
+ }
+
+ if (onlineCpus == 0)
+ onlineCpus = 1;
+
+ return (double)cpuDelta / systemDelta * onlineCpus * 100.0;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await Cts.CancelAsync();
+ }
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/Extensions.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/Extensions.cs
index 992f685..3e663a7 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/Extensions.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/Extensions.cs
@@ -1,4 +1,6 @@
using Docker.DotNet;
+using Microsoft.Extensions.Options;
+using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
@@ -7,16 +9,23 @@ public static class Extensions
{
public static void AddDockerServices(this IServiceCollection collection)
{
- var client = new DockerClientBuilder()
- .WithEndpoint(new Uri("unix:///var/run/docker.sock"))
- .Build();
+ collection.AddOptions().BindConfiguration("Moonlight:Docker");
- collection.AddSingleton(client);
+ collection.AddSingleton(sp =>
+ {
+ var options = sp.GetRequiredService>();
+
+ return new DockerClientBuilder()
+ .WithEndpoint(new Uri(options.Value.SocketUri))
+ .Build();
+ });
collection.AddSingleton();
collection.AddHostedService(sp => sp.GetRequiredService());
collection.AddSingleton();
collection.AddSingleton();
+
+ collection.AddSingleton();
}
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/Extensions.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/Extensions.cs
index 8cc1e54..8f12fb8 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/Extensions.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/Extensions.cs
@@ -1,3 +1,4 @@
+using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
@@ -8,5 +9,7 @@ public static class Extensions
{
services.AddSingleton();
services.AddSingleton();
+
+ services.AddOptions().BindConfiguration("Moonlight:LocalStorage");
}
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorage.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorage.cs
index f016ee7..b8f2e7e 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorage.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorage.cs
@@ -4,12 +4,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalInstallStorage : IInstallStorage
{
- public string HostPath { get; }
+ public string BindPath { get; }
- public LocalInstallStorage(string hostPath)
+ public LocalInstallStorage(string bindPath)
{
- HostPath = hostPath;
+ BindPath = bindPath;
}
- public Task GetHostPathAsync() => Task.FromResult(HostPath);
+ public Task GetBindPathAsync() => Task.FromResult(BindPath);
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorageService.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorageService.cs
index df0f555..ced8f3e 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorageService.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalInstallStorageService.cs
@@ -1,3 +1,5 @@
+using Microsoft.Extensions.Options;
+using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
@@ -5,11 +7,16 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalInstallStorageService : IInstallStorageService
{
- private const string HostPathTemplate = "./mldaemon/install/{0}";
-
+ private readonly IOptions Options;
+
+ public LocalInstallStorageService(IOptions options)
+ {
+ Options = options;
+ }
+
public Task FindAsync(string id)
{
- var path = string.Format(HostPathTemplate, id);
+ var path = Path.Combine(Options.Value.InstallPath, id);
if (!Directory.Exists(path))
return Task.FromResult(null);
@@ -19,7 +26,7 @@ public class LocalInstallStorageService : IInstallStorageService
public Task CreateAsync(string id, RuntimeConfiguration runtimeConfiguration, InstallConfiguration installConfiguration)
{
- var path = string.Format(HostPathTemplate, id);
+ var path = Path.Combine(Options.Value.InstallPath, id);
Directory.CreateDirectory(path);
@@ -35,8 +42,8 @@ public class LocalInstallStorageService : IInstallStorageService
);
}
- if(Directory.Exists(localInstallStorage.HostPath))
- Directory.Delete(localInstallStorage.HostPath, true);
+ if(Directory.Exists(localInstallStorage.BindPath))
+ Directory.Delete(localInstallStorage.BindPath, true);
return Task.CompletedTask;
}
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorage.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorage.cs
index f780e94..ef0679d 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorage.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorage.cs
@@ -4,12 +4,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalRuntimeStorage : IRuntimeStorage
{
- public string HostPath { get; }
+ public string BindPath { get; }
- public LocalRuntimeStorage(string hostPath)
+ public LocalRuntimeStorage(string bindPath)
{
- HostPath = hostPath;
+ BindPath = bindPath;
}
- public Task GetHostPathAsync() => Task.FromResult(HostPath);
+ public Task GetBindPathAsync() => Task.FromResult(BindPath);
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorageService.cs b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorageService.cs
index 53943fd..484bafd 100644
--- a/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorageService.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Implementations/Local/LocalRuntimeStorageService.cs
@@ -1,3 +1,5 @@
+using Microsoft.Extensions.Options;
+using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
@@ -5,11 +7,16 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalRuntimeStorageService : IRuntimeStorageService
{
- private const string HostPathTemplate = "./mldaemon/runtime/{0}";
+ private readonly IOptions Options;
+
+ public LocalRuntimeStorageService(IOptions options)
+ {
+ Options = options;
+ }
public Task FindAsync(string id)
{
- var path = string.Format(HostPathTemplate, id);
+ var path = Path.Combine(Options.Value.RuntimePath, id);
if (!Directory.Exists(path))
return Task.FromResult(null);
@@ -19,7 +26,7 @@ public class LocalRuntimeStorageService : IRuntimeStorageService
public Task CreateAsync(string id, RuntimeConfiguration configuration)
{
- var path = string.Format(HostPathTemplate, id);
+ var path = Path.Combine(Options.Value.RuntimePath, id);
Directory.CreateDirectory(path);
@@ -38,8 +45,8 @@ public class LocalRuntimeStorageService : IRuntimeStorageService
);
}
- if(Directory.Exists(localRuntimeStorage.HostPath))
- Directory.Delete(localRuntimeStorage.HostPath, true);
+ if(Directory.Exists(localRuntimeStorage.BindPath))
+ Directory.Delete(localRuntimeStorage.BindPath, true);
return Task.CompletedTask;
}
diff --git a/MoonlightServers.Daemon/ServerSystem/Server.Install.cs b/MoonlightServers.Daemon/ServerSystem/Server.Install.cs
index aaa31f2..607eabf 100644
--- a/MoonlightServers.Daemon/ServerSystem/Server.Install.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Server.Install.cs
@@ -74,7 +74,7 @@ public partial class Server
InstallStorage = await InstallStorageService.CreateAsync(Uuid, RuntimeConfiguration, InstallConfiguration);
// Write install script
- var installStoragePath = await InstallStorage.GetHostPathAsync();
+ var installStoragePath = await InstallStorage.GetBindPathAsync();
await File.WriteAllTextAsync(
Path.Combine(installStoragePath, "install.sh"),
diff --git a/MoonlightServers.Daemon/ServerSystem/Server.Update.cs b/MoonlightServers.Daemon/ServerSystem/Server.Update.cs
new file mode 100644
index 0000000..537f4bb
--- /dev/null
+++ b/MoonlightServers.Daemon/ServerSystem/Server.Update.cs
@@ -0,0 +1,34 @@
+namespace MoonlightServers.Daemon.ServerSystem;
+
+public partial class Server
+{
+ public async Task UpdateAsync()
+ {
+ await Lock.WaitAsync();
+
+ try
+ {
+ if (State is ServerState.Online or ServerState.Starting or ServerState.Stopping)
+ {
+ Logger.LogTrace("Fetching latest runtime configuration");
+ RuntimeConfiguration = await ConfigurationService.GetRuntimeConfigurationAsync(Uuid);
+
+ if (RuntimeEnvironment != null)
+ {
+ Logger.LogTrace("Updating runtime environment");
+ await RuntimeEnvironmentService.UpdateAsync(RuntimeEnvironment, RuntimeConfiguration);
+ }
+
+ if (RuntimeStorage != null)
+ {
+ Logger.LogTrace("Updating runtime storage");
+ await RuntimeStorageService.UpdateAsync(RuntimeStorage, RuntimeConfiguration);
+ }
+ }
+ }
+ finally
+ {
+ Lock.Release();
+ }
+ }
+}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/ServerSystem/Server.cs b/MoonlightServers.Daemon/ServerSystem/Server.cs
index 87b7ec8..600f040 100644
--- a/MoonlightServers.Daemon/ServerSystem/Server.cs
+++ b/MoonlightServers.Daemon/ServerSystem/Server.cs
@@ -67,11 +67,12 @@ public partial class Server : IAsyncDisposable
private async Task OnConsoleMessageAsync(string message)
{
- Console.WriteLine($"Console: {message}");
+ Console.Write($"Console: {message}");
}
private async Task OnStatisticsReceivedAsync(ServerStatistics statistics)
{
+ Logger.LogTrace("{cpu:F} {used:F} {total:F}", statistics.CpuUsage, statistics.UsedMemory / 1024 / 1024, statistics.TotalMemory / 1024 / 1024);
}
private Task ChangeStateAsync(ServerState newState)
@@ -88,7 +89,7 @@ public partial class Server : IAsyncDisposable
if (RuntimeEnvironment != null)
{
- Logger.LogTrace("Detaching and disposing runtime environment");
+ Logger.LogTrace("Detaching events and disposing runtime environment");
RuntimeEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
RuntimeEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
@@ -99,7 +100,7 @@ public partial class Server : IAsyncDisposable
if (InstallEnvironment != null)
{
- Logger.LogTrace("Detaching and disposing install environment");
+ Logger.LogTrace("Detaching events and disposing install environment");
InstallEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
InstallEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
diff --git a/MoonlightServers.Daemon/ServerSystem/ServerStatistics.cs b/MoonlightServers.Daemon/ServerSystem/ServerStatistics.cs
index a513812..0a4bd7b 100644
--- a/MoonlightServers.Daemon/ServerSystem/ServerStatistics.cs
+++ b/MoonlightServers.Daemon/ServerSystem/ServerStatistics.cs
@@ -1,3 +1,11 @@
namespace MoonlightServers.Daemon.ServerSystem;
-public record ServerStatistics();
\ No newline at end of file
+public record ServerStatistics(
+ double CpuUsage,
+ ulong UsedMemory,
+ ulong TotalMemory,
+ ulong OutgoingNetwork,
+ ulong IngoingNetwork,
+ ulong WriteDisk,
+ ulong ReadDisk
+);
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/Services/ServerConfigurationService.cs b/MoonlightServers.Daemon/Services/ServerConfigurationService.cs
index 02af6c5..3c79dff 100644
--- a/MoonlightServers.Daemon/Services/ServerConfigurationService.cs
+++ b/MoonlightServers.Daemon/Services/ServerConfigurationService.cs
@@ -44,7 +44,7 @@ public class ServerConfigurationService
return new InstallConfiguration(
"bash",
"installer",
- await File.ReadAllTextAsync("/home/chiara/Documents/daemonScripts/install.sh")
+ "#!/bin/bash\n# Paper Installation Script\n#\necho -e \"Started Installation\"\n\n# Server Files: /mnt/server\nPROJECT=paper\n\nif [ \"${MINECRAFT_VERSION}\" == \"latest\" ]; then\n LATEST_VERSION=$(curl -s https://api.papermc.io/v2/projects/${PROJECT} | jq -r '.versions[-1]')\n MINECRAFT_VERSION=${LATEST_VERSION}\nfi\n\nGET_BUILD=$(curl -s https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION} | jq -r '.builds[-1]')\nBUILD_NUMBER=${GET_BUILD}\n\n\nJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\n\necho \"Version being downloaded\"\necho -e \"MC Version: ${MINECRAFT_VERSION}\"\necho -e \"Build: ${BUILD_NUMBER}\"\necho -e \"JAR Name of Build: ${JAR_NAME}\"\nDOWNLOAD_URL=https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds/${BUILD_NUMBER}/downloads/${JAR_NAME}\n\ncd /mnt/server\n\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\n\nif [ -f ${SERVER_JARFILE} ]; then\n mv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\nfi\n\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\n"
);
}
}
\ No newline at end of file
diff --git a/MoonlightServers.Daemon/Services/ServerService.cs b/MoonlightServers.Daemon/Services/ServerService.cs
new file mode 100644
index 0000000..a7afea1
--- /dev/null
+++ b/MoonlightServers.Daemon/Services/ServerService.cs
@@ -0,0 +1,67 @@
+using System.Collections.Concurrent;
+using MoonlightServers.Daemon.ServerSystem;
+
+namespace MoonlightServers.Daemon.Services;
+
+public class ServerService : IAsyncDisposable
+{
+ private readonly ServerFactory ServerFactory;
+ private readonly ConcurrentDictionary Servers = new();
+ private readonly ILogger Logger;
+
+ public ServerService(ServerFactory serverFactory, ILogger logger)
+ {
+ ServerFactory = serverFactory;
+ Logger = logger;
+ }
+
+ public async Task SyncAsync(string uuid)
+ {
+ if (Servers.TryGetValue(uuid, out var server))
+ {
+ Logger.LogTrace("Updating existing server {uuid}", uuid);
+
+ await server.UpdateAsync();
+ }
+ else
+ {
+ Logger.LogTrace("Creating new server instance {uuid}", uuid);
+
+ var newServer = await ServerFactory.CreateAsync(uuid);
+ await newServer.InitializeAsync();
+
+ Servers[uuid] = newServer;
+ }
+ }
+
+ public Task GetAsync(string uuid)
+ {
+ var server = Servers.GetValueOrDefault(uuid);
+ return Task.FromResult(server);
+ }
+
+ public async Task DeleteAsync(string uuid)
+ {
+ Logger.LogTrace("Deleting server {uuid}", uuid);
+
+ var server = Servers.GetValueOrDefault(uuid);
+
+ if(server == null)
+ return;
+
+ // Trigger internal deletion mechanism
+ await server.DeleteAsync();
+
+ // Dispose any left over resources
+ await server.DisposeAsync();
+
+ // Remove server from cache
+ Servers.TryRemove(uuid, out _);
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ foreach (var server in Servers)
+ await server.Value.DisposeAsync();
+ }
+}
\ No newline at end of file