Improved comments. Started implementing docker components and other base components. Updated dependencies
This commit is contained in:
@@ -9,19 +9,16 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
||||
<PackageReference Include="MoonCore" Version="1.9.4" />
|
||||
<PackageReference Include="MoonCore.Extended" Version="1.3.6" />
|
||||
<PackageReference Include="MoonCore" Version="1.9.7" />
|
||||
<PackageReference Include="MoonCore.Extended" Version="1.3.7" />
|
||||
<PackageReference Include="MoonCore.Unix" Version="1.0.8" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Stateless" Version="5.17.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1"/>
|
||||
<PackageReference Include="System.Reactive.Async" Version="6.0.0-alpha.18" />
|
||||
<PackageReference Include="Stateless" Version="5.19.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Http\Controllers\Servers\" />
|
||||
<Folder Include="Http\Middleware\" />
|
||||
<Folder Include="ServerSystem\Docker\Components\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
203
MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs
Normal file
203
MoonlightServers.Daemon/ServerSystem/Docker/DockerConsole.cs
Normal file
@@ -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<string> StdOutEventSource = new();
|
||||
private readonly ConcurrentList<string> 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<IEnumerable<string>> GetCacheAsync()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(StdOutCache);
|
||||
}
|
||||
|
||||
public async Task<IAsyncDisposable> SubscribeStdOutAsync(Func<string, ValueTask> callback)
|
||||
=> await StdOutEventSource.SubscribeAsync(callback);
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (!Cts.IsCancellationRequested)
|
||||
await Cts.CancelAsync();
|
||||
|
||||
if (BaseStream != null)
|
||||
BaseStream.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
@@ -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<int> 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<bool> 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<IAsyncDisposable> SubscribeExited(Func<int, ValueTask> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<string> GetPathAsync()
|
||||
=> Task.FromResult(BaseDirectory);
|
||||
|
||||
public Task<bool> CheckExistsAsync()
|
||||
{
|
||||
var exists = Directory.Exists(BaseDirectory);
|
||||
return Task.FromResult(exists);
|
||||
}
|
||||
|
||||
public Task<bool> 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;
|
||||
}
|
||||
@@ -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<string> GetPathAsync()
|
||||
=> Task.FromResult(BaseDirectory);
|
||||
|
||||
public Task<bool> CheckExistsAsync()
|
||||
{
|
||||
var exists = Directory.Exists(BaseDirectory);
|
||||
return Task.FromResult(exists);
|
||||
}
|
||||
|
||||
public Task<bool> 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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<bool> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ public interface IConsole : IServerComponent
|
||||
/// would write into the containers standard input.
|
||||
/// <remarks>This method does not add a newline separator at the end of the content. The caller needs to add this themselves if required</remarks>
|
||||
/// </summary>
|
||||
/// <param name="content">The content to write</param>
|
||||
/// <param name="content">Content to write</param>
|
||||
/// <returns></returns>
|
||||
public Task WriteStdInAsync(string content);
|
||||
/// <summary>
|
||||
@@ -15,17 +15,9 @@ public interface IConsole : IServerComponent
|
||||
/// would write into the containers standard output.
|
||||
/// <remarks>This method does not add a newline separator at the end of the content. The caller needs to add this themselves if required</remarks>
|
||||
/// </summary>
|
||||
/// <param name="content">The content to write</param>
|
||||
/// <param name="content">Content to write</param>
|
||||
/// <returns></returns>
|
||||
public Task WriteStdOutAsync(string content);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a system message to the standard output with the moonlight console prefix
|
||||
/// <remarks>This method *does* add the newline separator at the end</remarks>
|
||||
/// </summary>
|
||||
/// <param name="content">The content to write into the standard output</param>
|
||||
/// <returns></returns>
|
||||
public Task WriteMoonlightAsync(string content);
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the console to the runtime environment
|
||||
@@ -60,7 +52,7 @@ public interface IConsole : IServerComponent
|
||||
/// <summary>
|
||||
/// Gets the content from the standard output cache
|
||||
/// </summary>
|
||||
/// <returns>The content from the cache</returns>
|
||||
/// <returns>Content from the cache</returns>
|
||||
public Task<IEnumerable<string>> GetCacheAsync();
|
||||
|
||||
/// <summary>
|
||||
@@ -68,5 +60,5 @@ public interface IConsole : IServerComponent
|
||||
/// </summary>
|
||||
/// <param name="callback">Callback which will be invoked whenever a new line is received</param>
|
||||
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||
public Task<IAsyncDisposable> SubscribeStdOutAsync(Func<string, Task> callback);
|
||||
public Task<IAsyncDisposable> SubscribeStdOutAsync(Func<string, ValueTask> callback);
|
||||
}
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Creates the installation environment
|
||||
/// </summary>
|
||||
/// <param name="runtimePath">The host path of the runtime storage location</param>
|
||||
/// <param name="hostPath">The host path of the installation file system</param>
|
||||
/// <param name="runtimePath">Host path of the runtime storage location</param>
|
||||
/// <param name="hostPath">Host path of the installation file system</param>
|
||||
/// <param name="data">Installation data for the server</param>
|
||||
/// <returns></returns>
|
||||
public Task CreateAsync(string runtimePath, string hostPath);
|
||||
public Task CreateAsync(string runtimePath, string hostPath, ServerInstallDataResponse data);
|
||||
|
||||
/// <summary>
|
||||
/// Starts the installation
|
||||
@@ -37,9 +40,9 @@ public interface IInstallation : IServerComponent
|
||||
/// <summary>
|
||||
/// Subscribes to the event when the installation exists
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to invoke whenever the installation exists</param>
|
||||
/// <param name="callback">Callback to invoke whenever the installation exists</param>
|
||||
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||
public Task<IAsyncDisposable> SubscribeExited(Func<int, Task> callback);
|
||||
public Task<IAsyncDisposable> SubscribeExited(Func<int, ValueTask> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Connects an existing installation to this abstraction in order to restore it.
|
||||
|
||||
@@ -11,7 +11,7 @@ public interface IOnlineDetector : IServerComponent
|
||||
/// <summary>
|
||||
/// Handles the detection of the online state based on the received output
|
||||
/// </summary>
|
||||
/// <param name="line">The excerpt of the output</param>
|
||||
/// <param name="line">Excerpt of the output</param>
|
||||
/// <returns>True if the detection showed that the server is online. False if the detection didnt find anything</returns>
|
||||
public Task<bool> HandleOutputAsync(string line);
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ public interface IReporter : IServerComponent
|
||||
/// <summary>
|
||||
/// Writes both in the server logs as well in the server console the provided message as a status update
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write</param>
|
||||
/// <param name="message">Message to write</param>
|
||||
/// <returns></returns>
|
||||
public Task StatusAsync(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Writes both in the server logs as well in the server console the provided message as an error
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write</param>
|
||||
/// <param name="message">Message to write</param>
|
||||
/// <returns></returns>
|
||||
public Task ErrorAsync(string message);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public interface IRuntime : IServerComponent
|
||||
/// <summary>
|
||||
/// Creates the runtime with the specified path as the storage path where the server files should be stored in
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="path">Path where the server files are located</param>
|
||||
/// <returns></returns>
|
||||
public Task CreateAsync(string path);
|
||||
|
||||
@@ -42,7 +42,7 @@ public interface IRuntime : IServerComponent
|
||||
/// <summary>
|
||||
/// This subscribes to the exited event of the runtime
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback gets invoked whenever the runtime exites</param>
|
||||
/// <param name="callback">Callback gets invoked whenever the runtime exites</param>
|
||||
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||
public Task<IAsyncDisposable> SubscribeExited(Func<int, Task> callback);
|
||||
|
||||
|
||||
@@ -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<DockerConsole>(scope.ServiceProvider, logger);
|
||||
reporter = ActivatorUtilities.CreateInstance<ServerReporter>(scope.ServiceProvider, console, logger);
|
||||
runtimeFs = ActivatorUtilities.CreateInstance<RawRuntimeFs>(scope.ServiceProvider, logger, reporter);
|
||||
installFs = ActivatorUtilities.CreateInstance<RawInstallationFs>(scope.ServiceProvider, logger, reporter);
|
||||
installation = ActivatorUtilities.CreateInstance<DockerInstallation>(scope.ServiceProvider, logger, reporter);
|
||||
onlineDetector = ActivatorUtilities.CreateInstance<RegexOnlineDetector>(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,
|
||||
|
||||
@@ -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<DockerEventService> Logger;
|
||||
private readonly DockerClient DockerClient;
|
||||
|
||||
public IAsyncObservable<Message> OnContainerEvent => OnContainerSubject;
|
||||
public IAsyncObservable<Message> OnImageEvent => OnImageSubject;
|
||||
public IAsyncObservable<Message> OnNetworkEvent => OnNetworkSubject;
|
||||
|
||||
private readonly EventSubject<Message> OnContainerSubject = new();
|
||||
private readonly EventSubject<Message> OnImageSubject = new();
|
||||
private readonly EventSubject<Message> OnNetworkSubject = new();
|
||||
private readonly EventSource<Message> ContainerSource = new();
|
||||
private readonly EventSource<Message> ImageSource = new();
|
||||
private readonly EventSource<Message> NetworkSource = new();
|
||||
|
||||
public DockerEventService(
|
||||
ILogger<DockerEventService> logger,
|
||||
@@ -30,10 +27,19 @@ public class DockerEventService : BackgroundService
|
||||
DockerClient = dockerClient;
|
||||
}
|
||||
|
||||
public async ValueTask<IAsyncDisposable> SubscribeContainerAsync(Func<Message, ValueTask> callback)
|
||||
=> await ContainerSource.SubscribeAsync(callback);
|
||||
|
||||
public async ValueTask<IAsyncDisposable> SubscribeImageAsync(Func<Message, ValueTask> callback)
|
||||
=> await ImageSource.SubscribeAsync(callback);
|
||||
|
||||
public async ValueTask<IAsyncDisposable> SubscribeNetworkAsync(Func<Message, ValueTask> 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user