Implemented first iteration of the docker-based server installer. Added restore functionality for the installer. Wired up for basic installer testing
This commit is contained in:
@@ -3,6 +3,7 @@ using Mono.Unix.Native;
|
|||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using MoonlightServers.Daemon.Configuration;
|
using MoonlightServers.Daemon.Configuration;
|
||||||
using MoonlightServers.Daemon.Models.Cache;
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Mappers;
|
namespace MoonlightServers.Daemon.Mappers;
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ public class ServerConfigurationMapper
|
|||||||
|
|
||||||
// TODO: Extract this to an external service with config options and return a userspace user id and a install user id
|
// TODO: Extract this to an external service with config options and return a userspace user id and a install user id
|
||||||
// in order to know which permissions are required in order to run the container with the correct permissions
|
// in order to know which permissions are required in order to run the container with the correct permissions
|
||||||
|
|
||||||
var userId = Syscall.getuid();
|
var userId = Syscall.getuid();
|
||||||
|
|
||||||
if (userId == 0)
|
if (userId == 0)
|
||||||
@@ -137,7 +138,64 @@ public class ServerConfigurationMapper
|
|||||||
// in the daemon instead of letting it the entrypoint do. iirc pelican wants to do that as well so we need to do that
|
// in the daemon instead of letting it the entrypoint do. iirc pelican wants to do that as well so we need to do that
|
||||||
// sooner or later in order to stay compatible to pelican
|
// sooner or later in order to stay compatible to pelican
|
||||||
// Possible flag name: LegacyEntrypointMode
|
// Possible flag name: LegacyEntrypointMode
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateContainerParameters ToInstallParameters(
|
||||||
|
ServerConfiguration serverConfiguration,
|
||||||
|
ServerInstallDataResponse installData,
|
||||||
|
string runtimeHostPath,
|
||||||
|
string installationHostPath,
|
||||||
|
string containerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var parameters = ToSharedParameters(serverConfiguration);
|
||||||
|
|
||||||
|
// - Name
|
||||||
|
parameters.Name = containerName;
|
||||||
|
parameters.Hostname = containerName;
|
||||||
|
|
||||||
|
// - Image
|
||||||
|
parameters.Image = installData.DockerImage;
|
||||||
|
|
||||||
|
// -- Working directory
|
||||||
|
parameters.WorkingDir = "/mnt/server";
|
||||||
|
|
||||||
|
// - User
|
||||||
|
// Note: Some images might not work if we set a user here
|
||||||
|
|
||||||
|
var userId = Syscall.getuid();
|
||||||
|
|
||||||
|
// If we are root, we are able to change owner permissions after the installation
|
||||||
|
// so we run the installation as root, otherwise we need to run it as our current user,
|
||||||
|
// so we are able to access the files created by the installer
|
||||||
|
if (userId == 0)
|
||||||
|
parameters.User = "0:0";
|
||||||
|
else
|
||||||
|
parameters.User = $"{userId}:{userId}";
|
||||||
|
|
||||||
|
// -- Mounts
|
||||||
|
parameters.HostConfig.Mounts = new List<Mount>();
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
|
{
|
||||||
|
Source = runtimeHostPath,
|
||||||
|
Target = "/mnt/server",
|
||||||
|
ReadOnly = false,
|
||||||
|
Type = "bind"
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
|
{
|
||||||
|
Source = installationHostPath,
|
||||||
|
Target = "/mnt/install",
|
||||||
|
ReadOnly = false,
|
||||||
|
Type = "bind"
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.Cmd = [installData.Shell, "/mnt/install/install.sh"];
|
||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +321,7 @@ public class ServerConfigurationMapper
|
|||||||
for (var i = 0; i < serverConfiguration.Allocations.Length; i++)
|
for (var i = 0; i < serverConfiguration.Allocations.Length; i++)
|
||||||
{
|
{
|
||||||
var allocation = serverConfiguration.Allocations[i];
|
var allocation = serverConfiguration.Allocations[i];
|
||||||
|
|
||||||
result.Add($"ML_PORT_{i}", allocation.Port.ToString());
|
result.Add($"ML_PORT_{i}", allocation.Port.ToString());
|
||||||
|
|
||||||
if (i == 0) // TODO: Implement a way to set the default/main allocation
|
if (i == 0) // TODO: Implement a way to set the default/main allocation
|
||||||
@@ -273,7 +331,7 @@ public class ServerConfigurationMapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy variables as env vars
|
// Copy variables as env vars
|
||||||
foreach (var variable in serverConfiguration.Variables)
|
foreach (var variable in serverConfiguration.Variables)
|
||||||
result.Add(variable.Key, variable.Value);
|
result.Add(variable.Key, variable.Value);
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
<_ContentIncludedByDefault Remove="volumes\3\usercache.json" />
|
<_ContentIncludedByDefault Remove="volumes\3\usercache.json" />
|
||||||
<_ContentIncludedByDefault Remove="volumes\3\version_history.json" />
|
<_ContentIncludedByDefault Remove="volumes\3\version_history.json" />
|
||||||
<_ContentIncludedByDefault Remove="volumes\3\whitelist.json" />
|
<_ContentIncludedByDefault Remove="volumes\3\whitelist.json" />
|
||||||
|
<_ContentIncludedByDefault Remove="storage\volumes\69\plugins\spark\config.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ public interface IInstaller : IServerComponent
|
|||||||
public IAsyncObservable<object> OnExited { get; }
|
public IAsyncObservable<object> OnExited { get; }
|
||||||
public bool IsRunning { get; }
|
public bool IsRunning { get; }
|
||||||
|
|
||||||
|
public Task Setup();
|
||||||
public Task Start();
|
public Task Start();
|
||||||
public Task Abort();
|
public Task Abort();
|
||||||
public Task Cleanup();
|
public Task Cleanup();
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class Server : IAsyncDisposable
|
|||||||
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
||||||
|
|
||||||
StateMachine.Configure(ServerState.Installing)
|
StateMachine.Configure(ServerState.Installing)
|
||||||
.Permit(ServerTrigger.FailSafe, ServerState.Offline)
|
.Permit(ServerTrigger.FailSafe, ServerState.Offline) // TODO: Add kill
|
||||||
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
.Permit(ServerTrigger.Exited, ServerState.Offline);
|
||||||
|
|
||||||
// Handle transitions
|
// Handle transitions
|
||||||
@@ -130,6 +130,10 @@ public class Server : IAsyncDisposable
|
|||||||
StateMachine.Configure(ServerState.Starting)
|
StateMachine.Configure(ServerState.Starting)
|
||||||
.OnEntryAsync(HandleStart)
|
.OnEntryAsync(HandleStart)
|
||||||
.OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit);
|
.OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit);
|
||||||
|
|
||||||
|
StateMachine.Configure(ServerState.Installing)
|
||||||
|
.OnEntryAsync(HandleInstall)
|
||||||
|
.OnExitFromAsync(ServerTrigger.Exited, HandleInstallExit);
|
||||||
|
|
||||||
StateMachine.Configure(ServerState.Online)
|
StateMachine.Configure(ServerState.Online)
|
||||||
.OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit);
|
.OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit);
|
||||||
@@ -199,6 +203,40 @@ public class Server : IAsyncDisposable
|
|||||||
await Provisioner.Deprovision();
|
await Provisioner.Deprovision();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleInstall()
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Installing");
|
||||||
|
|
||||||
|
Logger.LogDebug("Setting up");
|
||||||
|
await Console.WriteToMoonlight("Setting up installation");
|
||||||
|
|
||||||
|
// TODO: Extract to service
|
||||||
|
|
||||||
|
Context.InstallConfiguration = new()
|
||||||
|
{
|
||||||
|
Shell = "/bin/ash",
|
||||||
|
DockerImage = "ghcr.io/parkervcp/installers:alpine",
|
||||||
|
Script =
|
||||||
|
"#!/bin/ash\n# Paper Installation Script\n#\n# Server Files: /mnt/server\nPROJECT=paper\n\nif [ -n \"${DL_PATH}\" ]; then\n\techo -e \"Using supplied download url: ${DL_PATH}\"\n\tDOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's/{{/${/g' -e 's/}}/}/g')`\nelse\n\tVER_EXISTS=`curl -s https://api.papermc.io/v2/projects/${PROJECT} | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | contains($VERSION)' | grep -m1 true`\n\tLATEST_VERSION=`curl -s https://api.papermc.io/v2/projects/${PROJECT} | jq -r '.versions' | jq -r '.[-1]'`\n\n\tif [ \"${VER_EXISTS}\" == \"true\" ]; then\n\t\techo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\n\telse\n\t\techo -e \"Specified version not found. Defaulting to the latest ${PROJECT} version\"\n\t\tMINECRAFT_VERSION=${LATEST_VERSION}\n\tfi\n\n\tBUILD_EXISTS=`curl -s https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds[] | tostring | contains($BUILD)' | grep -m1 true`\n\tLATEST_BUILD=`curl -s https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION} | jq -r '.builds' | jq -r '.[-1]'`\n\n\tif [ \"${BUILD_EXISTS}\" == \"true\" ]; then\n\t\techo -e \"Build is valid for version ${MINECRAFT_VERSION}. Using build ${BUILD_NUMBER}\"\n\telse\n\t\techo -e \"Using the latest ${PROJECT} build for version ${MINECRAFT_VERSION}\"\n\t\tBUILD_NUMBER=${LATEST_BUILD}\n\tfi\n\n\tJAR_NAME=${PROJECT}-${MINECRAFT_VERSION}-${BUILD_NUMBER}.jar\n\n\techo \"Version being downloaded\"\n\techo -e \"MC Version: ${MINECRAFT_VERSION}\"\n\techo -e \"Build: ${BUILD_NUMBER}\"\n\techo -e \"JAR Name of Build: ${JAR_NAME}\"\n\tDOWNLOAD_URL=https://api.papermc.io/v2/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds/${BUILD_NUMBER}/downloads/${JAR_NAME}\nfi\n\ncd /mnt/server\n\necho -e \"Running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\n\nif [ -f ${SERVER_JARFILE} ]; then\n\tmv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\nfi\n\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\n\nif [ ! -f server.properties ]; then\n echo -e \"Downloading MC server.properties\"\n curl -o server.properties https://raw.githubusercontent.com/parkervcp/eggs/master/minecraft/java/server.properties\nfi"
|
||||||
|
};
|
||||||
|
|
||||||
|
await Installer.Setup();
|
||||||
|
|
||||||
|
await Console.AttachToInstallation();
|
||||||
|
|
||||||
|
await Installer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleInstallExit()
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Installation done");
|
||||||
|
await Console.WriteToMoonlight("Cleaning up");
|
||||||
|
|
||||||
|
await Installer.Cleanup();
|
||||||
|
|
||||||
|
await Console.WriteToMoonlight("Installation completed");
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MoonlightServers.Daemon.Models.Cache;
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
namespace MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
|
|
||||||
@@ -6,4 +7,5 @@ public record ServerContext
|
|||||||
{
|
{
|
||||||
public ServerConfiguration Configuration { get; set; }
|
public ServerConfiguration Configuration { get; set; }
|
||||||
public AsyncServiceScope ServiceScope { get; set; }
|
public AsyncServiceScope ServiceScope { get; set; }
|
||||||
|
public ServerInstallDataResponse InstallConfiguration { get; set; }
|
||||||
}
|
}
|
||||||
@@ -8,19 +8,20 @@ public class DefaultRestorer : IRestorer
|
|||||||
private readonly ILogger<DefaultRestorer> Logger;
|
private readonly ILogger<DefaultRestorer> Logger;
|
||||||
private readonly IConsole Console;
|
private readonly IConsole Console;
|
||||||
private readonly IProvisioner Provisioner;
|
private readonly IProvisioner Provisioner;
|
||||||
|
private readonly IInstaller Installer;
|
||||||
private readonly IStatistics Statistics;
|
private readonly IStatistics Statistics;
|
||||||
|
|
||||||
public DefaultRestorer(
|
public DefaultRestorer(
|
||||||
ILogger<DefaultRestorer> logger,
|
ILogger<DefaultRestorer> logger,
|
||||||
IConsole console,
|
IConsole console,
|
||||||
IProvisioner provisioner,
|
IProvisioner provisioner,
|
||||||
IStatistics statistics
|
IStatistics statistics, IInstaller installer)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
Console = console;
|
Console = console;
|
||||||
Provisioner = provisioner;
|
Provisioner = provisioner;
|
||||||
Statistics = statistics;
|
Statistics = statistics;
|
||||||
|
Installer = installer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Initialize()
|
public Task Initialize()
|
||||||
@@ -44,11 +45,19 @@ public class DefaultRestorer : IRestorer
|
|||||||
|
|
||||||
return ServerState.Online;
|
return ServerState.Online;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (Installer.IsRunning)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Nothing found to restore");
|
Logger.LogDebug("Detected installation to restore");
|
||||||
return ServerState.Offline;
|
|
||||||
|
await Console.AttachToInstallation();
|
||||||
|
await Statistics.SubscribeToInstallation();
|
||||||
|
|
||||||
|
return ServerState.Installing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Nothing found to restore");
|
||||||
|
return ServerState.Offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
public ValueTask DisposeAsync()
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
|
using Docker.DotNet;
|
||||||
|
using Docker.DotNet.Models;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
|
using MoonlightServers.Daemon.Mappers;
|
||||||
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
using MoonlightServers.Daemon.ServerSys.Abstractions;
|
||||||
using MoonlightServers.Daemon.Services;
|
using MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
@@ -10,55 +14,264 @@ public class DockerInstaller : IInstaller
|
|||||||
public IAsyncObservable<object> OnExited => OnExitedSubject.ToAsyncObservable();
|
public IAsyncObservable<object> OnExited => OnExitedSubject.ToAsyncObservable();
|
||||||
public bool IsRunning { get; private set; } = false;
|
public bool IsRunning { get; private set; } = false;
|
||||||
|
|
||||||
private readonly Subject<string> OnExitedSubject = new();
|
private readonly Subject<Message> OnExitedSubject = new();
|
||||||
|
|
||||||
private readonly ILogger<DockerInstaller> Logger;
|
private readonly ILogger<DockerInstaller> Logger;
|
||||||
private readonly DockerEventService EventService;
|
private readonly DockerEventService EventService;
|
||||||
|
private readonly IConsole Console;
|
||||||
|
private readonly DockerClient DockerClient;
|
||||||
|
private readonly ServerContext Context;
|
||||||
|
private readonly DockerImageService ImageService;
|
||||||
|
private readonly IFileSystem FileSystem;
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly ServerConfigurationMapper Mapper;
|
||||||
|
|
||||||
private string? ContainerId;
|
private string? ContainerId;
|
||||||
private string? ContainerName;
|
private string ContainerName;
|
||||||
|
|
||||||
|
private string InstallHostPath;
|
||||||
|
|
||||||
|
private IAsyncDisposable? ContainerEventSubscription;
|
||||||
|
|
||||||
public DockerInstaller(
|
public DockerInstaller(
|
||||||
ILogger<DockerInstaller> logger,
|
ILogger<DockerInstaller> logger,
|
||||||
DockerEventService eventService
|
DockerEventService eventService,
|
||||||
|
IConsole console,
|
||||||
|
DockerClient dockerClient,
|
||||||
|
ServerContext context,
|
||||||
|
DockerImageService imageService,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
AppConfiguration configuration,
|
||||||
|
ServerConfigurationMapper mapper
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
EventService = eventService;
|
EventService = eventService;
|
||||||
|
Console = console;
|
||||||
|
DockerClient = dockerClient;
|
||||||
|
Context = context;
|
||||||
|
ImageService = imageService;
|
||||||
|
FileSystem = fileSystem;
|
||||||
|
Configuration = configuration;
|
||||||
|
Mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Initialize()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
ContainerName = $"moonlight-install-{Context.Configuration.Id}";
|
||||||
|
InstallHostPath =
|
||||||
|
Path.GetFullPath(Path.Combine(Configuration.Storage.Install, Context.Configuration.Id.ToString()));
|
||||||
|
|
||||||
|
ContainerEventSubscription = await EventService
|
||||||
|
.OnContainerEvent
|
||||||
|
.SubscribeAsync(HandleContainerEvent);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
IsRunning = container.State.Running;
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueTask HandleContainerEvent(Message message)
|
||||||
|
{
|
||||||
|
// Only handle events for our own container
|
||||||
|
if (message.ID != ContainerId)
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
|
||||||
|
// Only handle die events
|
||||||
|
if (message.Action != "die")
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
|
||||||
|
OnExitedSubject.OnNext(message);
|
||||||
|
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Sync()
|
public Task Sync()
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task Setup()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
// Plan of action:
|
||||||
|
// 1. Ensure no other container with that name exist
|
||||||
|
// 2. Ensure the docker image has been downloaded
|
||||||
|
// 3. Create the installation volume and place script in there
|
||||||
|
// 4. Create the container from the configuration in the meta
|
||||||
|
|
||||||
|
// 1. Ensure no other container with that name exist
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Searching for orphan container");
|
||||||
|
|
||||||
|
var possibleContainer = await DockerClient.Containers.InspectContainerAsync(ContainerName);
|
||||||
|
|
||||||
|
Logger.LogDebug("Orphan container found. Removing it");
|
||||||
|
await Console.WriteToMoonlight("Found orphan container. Removing it");
|
||||||
|
|
||||||
|
await EnsureContainerOffline(possibleContainer);
|
||||||
|
|
||||||
|
Logger.LogInformation("Removing orphan container");
|
||||||
|
await DockerClient.Containers.RemoveContainerAsync(ContainerName, new());
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Ensure the docker image has been downloaded
|
||||||
|
await Console.WriteToMoonlight("Downloading docker image");
|
||||||
|
|
||||||
|
await ImageService.Download(Context.Configuration.DockerImage, async message =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Console.WriteToMoonlight(message);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Ignored. Not handling it here could cause an application wide crash afaik
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Create the installation volume and place script in there
|
||||||
|
|
||||||
|
await Console.WriteToMoonlight("Creating storage");
|
||||||
|
|
||||||
|
if(Directory.Exists(InstallHostPath))
|
||||||
|
Directory.Delete(InstallHostPath, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(InstallHostPath);
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(Path.Combine(InstallHostPath, "install.sh"), Context.InstallConfiguration.Script);
|
||||||
|
|
||||||
|
// 4. Create the container from the configuration in the meta
|
||||||
|
var runtimeFsPath = FileSystem.GetExternalPath();
|
||||||
|
|
||||||
|
var parameters = Mapper.ToInstallParameters(
|
||||||
|
Context.Configuration,
|
||||||
|
Context.InstallConfiguration,
|
||||||
|
runtimeFsPath,
|
||||||
|
InstallHostPath,
|
||||||
|
ContainerName
|
||||||
|
);
|
||||||
|
|
||||||
|
var createdContainer = await DockerClient.Containers.CreateContainerAsync(parameters);
|
||||||
|
|
||||||
|
ContainerId = createdContainer.ID;
|
||||||
|
|
||||||
|
Logger.LogInformation("Created container");
|
||||||
|
await Console.WriteToMoonlight("Created container");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Start()
|
public async Task Start()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
Logger.LogInformation("Starting container");
|
||||||
|
await Console.WriteToMoonlight("Starting container");
|
||||||
|
await DockerClient.Containers.StartContainerAsync(ContainerId, new());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Abort()
|
public async Task Abort()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
await EnsureContainerOffline();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Cleanup()
|
public async Task Cleanup()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
// Plan of action:
|
||||||
|
// 1. Search for the container by id or name
|
||||||
|
// 2. Ensure container is offline
|
||||||
|
// 3. Remove the container
|
||||||
|
// 4. Delete installation volume if it exists
|
||||||
|
|
||||||
|
// 1. Search for the container by id or name
|
||||||
|
ContainerInspectResponse? container = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ContainerId))
|
||||||
|
container = await DockerClient.Containers.InspectContainerAsync(ContainerName);
|
||||||
|
else
|
||||||
|
container = await DockerClient.Containers.InspectContainerAsync(ContainerId);
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
|
||||||
|
Logger.LogDebug("Runtime container could not be found. Reporting deprovision success");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No container found? We are done here then
|
||||||
|
if (container == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 2. Ensure container is offline
|
||||||
|
await EnsureContainerOffline(container);
|
||||||
|
|
||||||
|
// 3. Remove the container
|
||||||
|
Logger.LogInformation("Removing container");
|
||||||
|
await Console.WriteToMoonlight("Removing container");
|
||||||
|
|
||||||
|
await DockerClient.Containers.RemoveContainerAsync(container.ID, new());
|
||||||
|
|
||||||
|
// 4. Delete installation volume if it exists
|
||||||
|
|
||||||
|
if (Directory.Exists(InstallHostPath))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Removing storage");
|
||||||
|
await Console.WriteToMoonlight("Removing storage");
|
||||||
|
|
||||||
|
Directory.Delete(InstallHostPath, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ServerCrash?> SearchForCrash()
|
public async Task<ServerCrash?> SearchForCrash()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureContainerOffline(ContainerInspectResponse? container = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ContainerId))
|
||||||
|
container = await DockerClient.Containers.InspectContainerAsync(ContainerName);
|
||||||
|
else
|
||||||
|
container = await DockerClient.Containers.InspectContainerAsync(ContainerId);
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("No container found to ensure its offline");
|
||||||
|
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
// No container found? We are done here then
|
||||||
|
if (container == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if container is running
|
||||||
|
if (!container.State.Running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Console.WriteToMoonlight("Killing container");
|
||||||
|
await DockerClient.Containers.KillContainerAsync(ContainerId, new());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
OnExitedSubject.Dispose();
|
OnExitedSubject.Dispose();
|
||||||
|
|
||||||
|
if (ContainerEventSubscription != null)
|
||||||
|
await ContainerEventSubscription.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,10 +16,10 @@ public class ServerFactory
|
|||||||
{
|
{
|
||||||
var scope = ServiceProvider.CreateAsyncScope();
|
var scope = ServiceProvider.CreateAsyncScope();
|
||||||
|
|
||||||
var meta = scope.ServiceProvider.GetRequiredService<ServerContext>();
|
var context = scope.ServiceProvider.GetRequiredService<ServerContext>();
|
||||||
|
|
||||||
meta.Configuration = configuration;
|
context.Configuration = configuration;
|
||||||
meta.ServiceScope = scope;
|
context.ServiceScope = scope;
|
||||||
|
|
||||||
return scope.ServiceProvider.GetRequiredService<Server>();
|
return scope.ServiceProvider.GetRequiredService<Server>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ public class Startup
|
|||||||
await server.StateMachine.FireAsync(ServerTrigger.Stop);
|
await server.StateMachine.FireAsync(ServerTrigger.Stop);
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|
||||||
|
await server.StateMachine.FireAsync(ServerTrigger.Install);
|
||||||
|
|
||||||
|
Console.ReadLine();
|
||||||
|
|
||||||
await server.Context.ServiceScope.DisposeAsync();
|
await server.Context.ServiceScope.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user