Implemented server killing. Updated server manage ui. Added latest tailwind stuff. Added internal error handling

This commit is contained in:
2025-02-15 20:26:10 +01:00
parent 1fbf1ae9ec
commit 56d4313fa8
12 changed files with 294 additions and 109 deletions

View File

@@ -31,4 +31,11 @@ public partial class Server
await Destroy();
}
public async Task InternalError()
{
await LogToConsole("An unhandled error occured performing action");
Logger.LogInformation("Reporting or smth");
}
}

View File

@@ -35,31 +35,36 @@ public partial class Server
// Setup transitions
StateMachine.Configure(ServerState.Offline)
.Permit(ServerTrigger.Start, ServerState.Starting)
.Permit(ServerTrigger.Reinstall, ServerState.Installing);
.Permit(ServerTrigger.Start, ServerState.Starting) // Allow to start
.Permit(ServerTrigger.Reinstall, ServerState.Installing) // Allow to install
.OnEntryFromAsync(ServerTrigger.NotifyInternalError, InternalError); // Handle unhandled errors
StateMachine.Configure(ServerState.Starting)
.Permit(ServerTrigger.NotifyRuntimeContainerDied, ServerState.Offline)
.Permit(ServerTrigger.NotifyOnline, ServerState.Online)
.Permit(ServerTrigger.Stop, ServerState.Stopping)
.OnEntryAsync(InternalStart)
.OnExitFromAsync(ServerTrigger.NotifyRuntimeContainerDied, InternalCrash);
.Permit(ServerTrigger.Stop, ServerState.Stopping) // Allow stopping
.Permit(ServerTrigger.NotifyOnline, ServerState.Online) // Allow the server to report as online
.Permit(ServerTrigger.NotifyRuntimeContainerDied, ServerState.Offline) // Allow server to handle container death
.Permit(ServerTrigger.NotifyInternalError, ServerState.Offline) // Error handling for the action below
.OnEntryAsync(InternalStart) // Perform start action
.OnExitFromAsync(ServerTrigger.NotifyRuntimeContainerDied, InternalCrash); // Define a runtime container death as a crash
StateMachine.Configure(ServerState.Online)
.Permit(ServerTrigger.NotifyRuntimeContainerDied, ServerState.Offline)
.Permit(ServerTrigger.Stop, ServerState.Stopping)
.OnExitFromAsync(ServerTrigger.NotifyRuntimeContainerDied, InternalCrash);
.Permit(ServerTrigger.Stop, ServerState.Stopping) // Allow stopping
.Permit(ServerTrigger.NotifyRuntimeContainerDied, ServerState.Offline) // Allow server to handle container death
.OnExitFromAsync(ServerTrigger.NotifyRuntimeContainerDied, InternalCrash); // Define a runtime container death as a crash
StateMachine.Configure(ServerState.Stopping)
.Permit(ServerTrigger.NotifyRuntimeContainerDied, ServerState.Offline)
.Permit(ServerTrigger.Kill, ServerState.Offline)
.OnEntryAsync(InternalStop)
.OnExitFromAsync(ServerTrigger.NotifyRuntimeContainerDied, InternalFinishStop);
.PermitReentry(ServerTrigger.Kill) // Allow killing, will return to stopping to trigger kill and handle the death correctly
.Permit(ServerTrigger.NotifyRuntimeContainerDied, ServerState.Offline) // Allow server to handle container death
.Permit(ServerTrigger.NotifyInternalError, ServerState.Offline) // Error handling for the actions below
.OnEntryFromAsync(ServerTrigger.Stop, InternalStop) // Perform stop action
.OnEntryFromAsync(ServerTrigger.Kill, InternalKill) // Perform kill action
.OnExitFromAsync(ServerTrigger.NotifyRuntimeContainerDied, InternalFinishStop); // Define a runtime container death as a successful stop
StateMachine.Configure(ServerState.Installing)
.Permit(ServerTrigger.NotifyInstallationContainerDied, ServerState.Offline)
.OnEntryAsync(InternalInstall)
.OnExitAsync(InternalFinishInstall);
.Permit(ServerTrigger.NotifyInstallationContainerDied, ServerState.Offline) // Allow server to handle container death
.Permit(ServerTrigger.NotifyInternalError, ServerState.Offline) // Error handling for the action below
.OnEntryAsync(InternalInstall) // Perform install action
.OnExitFromAsync(ServerTrigger.NotifyInstallationContainerDied, InternalFinishInstall); // Define the death of the installation container as successful
return Task.CompletedTask;
}

View File

@@ -14,69 +14,79 @@ public partial class Server
private async Task InternalInstall()
{
// TODO: Consider if checking for existing install containers is actually useful, because
// when the daemon is starting and a installation is still ongoing it will reattach anyways
// and the container has the auto remove flag enabled by default (maybe also consider this for the normal runtime container)
await LogToConsole("Fetching installation configuration");
// Fetching remote configuration
var remoteService = ServiceProvider.GetRequiredService<RemoteService>();
using var remoteHttpClient = await remoteService.CreateHttpClient();
var installData = await remoteHttpClient.GetJson<ServerInstallDataResponse>($"api/servers/remote/servers/{Configuration.Id}/install");
var dockerImageService = ServiceProvider.GetRequiredService<DockerImageService>();
// We call an external service for that, as we want to have a central management point of images
// for analytics and automatic deletion
await dockerImageService.Ensure(installData.DockerImage, async message => { await LogToConsole(message); });
// Ensuring storage configuration
var installationHostPath = await EnsureInstallationVolume();
var runtimeHostPath = await EnsureRuntimeVolume();
// Write installation script to path
var content = installData.Script.Replace("\r\n", "\n");
await File.WriteAllTextAsync(PathBuilder.File(installationHostPath, "install.sh"), content);
// Creating container configuration
var parameters = Configuration.ToInstallationCreateParameters(
runtimeHostPath,
installationHostPath,
InstallationContainerName,
installData.DockerImage,
installData.Shell
);
var dockerClient = ServiceProvider.GetRequiredService<DockerClient>();
// Ensure we can actually spawn the container
try
{
var existingContainer = await dockerClient.Containers.InspectContainerAsync(InstallationContainerName);
// TODO: Consider if checking for existing install containers is actually useful, because
// when the daemon is starting and a installation is still ongoing it will reattach anyways
// and the container has the auto remove flag enabled by default (maybe also consider this for the normal runtime container)
await LogToConsole("Fetching installation configuration");
// Fetching remote configuration
var remoteService = ServiceProvider.GetRequiredService<RemoteService>();
using var remoteHttpClient = await remoteService.CreateHttpClient();
var installData =
await remoteHttpClient.GetJson<ServerInstallDataResponse>(
$"api/servers/remote/servers/{Configuration.Id}/install");
var dockerImageService = ServiceProvider.GetRequiredService<DockerImageService>();
// We call an external service for that, as we want to have a central management point of images
// for analytics and automatic deletion
await dockerImageService.Ensure(installData.DockerImage, async message => { await LogToConsole(message); });
// Ensuring storage configuration
var installationHostPath = await EnsureInstallationVolume();
var runtimeHostPath = await EnsureRuntimeVolume();
// Write installation script to path
var content = installData.Script.Replace("\r\n", "\n");
await File.WriteAllTextAsync(PathBuilder.File(installationHostPath, "install.sh"), content);
// Creating container configuration
var parameters = Configuration.ToInstallationCreateParameters(
runtimeHostPath,
installationHostPath,
InstallationContainerName,
installData.DockerImage,
installData.Shell
);
var dockerClient = ServiceProvider.GetRequiredService<DockerClient>();
// Ensure we can actually spawn the container
try
{
var existingContainer = await dockerClient.Containers.InspectContainerAsync(InstallationContainerName);
// Perform automatic cleanup / restore
if (existingContainer.State.Running)
await dockerClient.Containers.KillContainerAsync(existingContainer.ID, new());
await dockerClient.Containers.RemoveContainerAsync(existingContainer.ID, new());
}
catch (DockerContainerNotFoundException)
{
// Ignored
}
// Perform automatic cleanup / restore
if (existingContainer.State.Running)
await dockerClient.Containers.KillContainerAsync(existingContainer.ID, new());
await dockerClient.Containers.RemoveContainerAsync(existingContainer.ID, new());
// Spawn the container
var container = await dockerClient.Containers.CreateContainerAsync(parameters);
InstallationContainerId = container.ID;
await AttachConsole(InstallationContainerId);
await dockerClient.Containers.StartContainerAsync(InstallationContainerId, new());
}
catch (DockerContainerNotFoundException)
catch (Exception e)
{
// Ignored
Logger.LogError("An error occured while performing install trigger: {e}", e);
await StateMachine.FireAsync(ServerTrigger.NotifyInternalError);
}
// Spawn the container
var container = await dockerClient.Containers.CreateContainerAsync(parameters);
InstallationContainerId = container.ID;
await AttachConsole(InstallationContainerId);
await dockerClient.Containers.StartContainerAsync(InstallationContainerId, new());
}
private async Task InternalFinishInstall()

View File

@@ -0,0 +1,28 @@
using Docker.DotNet;
using MoonlightServers.Daemon.Enums;
namespace MoonlightServers.Daemon.Abstractions;
public partial class Server
{
public async Task Kill() => await StateMachine.FireAsync(ServerTrigger.Kill);
private async Task InternalKill()
{
try
{
if (RuntimeContainerId == null)
return;
await LogToConsole("Killing container");
var dockerClient = ServiceProvider.GetRequiredService<DockerClient>();
await dockerClient.Containers.KillContainerAsync(RuntimeContainerId, new());
}
catch (Exception e)
{
Logger.LogError("An error occured while performing stop trigger: {e}", e);
await StateMachine.FireAsync(ServerTrigger.NotifyInternalError);
}
}
}

View File

@@ -9,15 +9,23 @@ public partial class Server
private async Task InternalStart()
{
await ReCreate();
try
{
await ReCreate();
await LogToConsole("Starting container");
await LogToConsole("Starting container");
// We can disable the null check for the runtime container id, as we set it by calling ReCreate();
await AttachConsole(RuntimeContainerId!);
// Start container
var dockerClient = ServiceProvider.GetRequiredService<DockerClient>();
await dockerClient.Containers.StartContainerAsync(RuntimeContainerId, new());
// We can disable the null check for the runtime container id, as we set it by calling ReCreate();
await AttachConsole(RuntimeContainerId!);
// Start container
var dockerClient = ServiceProvider.GetRequiredService<DockerClient>();
await dockerClient.Containers.StartContainerAsync(RuntimeContainerId, new());
}
catch (Exception e)
{
Logger.LogError("An error occured while performing start trigger: {e}", e);
await StateMachine.FireAsync(ServerTrigger.NotifyInternalError);
}
}
}

View File

@@ -8,7 +8,15 @@ public partial class Server
private async Task InternalStop()
{
await Console.WriteToInput($"{Configuration.StopCommand}\n\r");
try
{
await Console.WriteToInput($"{Configuration.StopCommand}\n\r");
}
catch (Exception e)
{
Logger.LogError("An error occured while performing stop trigger: {e}", e);
await StateMachine.FireAsync(ServerTrigger.NotifyInternalError);
}
}
private async Task InternalFinishStop()

View File

@@ -9,5 +9,6 @@ public enum ServerTrigger
Reinstall = 4,
NotifyOnline = 5,
NotifyRuntimeContainerDied = 6,
NotifyInstallationContainerDied = 7
NotifyInstallationContainerDied = 7,
NotifyInternalError = 8
}

View File

@@ -17,7 +17,7 @@ public class ServerPowerController : Controller
}
[HttpPost("{serverId:int}/start")]
public async Task Start(int serverId, [FromQuery] bool runAsync = true)
public async Task Start(int serverId)
{
var server = ServerService.GetServer(serverId);
@@ -28,7 +28,7 @@ public class ServerPowerController : Controller
}
[HttpPost("{serverId:int}/stop")]
public async Task Stop(int serverId, [FromQuery] bool runAsync = true)
public async Task Stop(int serverId)
{
var server = ServerService.GetServer(serverId);
@@ -39,7 +39,7 @@ public class ServerPowerController : Controller
}
[HttpPost("{serverId:int}/install")]
public async Task Install(int serverId, [FromQuery] bool runAsync = true)
public async Task Install(int serverId)
{
var server = ServerService.GetServer(serverId);
@@ -48,4 +48,15 @@ public class ServerPowerController : Controller
await server.Install();
}
[HttpPost("{serverId:int}/kill")]
public async Task Kill(int serverId)
{
var server = ServerService.GetServer(serverId);
if (server == null)
throw new HttpApiException("No server with this id found", 404);
await server.Kill();
}
}