Improved comments. Started implementing docker components and other base components. Updated dependencies
This commit is contained in:
@@ -9,19 +9,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
||||||
<PackageReference Include="MoonCore" Version="1.9.4" />
|
<PackageReference Include="MoonCore" Version="1.9.7" />
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.3.6" />
|
<PackageReference Include="MoonCore.Extended" Version="1.3.7" />
|
||||||
<PackageReference Include="MoonCore.Unix" Version="1.0.8" />
|
<PackageReference Include="MoonCore.Unix" Version="1.0.8" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="Stateless" Version="5.17.0" />
|
<PackageReference Include="Stateless" Version="5.19.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1"/>
|
|
||||||
<PackageReference Include="System.Reactive.Async" Version="6.0.0-alpha.18" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Http\Controllers\Servers\" />
|
<Folder Include="Http\Controllers\Servers\" />
|
||||||
<Folder Include="Http\Middleware\" />
|
<Folder Include="Http\Middleware\" />
|
||||||
<Folder Include="ServerSystem\Docker\Components\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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
|
// 5. Create runtime
|
||||||
var hostPath = await Server.RuntimeFileSystem.GetPathAsync();
|
var hostPath = await Server.RuntimeFileSystem.GetPathAsync();
|
||||||
|
|
||||||
|
if (await Server.Runtime.CheckExistsAsync())
|
||||||
|
await Server.Runtime.DestroyAsync();
|
||||||
|
|
||||||
await Server.Runtime.CreateAsync(hostPath);
|
await Server.Runtime.CreateAsync(hostPath);
|
||||||
|
|
||||||
if (ExitSubscription == null)
|
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.
|
/// 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>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="content">The content to write</param>
|
/// <param name="content">Content to write</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task WriteStdInAsync(string content);
|
public Task WriteStdInAsync(string content);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -15,18 +15,10 @@ public interface IConsole : IServerComponent
|
|||||||
/// would write into the containers standard output.
|
/// 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>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="content">The content to write</param>
|
/// <param name="content">Content to write</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task WriteStdOutAsync(string content);
|
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>
|
/// <summary>
|
||||||
/// Attaches the console to the runtime environment
|
/// Attaches the console to the runtime environment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -60,7 +52,7 @@ public interface IConsole : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the content from the standard output cache
|
/// Gets the content from the standard output cache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The content from the cache</returns>
|
/// <returns>Content from the cache</returns>
|
||||||
public Task<IEnumerable<string>> GetCacheAsync();
|
public Task<IEnumerable<string>> GetCacheAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -68,5 +60,5 @@ public interface IConsole : IServerComponent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callback">Callback which will be invoked whenever a new line is received</param>
|
/// <param name="callback">Callback which will be invoked whenever a new line is received</param>
|
||||||
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
/// <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;
|
namespace MoonlightServers.Daemon.ServerSystem.Interfaces;
|
||||||
|
|
||||||
public interface IInstallation : IServerComponent
|
public interface IInstallation : IServerComponent
|
||||||
@@ -11,10 +13,11 @@ public interface IInstallation : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the installation environment
|
/// Creates the installation environment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="runtimePath">The host path of the runtime storage location</param>
|
/// <param name="runtimePath">Host path of the runtime storage location</param>
|
||||||
/// <param name="hostPath">The host path of the installation file system</param>
|
/// <param name="hostPath">Host path of the installation file system</param>
|
||||||
|
/// <param name="data">Installation data for the server</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task CreateAsync(string runtimePath, string hostPath);
|
public Task CreateAsync(string runtimePath, string hostPath, ServerInstallDataResponse data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the installation
|
/// Starts the installation
|
||||||
@@ -37,9 +40,9 @@ public interface IInstallation : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscribes to the event when the installation exists
|
/// Subscribes to the event when the installation exists
|
||||||
/// </summary>
|
/// </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>
|
/// <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>
|
/// <summary>
|
||||||
/// Connects an existing installation to this abstraction in order to restore it.
|
/// Connects an existing installation to this abstraction in order to restore it.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public interface IOnlineDetector : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the detection of the online state based on the received output
|
/// Handles the detection of the online state based on the received output
|
||||||
/// </summary>
|
/// </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>
|
/// <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);
|
public Task<bool> HandleOutputAsync(string line);
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ public interface IReporter : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes both in the server logs as well in the server console the provided message as a status update
|
/// Writes both in the server logs as well in the server console the provided message as a status update
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message to write</param>
|
/// <param name="message">Message to write</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task StatusAsync(string message);
|
public Task StatusAsync(string message);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes both in the server logs as well in the server console the provided message as an error
|
/// Writes both in the server logs as well in the server console the provided message as an error
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message to write</param>
|
/// <param name="message">Message to write</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task ErrorAsync(string message);
|
public Task ErrorAsync(string message);
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ public interface IRuntime : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the runtime with the specified path as the storage path where the server files should be stored in
|
/// Creates the runtime with the specified path as the storage path where the server files should be stored in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path"></param>
|
/// <param name="path">Path where the server files are located</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task CreateAsync(string path);
|
public Task CreateAsync(string path);
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ public interface IRuntime : IServerComponent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This subscribes to the exited event of the runtime
|
/// This subscribes to the exited event of the runtime
|
||||||
/// </summary>
|
/// </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>
|
/// <returns>Subscription disposable to unsubscribe from the event</returns>
|
||||||
public Task<IAsyncDisposable> SubscribeExited(Func<int, Task> callback);
|
public Task<IAsyncDisposable> SubscribeExited(Func<int, Task> callback);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using MoonlightServers.Daemon.Models.Cache;
|
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.Interfaces;
|
||||||
using MoonlightServers.Daemon.ServerSystem.Models;
|
using MoonlightServers.Daemon.ServerSystem.Models;
|
||||||
|
|
||||||
@@ -39,6 +42,14 @@ public class ServerFactory
|
|||||||
IStatistics statistics;
|
IStatistics statistics;
|
||||||
|
|
||||||
// Resolve the components
|
// 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
|
// TODO: Add a plugin hook for dynamically resolving components and checking if any is unset
|
||||||
|
|
||||||
// Resolve server from di
|
// Resolve server from di
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Reactive.Linq;
|
|||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
using Docker.DotNet;
|
using Docker.DotNet;
|
||||||
using Docker.DotNet.Models;
|
using Docker.DotNet.Models;
|
||||||
|
using MoonCore.Events;
|
||||||
using MoonCore.Observability;
|
using MoonCore.Observability;
|
||||||
using MoonlightServers.Daemon.Helpers;
|
using MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
@@ -13,13 +14,9 @@ public class DockerEventService : BackgroundService
|
|||||||
private readonly ILogger<DockerEventService> Logger;
|
private readonly ILogger<DockerEventService> Logger;
|
||||||
private readonly DockerClient DockerClient;
|
private readonly DockerClient DockerClient;
|
||||||
|
|
||||||
public IAsyncObservable<Message> OnContainerEvent => OnContainerSubject;
|
private readonly EventSource<Message> ContainerSource = new();
|
||||||
public IAsyncObservable<Message> OnImageEvent => OnImageSubject;
|
private readonly EventSource<Message> ImageSource = new();
|
||||||
public IAsyncObservable<Message> OnNetworkEvent => OnNetworkSubject;
|
private readonly EventSource<Message> NetworkSource = new();
|
||||||
|
|
||||||
private readonly EventSubject<Message> OnContainerSubject = new();
|
|
||||||
private readonly EventSubject<Message> OnImageSubject = new();
|
|
||||||
private readonly EventSubject<Message> OnNetworkSubject = new();
|
|
||||||
|
|
||||||
public DockerEventService(
|
public DockerEventService(
|
||||||
ILogger<DockerEventService> logger,
|
ILogger<DockerEventService> logger,
|
||||||
@@ -30,6 +27,15 @@ public class DockerEventService : BackgroundService
|
|||||||
DockerClient = dockerClient;
|
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)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Starting docker event service");
|
Logger.LogInformation("Starting docker event service");
|
||||||
@@ -47,15 +53,15 @@ public class DockerEventService : BackgroundService
|
|||||||
switch (message.Type)
|
switch (message.Type)
|
||||||
{
|
{
|
||||||
case "container":
|
case "container":
|
||||||
await OnContainerSubject.OnNextAsync(message);
|
await ContainerSource.InvokeAsync(message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "image":
|
case "image":
|
||||||
await OnImageSubject.OnNextAsync(message);
|
await ImageSource.InvokeAsync(message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "network":
|
case "network":
|
||||||
await OnNetworkSubject.OnNextAsync(message);
|
await NetworkSource.InvokeAsync(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,13 +85,4 @@ public class DockerEventService : BackgroundService
|
|||||||
|
|
||||||
Logger.LogInformation("Stopping docker event service");
|
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