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)
|
||||
{
|
||||
foreach (var provider in ServiceProviders)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = provider.GetService(serviceType);
|
||||
|
||||
if (service != null)
|
||||
return service;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||
<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.Unix" Version="1.0.8" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
|
||||
@@ -2,8 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
|
||||
public interface IConsole : IServerComponent
|
||||
{
|
||||
public IAsyncObservable<string> OnOutput { get; }
|
||||
public IAsyncObservable<string> OnInput { get; }
|
||||
public IObservable<string> OnOutput { get; }
|
||||
public IObservable<string> OnInput { get; }
|
||||
|
||||
public Task AttachToRuntime();
|
||||
public Task AttachToInstallation();
|
||||
|
||||
@@ -2,7 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
|
||||
public interface IInstaller : IServerComponent
|
||||
{
|
||||
public IAsyncObservable<object> OnExited { get; set; }
|
||||
public IObservable<object> OnExited { get; }
|
||||
public bool IsRunning { get; }
|
||||
|
||||
public Task Start();
|
||||
public Task Abort();
|
||||
|
||||
@@ -2,7 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
|
||||
public interface IProvisioner : IServerComponent
|
||||
{
|
||||
public IAsyncObservable<object> OnExited { get; set; }
|
||||
public IObservable<object> OnExited { get; }
|
||||
public bool IsProvisioned { get; }
|
||||
|
||||
public Task Provision();
|
||||
public Task Start();
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
|
||||
public interface IStatistics : IServerComponent
|
||||
{
|
||||
public IAsyncObservable<ServerStats> Stats { get; }
|
||||
public IAsyncObservable<ServerStats> OnStats { get; }
|
||||
|
||||
public Task SubscribeToRuntime();
|
||||
public Task SubscribeToInstallation();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Reactive.Subjects;
|
||||
using MoonlightServers.Daemon.ServerSystem;
|
||||
using Stateless;
|
||||
|
||||
@@ -12,11 +13,14 @@ public class Server : IAsyncDisposable
|
||||
public IRestorer Restorer { get; private set; }
|
||||
public IStatistics Statistics { 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 IAsyncDisposable? ProvisionExitSubscription;
|
||||
private IAsyncDisposable? InstallerExitSubscription;
|
||||
private IDisposable? ProvisionExitSubscription;
|
||||
private IDisposable? InstallerExitSubscription;
|
||||
|
||||
public Server(
|
||||
ILogger<Server> logger,
|
||||
@@ -26,7 +30,7 @@ public class Server : IAsyncDisposable
|
||||
IProvisioner provisioner,
|
||||
IRestorer restorer,
|
||||
IStatistics statistics,
|
||||
StateMachine<ServerState, ServerTrigger> stateMachine
|
||||
ServerContext context
|
||||
)
|
||||
{
|
||||
Logger = logger;
|
||||
@@ -36,7 +40,7 @@ public class Server : IAsyncDisposable
|
||||
Provisioner = provisioner;
|
||||
Restorer = restorer;
|
||||
Statistics = statistics;
|
||||
StateMachine = stateMachine;
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
@@ -74,14 +78,14 @@ public class Server : IAsyncDisposable
|
||||
CreateStateMachine(restoredState);
|
||||
|
||||
// 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.OnTransitioned(transition => OnStateSubject.OnNext(transition.Destination));
|
||||
|
||||
// Configure basic state machine flow
|
||||
|
||||
StateMachine.Configure(ServerState.Offline)
|
||||
@@ -120,7 +126,10 @@ public class Server : IAsyncDisposable
|
||||
// Handle transitions
|
||||
|
||||
StateMachine.Configure(ServerState.Starting)
|
||||
.OnActivateAsync(HandleStart);
|
||||
.OnEntryAsync(HandleStart);
|
||||
|
||||
StateMachine.Configure(ServerState.Stopping)
|
||||
.OnEntryFromAsync(ServerTrigger.Stop, HandleStop);
|
||||
}
|
||||
|
||||
#region State machine handlers
|
||||
@@ -170,15 +179,20 @@ public class Server : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleStop()
|
||||
{
|
||||
await Provisioner.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (ProvisionExitSubscription != null)
|
||||
await ProvisionExitSubscription.DisposeAsync();
|
||||
ProvisionExitSubscription.Dispose();
|
||||
|
||||
if (InstallerExitSubscription != null)
|
||||
await InstallerExitSubscription.DisposeAsync();
|
||||
InstallerExitSubscription.Dispose();
|
||||
|
||||
await Console.DisposeAsync();
|
||||
await FileSystem.DisposeAsync();
|
||||
|
||||
@@ -2,9 +2,8 @@ using MoonlightServers.Daemon.Models.Cache;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
|
||||
public record ServerMeta
|
||||
public record ServerContext
|
||||
{
|
||||
public ServerConfiguration Configuration { get; set; }
|
||||
public IServiceCollection ServiceCollection { get; set; }
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
public AsyncServiceScope ServiceScope { 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 IAsyncObservable<string> OnOutput => OnOutputSubject.ToAsyncObservable();
|
||||
public IAsyncObservable<string> OnInput => OnInputSubject.ToAsyncObservable();
|
||||
public IObservable<string> OnOutput => OnOutputSubject;
|
||||
public IObservable<string> OnInput => OnInputSubject;
|
||||
|
||||
private readonly AsyncSubject<string> OnOutputSubject = new();
|
||||
private readonly AsyncSubject<string> OnInputSubject = new();
|
||||
private readonly Subject<string> OnOutputSubject = new();
|
||||
private readonly Subject<string> OnInputSubject = new();
|
||||
|
||||
private readonly ConcurrentList<string> OutputCache = new();
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly ILogger<DockerConsole> Logger;
|
||||
private readonly ServerMeta Meta;
|
||||
private readonly ServerContext Context;
|
||||
|
||||
private MultiplexedStream? CurrentStream;
|
||||
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()
|
||||
@@ -38,13 +44,13 @@ public class DockerConsole : IConsole
|
||||
|
||||
public async Task AttachToRuntime()
|
||||
{
|
||||
var containerName = $"moonlight-runtime-{Meta.Configuration.Id}";
|
||||
var containerName = $"moonlight-runtime-{Context.Configuration.Id}";
|
||||
await AttachStream(containerName);
|
||||
}
|
||||
|
||||
public async Task AttachToInstallation()
|
||||
{
|
||||
var containerName = $"moonlight-install-{Meta.Configuration.Id}";
|
||||
var containerName = $"moonlight-install-{Context.Configuration.Id}";
|
||||
await AttachStream(containerName);
|
||||
}
|
||||
|
||||
@@ -108,7 +114,7 @@ public class DockerConsole : IConsole
|
||||
}
|
||||
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
|
||||
{
|
||||
@@ -121,7 +127,7 @@ public class DockerConsole : IConsole
|
||||
}
|
||||
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)
|
||||
=> 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()
|
||||
{
|
||||
|
||||
@@ -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 Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
@@ -9,28 +11,29 @@ namespace MoonlightServers.Daemon.ServerSys.Implementations;
|
||||
|
||||
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 ILogger<DockerProvisioner> Logger;
|
||||
private readonly DockerEventService EventService;
|
||||
private readonly ServerMeta Meta;
|
||||
private readonly ServerContext Context;
|
||||
private readonly IConsole Console;
|
||||
private readonly DockerImageService ImageService;
|
||||
private readonly ServerConfigurationMapper Mapper;
|
||||
private readonly IFileSystem FileSystem;
|
||||
|
||||
private AsyncSubject<object> OnExitedSubject = new();
|
||||
private Subject<object> OnExitedSubject = new();
|
||||
|
||||
private string? ContainerId;
|
||||
private string ContainerName;
|
||||
private IAsyncDisposable? ContainerEventSubscription;
|
||||
private IDisposable? ContainerEventSubscription;
|
||||
|
||||
public DockerProvisioner(
|
||||
DockerClient dockerClient,
|
||||
ILogger<DockerProvisioner> logger,
|
||||
DockerEventService eventService,
|
||||
ServerMeta meta,
|
||||
ServerContext context,
|
||||
IConsole console,
|
||||
DockerImageService imageService,
|
||||
ServerConfigurationMapper mapper,
|
||||
@@ -40,7 +43,7 @@ public class DockerProvisioner : IProvisioner
|
||||
DockerClient = dockerClient;
|
||||
Logger = logger;
|
||||
EventService = eventService;
|
||||
Meta = meta;
|
||||
Context = context;
|
||||
Console = console;
|
||||
ImageService = imageService;
|
||||
Mapper = mapper;
|
||||
@@ -49,30 +52,39 @@ public class DockerProvisioner : IProvisioner
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
ContainerName = $"moonlight-runtime-{Meta.Configuration.Id}";
|
||||
ContainerName = $"moonlight-runtime-{Context.Configuration.Id}";
|
||||
|
||||
ContainerEventSubscription = await EventService
|
||||
ContainerEventSubscription = EventService
|
||||
.OnContainerEvent
|
||||
.SubscribeAsync(HandleContainerEvent);
|
||||
.Subscribe(HandleContainerEvent);
|
||||
|
||||
// Check for any already existing runtime container
|
||||
// TODO: Implement a way for restoring the state
|
||||
// Needs to be able to be consumed by the restorer
|
||||
// Check for any already existing runtime container to reclaim
|
||||
Logger.LogDebug("Searching for orphan container to reclaim");
|
||||
|
||||
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
|
||||
if (message.ID != ContainerId)
|
||||
return ValueTask.CompletedTask;
|
||||
return;
|
||||
|
||||
// Only handle die events
|
||||
if (message.Action != "die")
|
||||
return ValueTask.CompletedTask;
|
||||
return;
|
||||
|
||||
OnExitedSubject.OnNext(message);
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Sync()
|
||||
@@ -111,7 +123,7 @@ public class DockerProvisioner : IProvisioner
|
||||
// 2. Ensure the docker image has been downloaded
|
||||
await Console.WriteToMoonlight("Downloading docker image");
|
||||
|
||||
await ImageService.Download(Meta.Configuration.DockerImage, async message =>
|
||||
await ImageService.Download(Context.Configuration.DockerImage, async message =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -127,7 +139,7 @@ public class DockerProvisioner : IProvisioner
|
||||
var hostFsPath = FileSystem.GetExternalPath();
|
||||
|
||||
var parameters = Mapper.ToRuntimeParameters(
|
||||
Meta.Configuration,
|
||||
Context.Configuration,
|
||||
hostFsPath,
|
||||
ContainerName
|
||||
);
|
||||
@@ -151,15 +163,15 @@ public class DockerProvisioner : IProvisioner
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
if (Meta.Configuration.StopCommand.StartsWith('^'))
|
||||
if (Context.Configuration.StopCommand.StartsWith('^'))
|
||||
{
|
||||
await DockerClient.Containers.KillContainerAsync(ContainerId, new()
|
||||
{
|
||||
Signal = Meta.Configuration.StopCommand.Substring(1)
|
||||
Signal = Context.Configuration.StopCommand.Substring(1)
|
||||
});
|
||||
}
|
||||
else
|
||||
await Console.WriteToInput(Meta.Configuration.StopCommand);
|
||||
await Console.WriteToInput(Context.Configuration.StopCommand + "\n\r");
|
||||
}
|
||||
|
||||
public async Task Kill()
|
||||
@@ -241,6 +253,6 @@ public class DockerProvisioner : IProvisioner
|
||||
OnExitedSubject.Dispose();
|
||||
|
||||
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 Exists { get; private set; }
|
||||
|
||||
private readonly ServerMeta Meta;
|
||||
private readonly ServerContext Context;
|
||||
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;
|
||||
}
|
||||
|
||||
public Task Initialize()
|
||||
=> Task.CompletedTask;
|
||||
{
|
||||
HostPath = Path.Combine(Directory.GetCurrentDirectory(), Configuration.Storage.Volumes, Context.Configuration.Id.ToString());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Sync()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task Create()
|
||||
{
|
||||
Directory.CreateDirectory(HostPath);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.Models.Cache;
|
||||
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
using MoonlightServers.Daemon.ServerSys.Implementations;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSys;
|
||||
|
||||
@@ -16,24 +14,13 @@ public class ServerFactory
|
||||
|
||||
public Server CreateServer(ServerConfiguration configuration)
|
||||
{
|
||||
var serverMeta = new ServerMeta();
|
||||
serverMeta.Configuration = configuration;
|
||||
var scope = ServiceProvider.CreateAsyncScope();
|
||||
|
||||
// Build service collection
|
||||
serverMeta.ServiceCollection = new ServiceCollection();
|
||||
var meta = scope.ServiceProvider.GetRequiredService<ServerContext>();
|
||||
|
||||
// Configure service pipeline for the server components
|
||||
serverMeta.ServiceCollection.AddSingleton<IConsole, DockerConsole>();
|
||||
serverMeta.ServiceCollection.AddSingleton<IFileSystem, RawFileSystem>();
|
||||
meta.Configuration = configuration;
|
||||
meta.ServiceScope = scope;
|
||||
|
||||
// TODO: Handle implementation configurations (e.g. virtual disk) here
|
||||
|
||||
// Combine both app service provider and our server instance specific one
|
||||
serverMeta.ServiceProvider = new CompositeServiceProvider([
|
||||
serverMeta.ServiceCollection.BuildServiceProvider(),
|
||||
ServiceProvider
|
||||
]);
|
||||
|
||||
return serverMeta.ServiceProvider.GetRequiredService<Server>();
|
||||
return scope.ServiceProvider.GetRequiredService<Server>();
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ public class DockerEventService : BackgroundService
|
||||
private readonly ILogger<DockerEventService> Logger;
|
||||
private readonly DockerClient DockerClient;
|
||||
|
||||
public IAsyncObservable<Message> OnContainerEvent => OnContainerSubject.ToAsyncObservable();
|
||||
public IAsyncObservable<Message> OnImageEvent => OnImageSubject.ToAsyncObservable();
|
||||
public IAsyncObservable<Message> OnNetworkEvent => OnNetworkSubject.ToAsyncObservable();
|
||||
public IObservable<Message> OnContainerEvent => OnContainerSubject;
|
||||
public IObservable<Message> OnImageEvent => OnImageSubject;
|
||||
public IObservable<Message> OnNetworkEvent => OnNetworkSubject;
|
||||
|
||||
private readonly AsyncSubject<Message> OnContainerSubject = new();
|
||||
private readonly AsyncSubject<Message> OnImageSubject = new();
|
||||
private readonly AsyncSubject<Message> OnNetworkSubject = new();
|
||||
private readonly Subject<Message> OnContainerSubject = new();
|
||||
private readonly Subject<Message> OnImageSubject = new();
|
||||
private readonly Subject<Message> OnNetworkSubject = new();
|
||||
|
||||
public DockerEventService(
|
||||
ILogger<DockerEventService> logger,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Docker.DotNet;
|
||||
@@ -13,9 +15,13 @@ using MoonlightServers.Daemon.Configuration;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.Http.Hubs;
|
||||
using MoonlightServers.Daemon.Mappers;
|
||||
using MoonlightServers.Daemon.Models.Cache;
|
||||
using MoonlightServers.Daemon.ServerSys;
|
||||
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||
using MoonlightServers.Daemon.ServerSys.Implementations;
|
||||
using MoonlightServers.Daemon.ServerSystem;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
using Server = MoonlightServers.Daemon.ServerSystem.Server;
|
||||
|
||||
namespace MoonlightServers.Daemon;
|
||||
|
||||
@@ -65,6 +71,70 @@ public class Startup
|
||||
await MapBase();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -72,7 +142,6 @@ public class Startup
|
||||
{
|
||||
Directory.CreateDirectory("storage");
|
||||
Directory.CreateDirectory(Path.Combine("storage", "logs"));
|
||||
Directory.CreateDirectory(Path.Combine("storage", "plugins"));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -261,6 +330,17 @@ public class Startup
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user