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