Started with adding container creation and a server state machine
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
using MoonCore.Helpers;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Configuration;
|
namespace MoonlightServers.Daemon.Configuration;
|
||||||
|
|
||||||
public class AppConfiguration
|
public class AppConfiguration
|
||||||
@@ -24,9 +26,9 @@ public class AppConfiguration
|
|||||||
|
|
||||||
public class StorageData
|
public class StorageData
|
||||||
{
|
{
|
||||||
public string Volumes { get; set; }
|
public string Volumes { get; set; } = PathBuilder.Dir("volumes");
|
||||||
public string VirtualDisks { get; set; }
|
public string VirtualDisks { get; set; } = PathBuilder.Dir("virtualDisks");
|
||||||
public string Backups { get; set; }
|
public string Backups { get; set; } = PathBuilder.Dir("backups");
|
||||||
public string Install { get; set; }
|
public string Install { get; set; } = PathBuilder.Dir("install");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
118
MoonlightServers.Daemon/Helpers/ServerActionHelper.cs
Normal file
118
MoonlightServers.Daemon/Helpers/ServerActionHelper.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using Docker.DotNet;
|
||||||
|
using Docker.DotNet.Models;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
|
using MoonlightServers.Daemon.Models;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
public class ServerActionHelper
|
||||||
|
{
|
||||||
|
public static async Task Start(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
await EnsureStorage(server, serviceProvider);
|
||||||
|
await EnsureDockerImage(server, serviceProvider);
|
||||||
|
await CreateRuntimeContainer(server, serviceProvider);
|
||||||
|
await StartRuntimeContainer(server, serviceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task EnsureStorage(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
await NotifyTask(server, serviceProvider, ServerTask.CreatingStorage);
|
||||||
|
|
||||||
|
// Build paths
|
||||||
|
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
|
var volumePath = PathBuilder.Dir(
|
||||||
|
configuration.Storage.Volumes,
|
||||||
|
server.Configuration.Id.ToString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create volume if missing
|
||||||
|
if (!Directory.Exists(volumePath))
|
||||||
|
Directory.CreateDirectory(volumePath);
|
||||||
|
|
||||||
|
// TODO: Virtual disk
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task EnsureDockerImage(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
await NotifyTask(server, serviceProvider, ServerTask.PullingDockerImage);
|
||||||
|
|
||||||
|
var dockerClient = serviceProvider.GetRequiredService<DockerClient>();
|
||||||
|
|
||||||
|
await dockerClient.Images.CreateImageAsync(new()
|
||||||
|
{
|
||||||
|
FromImage = server.Configuration.DockerImage
|
||||||
|
},
|
||||||
|
new AuthConfig(),
|
||||||
|
new Progress<JSONMessage>(async message =>
|
||||||
|
{
|
||||||
|
//var percentage = (int)(message.Progress.Current / message.Progress.Total);
|
||||||
|
//await UpdateProgress(server, serviceProvider, percentage);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CreateRuntimeContainer(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var dockerClient = serviceProvider.GetRequiredService<DockerClient>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var existingContainer = await dockerClient.Containers.InspectContainerAsync(
|
||||||
|
$"moonlight-runtime-{server.Configuration.Id}"
|
||||||
|
);
|
||||||
|
|
||||||
|
await NotifyTask(server, serviceProvider, ServerTask.RemovingContainer);
|
||||||
|
|
||||||
|
if (existingContainer.State.Running) // Stop already running container
|
||||||
|
{
|
||||||
|
await dockerClient.Containers.StopContainerAsync(existingContainer.ID, new()
|
||||||
|
{
|
||||||
|
WaitBeforeKillSeconds = 30 // TODO: Config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await dockerClient.Containers.RemoveContainerAsync(existingContainer.ID, new());
|
||||||
|
}
|
||||||
|
catch (DockerContainerNotFoundException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
await NotifyTask(server, serviceProvider, ServerTask.CreatingContainer);
|
||||||
|
|
||||||
|
// Create a new container
|
||||||
|
var parameters = new CreateContainerParameters();
|
||||||
|
|
||||||
|
ServerConfigurationHelper.ApplyRuntimeOptions(
|
||||||
|
parameters,
|
||||||
|
server.Configuration,
|
||||||
|
serviceProvider.GetRequiredService<AppConfiguration>()
|
||||||
|
);
|
||||||
|
|
||||||
|
var container = await dockerClient.Containers.CreateContainerAsync(parameters);
|
||||||
|
server.ContainerId = container.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task StartRuntimeContainer(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
await NotifyTask(server, serviceProvider, ServerTask.StartingContainer);
|
||||||
|
|
||||||
|
var dockerClient = serviceProvider.GetRequiredService<DockerClient>();
|
||||||
|
|
||||||
|
await dockerClient.Containers.StartContainerAsync(server.ContainerId, new());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task NotifyTask(Server server, IServiceProvider serviceProvider, ServerTask task)
|
||||||
|
{
|
||||||
|
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||||
|
var logger = loggerFactory.CreateLogger($"Server {server.Configuration.Id}");
|
||||||
|
|
||||||
|
logger.LogInformation("Task: {task}", task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task UpdateProgress(Server server, IServiceProvider serviceProvider, int progress)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
202
MoonlightServers.Daemon/Helpers/ServerConfigurationHelper.cs
Normal file
202
MoonlightServers.Daemon/Helpers/ServerConfigurationHelper.cs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
using Docker.DotNet.Models;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
public static class ServerConfigurationHelper
|
||||||
|
{
|
||||||
|
public static void ApplyRuntimeOptions(CreateContainerParameters parameters, ServerConfiguration configuration, AppConfiguration appConfiguration)
|
||||||
|
{
|
||||||
|
ApplySharedOptions(parameters, configuration);
|
||||||
|
|
||||||
|
// -- Cap drops
|
||||||
|
parameters.HostConfig.CapDrop = new List<string>()
|
||||||
|
{
|
||||||
|
"setpcap", "mknod", "audit_write", "net_raw", "dac_override",
|
||||||
|
"fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap"
|
||||||
|
};
|
||||||
|
|
||||||
|
// -- More security options
|
||||||
|
parameters.HostConfig.ReadonlyRootfs = true;
|
||||||
|
parameters.HostConfig.SecurityOpt = new List<string>()
|
||||||
|
{
|
||||||
|
"no-new-privileges"
|
||||||
|
};
|
||||||
|
|
||||||
|
// - Name
|
||||||
|
var name = $"moonlight-runtime-{configuration.Id}";
|
||||||
|
parameters.Name = name;
|
||||||
|
parameters.Hostname = name;
|
||||||
|
|
||||||
|
// - Image
|
||||||
|
parameters.Image = configuration.DockerImage;
|
||||||
|
|
||||||
|
// - Env
|
||||||
|
parameters.Env = ConstructEnv(configuration)
|
||||||
|
.Select(x => $"{x.Key}={x.Value}")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// -- Working directory
|
||||||
|
parameters.WorkingDir = "/home/container";
|
||||||
|
|
||||||
|
// - User
|
||||||
|
//TODO: use config
|
||||||
|
parameters.User = $"998:998";
|
||||||
|
|
||||||
|
// -- Mounts
|
||||||
|
parameters.HostConfig.Mounts = new List<Mount>();
|
||||||
|
|
||||||
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
|
{
|
||||||
|
Source = GetRuntimeVolume(configuration, appConfiguration),
|
||||||
|
Target = "/home/container",
|
||||||
|
ReadOnly = false,
|
||||||
|
Type = "bind"
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- Ports
|
||||||
|
//var config = configService.Get();
|
||||||
|
|
||||||
|
if (true) // TODO: Add network toggle
|
||||||
|
{
|
||||||
|
parameters.ExposedPorts = new Dictionary<string, EmptyStruct>();
|
||||||
|
parameters.HostConfig.PortBindings = new Dictionary<string, IList<PortBinding>>();
|
||||||
|
|
||||||
|
foreach (var allocation in configuration.Allocations)
|
||||||
|
{
|
||||||
|
parameters.ExposedPorts.Add($"{allocation.Port}/tcp", new());
|
||||||
|
parameters.ExposedPorts.Add($"{allocation.Port}/udp", new());
|
||||||
|
|
||||||
|
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/tcp", new List<PortBinding>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
HostPort = allocation.Port.ToString(),
|
||||||
|
HostIP = allocation.IpAddress
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/udp", new List<PortBinding>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
HostPort = allocation.Port.ToString(),
|
||||||
|
HostIP = allocation.IpAddress
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplySharedOptions(CreateContainerParameters parameters, ServerConfiguration configuration)
|
||||||
|
{
|
||||||
|
// - Input, output & error streams and tty
|
||||||
|
parameters.Tty = true;
|
||||||
|
parameters.AttachStderr = true;
|
||||||
|
parameters.AttachStdin = true;
|
||||||
|
parameters.AttachStdout = true;
|
||||||
|
parameters.OpenStdin = true;
|
||||||
|
|
||||||
|
// - Host config
|
||||||
|
parameters.HostConfig = new HostConfig();
|
||||||
|
|
||||||
|
// -- CPU limits
|
||||||
|
parameters.HostConfig.CPUQuota = configuration.Cpu * 1000;
|
||||||
|
parameters.HostConfig.CPUPeriod = 100000;
|
||||||
|
parameters.HostConfig.CPUShares = 1024;
|
||||||
|
|
||||||
|
// -- Memory and swap limits
|
||||||
|
var memoryLimit = configuration.Memory;
|
||||||
|
|
||||||
|
// The overhead multiplier gives the container a little bit more memory to prevent crashes
|
||||||
|
var memoryOverhead = memoryLimit + (memoryLimit * 0.05f); // TODO: Config
|
||||||
|
|
||||||
|
long swapLimit = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
// If swap is enabled globally and not disabled on this server, set swap
|
||||||
|
if (!configuration.Limits.DisableSwap && config.Server.EnableSwap)
|
||||||
|
swapLimit = (long)(memoryOverhead + memoryOverhead * config.Server.SwapMultiplier);
|
||||||
|
co
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Finalize limits by converting and updating the host config
|
||||||
|
parameters.HostConfig.Memory = ByteConverter.FromMegaBytes((long)memoryOverhead, 1000).Bytes;
|
||||||
|
parameters.HostConfig.MemoryReservation = ByteConverter.FromMegaBytes(memoryLimit, 1000).Bytes;
|
||||||
|
parameters.HostConfig.MemorySwap = swapLimit == -1 ? swapLimit : ByteConverter.FromMegaBytes(swapLimit, 1000).Bytes;
|
||||||
|
|
||||||
|
// -- Other limits
|
||||||
|
parameters.HostConfig.BlkioWeight = 100;
|
||||||
|
//container.HostConfig.PidsLimit = configuration.Limits.PidsLimit;
|
||||||
|
parameters.HostConfig.OomKillDisable = true; //!configuration.Limits.EnableOomKill;
|
||||||
|
|
||||||
|
// -- DNS
|
||||||
|
parameters.HostConfig.DNS = /*config.Docker.DnsServers.Any() ? config.Docker.DnsServers :*/ new List<string>()
|
||||||
|
{
|
||||||
|
"1.1.1.1",
|
||||||
|
"9.9.9.9"
|
||||||
|
};
|
||||||
|
|
||||||
|
// -- Tmpfs
|
||||||
|
parameters.HostConfig.Tmpfs = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "/tmp", $"rw,exec,nosuid,size=100M" } // TODO: Config
|
||||||
|
};
|
||||||
|
|
||||||
|
// -- Logging
|
||||||
|
parameters.HostConfig.LogConfig = new()
|
||||||
|
{
|
||||||
|
Type = "json-file", // We need to use this provider, as the GetLogs endpoint needs it
|
||||||
|
Config = new Dictionary<string, string>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// - Labels
|
||||||
|
parameters.Labels = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
parameters.Labels.Add("Software", "Moonlight-Panel");
|
||||||
|
parameters.Labels.Add("ServerId", configuration.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, string> ConstructEnv(ServerConfiguration configuration)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
// Default environment variables
|
||||||
|
//TODO: Add timezone, add server ip
|
||||||
|
result.Add("STARTUP", configuration.StartupCommand);
|
||||||
|
result.Add("SERVER_MEMORY", configuration.Memory.ToString());
|
||||||
|
|
||||||
|
if (configuration.Allocations.Length > 0)
|
||||||
|
{
|
||||||
|
var mainAllocation = configuration.Allocations.First();
|
||||||
|
|
||||||
|
result.Add("SERVER_IP", mainAllocation.IpAddress);
|
||||||
|
result.Add("SERVER_PORT", mainAllocation.Port.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle additional allocation variables
|
||||||
|
var i = 1;
|
||||||
|
foreach (var additionalAllocation in configuration.Allocations)
|
||||||
|
{
|
||||||
|
result.Add($"ML_PORT_{i}", additionalAllocation.Port.ToString());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy variables as env vars
|
||||||
|
foreach (var variable in configuration.Variables)
|
||||||
|
result.Add(variable.Key, variable.Value);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetRuntimeVolume(ServerConfiguration configuration, AppConfiguration appConfiguration)
|
||||||
|
{
|
||||||
|
var localPath = PathBuilder.Dir(appConfiguration.Storage.Volumes, configuration.Id.ToString());
|
||||||
|
var absolutePath = Path.GetFullPath(localPath);
|
||||||
|
|
||||||
|
return absolutePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
MoonlightServers.Daemon/Helpers/StateMachine.cs
Normal file
69
MoonlightServers.Daemon/Helpers/StateMachine.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
public class StateMachine<T> where T : struct, Enum
|
||||||
|
{
|
||||||
|
private readonly List<StateMachineTransition> Transitions = new();
|
||||||
|
private readonly object Lock = new();
|
||||||
|
|
||||||
|
public T CurrentState { get; private set; }
|
||||||
|
public event Func<T, Task> OnTransitioned;
|
||||||
|
public event Action<T, Exception> OnError;
|
||||||
|
|
||||||
|
public StateMachine(T initialState)
|
||||||
|
{
|
||||||
|
CurrentState = initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTransition(T from, T to, T? onError, Func<Task> fun)
|
||||||
|
{
|
||||||
|
Transitions.Add(new()
|
||||||
|
{
|
||||||
|
From = from,
|
||||||
|
To = to,
|
||||||
|
OnError = onError,
|
||||||
|
OnTransitioning = fun
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTransition(T from, T to, Func<Task> fun) => AddTransition(from, to, null, fun);
|
||||||
|
|
||||||
|
public async Task TransitionTo(T to)
|
||||||
|
{
|
||||||
|
lock (Lock)
|
||||||
|
{
|
||||||
|
var transition = Transitions.FirstOrDefault(x =>
|
||||||
|
x.From.Equals(CurrentState) &&
|
||||||
|
x.To.Equals(to)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (transition == null)
|
||||||
|
throw new InvalidOperationException("Unable to transition to the request state: No transition found");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
transition.OnTransitioning.Invoke().Wait();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if(OnError != null)
|
||||||
|
OnError.Invoke(to, e);
|
||||||
|
|
||||||
|
if (transition.OnError.HasValue)
|
||||||
|
CurrentState = transition.OnError.Value;
|
||||||
|
else
|
||||||
|
throw new AggregateException("An error occured while transitioning to a state", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(OnTransitioned != null)
|
||||||
|
await OnTransitioned.Invoke(CurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StateMachineTransition
|
||||||
|
{
|
||||||
|
public T From { get; set; }
|
||||||
|
public T To { get; set; }
|
||||||
|
public T? OnError { get; set; }
|
||||||
|
public Func<Task> OnTransitioning { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonlightServers.Daemon.Models;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Http.Controllers.Servers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/servers")]
|
||||||
|
public class ServersController : Controller
|
||||||
|
{
|
||||||
|
private readonly ServerService ServerService;
|
||||||
|
|
||||||
|
public ServersController(ServerService serverService)
|
||||||
|
{
|
||||||
|
ServerService = serverService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{serverId}/start")]
|
||||||
|
public async Task Start(int serverId)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetServer(serverId);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
throw new HttpApiException("No server with this id found", 404);
|
||||||
|
|
||||||
|
await server.StateMachine.TransitionTo(ServerState.Starting);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace MoonlightServers.Daemon.Models;
|
|
||||||
|
|
||||||
public class Allocation
|
|
||||||
{
|
|
||||||
public string IpAddress { get; set; }
|
|
||||||
public int Port { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,30 @@
|
|||||||
namespace MoonlightServers.Daemon.Models;
|
namespace MoonlightServers.Daemon.Models.Cache;
|
||||||
|
|
||||||
public class ServerData
|
public class ServerConfiguration
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string StartupCommand { get; set; }
|
|
||||||
public string OnlineDetection { get; set; }
|
|
||||||
public string StopCommand { get; set; }
|
|
||||||
public string DockerImage { get; set; }
|
|
||||||
public bool PullDockerImage { get; set; }
|
|
||||||
public string ParseConiguration { get; set; }
|
|
||||||
|
|
||||||
|
// Limits
|
||||||
public int Cpu { get; set; }
|
public int Cpu { get; set; }
|
||||||
public int Memory { get; set; }
|
public int Memory { get; set; }
|
||||||
public int Disk { get; set; }
|
public int Disk { get; set; }
|
||||||
|
|
||||||
public bool UseVirtualDisk { get; set; }
|
|
||||||
public int Bandwidth { get; set; }
|
public int Bandwidth { get; set; }
|
||||||
|
public bool UseVirtualDisk { get; set; }
|
||||||
|
|
||||||
|
// Start, Stop & Status
|
||||||
|
public string StartupCommand { get; set; }
|
||||||
|
public string StopCommand { get; set; }
|
||||||
|
public string OnlineDetection { get; set; }
|
||||||
|
|
||||||
|
// Container
|
||||||
|
public string DockerImage { get; set; }
|
||||||
|
public AllocationConfiguration[] Allocations { get; set; }
|
||||||
|
|
||||||
public List<Allocation> Allocations { get; set; }
|
|
||||||
public Dictionary<string, string> Variables { get; set; }
|
public Dictionary<string, string> Variables { get; set; }
|
||||||
|
|
||||||
|
public struct AllocationConfiguration
|
||||||
|
{
|
||||||
|
public string IpAddress { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
12
MoonlightServers.Daemon/Models/Server.cs
Normal file
12
MoonlightServers.Daemon/Models/Server.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using MoonlightServers.Daemon.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Models;
|
||||||
|
|
||||||
|
public class Server
|
||||||
|
{
|
||||||
|
public ServerState State => StateMachine.CurrentState;
|
||||||
|
public StateMachine<ServerState> StateMachine { get; set; }
|
||||||
|
public ServerConfiguration Configuration { get; set; }
|
||||||
|
public string? ContainerId { get; set; }
|
||||||
|
}
|
||||||
10
MoonlightServers.Daemon/Models/ServerState.cs
Normal file
10
MoonlightServers.Daemon/Models/ServerState.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Models;
|
||||||
|
|
||||||
|
public enum ServerState
|
||||||
|
{
|
||||||
|
Offline = 0,
|
||||||
|
Starting = 1,
|
||||||
|
Online = 2,
|
||||||
|
Stopping = 3,
|
||||||
|
Installing = 4
|
||||||
|
}
|
||||||
11
MoonlightServers.Daemon/Models/ServerTask.cs
Normal file
11
MoonlightServers.Daemon/Models/ServerTask.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Models;
|
||||||
|
|
||||||
|
public enum ServerTask
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
CreatingStorage = 1,
|
||||||
|
PullingDockerImage = 2,
|
||||||
|
RemovingContainer = 3,
|
||||||
|
CreatingContainer = 4,
|
||||||
|
StartingContainer = 5
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="Extensions\" />
|
||||||
<Folder Include="Http\Middleware\" />
|
<Folder Include="Http\Middleware\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using MoonCore.Attributes;
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Helpers;
|
||||||
using MoonCore.Models;
|
using MoonCore.Models;
|
||||||
|
using MoonlightServers.Daemon.Helpers;
|
||||||
using MoonlightServers.Daemon.Models;
|
using MoonlightServers.Daemon.Models;
|
||||||
|
using MoonlightServers.Daemon.Models.Cache;
|
||||||
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon.Services;
|
namespace MoonlightServers.Daemon.Services;
|
||||||
@@ -8,21 +11,31 @@ namespace MoonlightServers.Daemon.Services;
|
|||||||
[Singleton]
|
[Singleton]
|
||||||
public class ServerService
|
public class ServerService
|
||||||
{
|
{
|
||||||
private readonly List<ServerData> Servers = new();
|
private readonly List<Server> Servers = new();
|
||||||
private readonly RemoteService RemoteService;
|
|
||||||
private readonly ILogger<ServerService> Logger;
|
private readonly ILogger<ServerService> Logger;
|
||||||
|
private readonly RemoteService RemoteService;
|
||||||
|
private readonly IServiceProvider ServiceProvider;
|
||||||
|
private bool IsInitialized = false;
|
||||||
|
|
||||||
public ServerService(RemoteService remoteService, ILogger<ServerService> logger)
|
public ServerService(RemoteService remoteService, ILogger<ServerService> logger, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
RemoteService = remoteService;
|
RemoteService = remoteService;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
ServiceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize() //TODO: Add initialize call from panel
|
public async Task Initialize() //TODO: Add initialize call from panel
|
||||||
{
|
{
|
||||||
//TODO: Handle block creating servers while initializing
|
if (IsInitialized)
|
||||||
Logger.LogInformation("Loading servers from panel");
|
{
|
||||||
|
Logger.LogWarning("Ignoring initialize call: Already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsInitialized = true;
|
||||||
|
|
||||||
|
// Loading models and converting them
|
||||||
|
Logger.LogInformation("Fetching servers from panel");
|
||||||
var apiClient = await RemoteService.CreateHttpClient();
|
var apiClient = await RemoteService.CreateHttpClient();
|
||||||
|
|
||||||
var servers = await PagedData<ServerDataResponse>.All(async (page, pageSize) =>
|
var servers = await PagedData<ServerDataResponse>.All(async (page, pageSize) =>
|
||||||
@@ -31,14 +44,62 @@ public class ServerService
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var configurations = servers.Select(x => new ServerConfiguration()
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
StartupCommand = x.StartupCommand,
|
||||||
|
Allocations = x.Allocations.Select(y => new ServerConfiguration.AllocationConfiguration()
|
||||||
|
{
|
||||||
|
IpAddress = y.IpAddress,
|
||||||
|
Port = y.Port
|
||||||
|
}).ToArray(),
|
||||||
|
Variables = x.Variables,
|
||||||
|
OnlineDetection = x.OnlineDetection,
|
||||||
|
DockerImage = x.DockerImage,
|
||||||
|
UseVirtualDisk = x.UseVirtualDisk,
|
||||||
|
Bandwidth = x.Bandwidth,
|
||||||
|
Cpu = x.Cpu,
|
||||||
|
Disk = x.Disk,
|
||||||
|
Memory = x.Memory,
|
||||||
|
StopCommand = x.StopCommand
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
Logger.LogInformation("Initializing {count} servers", servers.Length);
|
Logger.LogInformation("Initializing {count} servers", servers.Length);
|
||||||
|
|
||||||
|
foreach (var configuration in configurations)
|
||||||
|
await InitializeServer(configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ImportServer(ServerData serverData)
|
private async Task InitializeServer(ServerConfiguration configuration)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Initializing server '{id}'", configuration.Id);
|
||||||
|
|
||||||
|
var server = new Server()
|
||||||
|
{
|
||||||
|
Configuration = configuration,
|
||||||
|
StateMachine = new(ServerState.Offline)
|
||||||
|
};
|
||||||
|
|
||||||
|
server.StateMachine.OnError += (state, exception) =>
|
||||||
|
{
|
||||||
|
Logger.LogError("Server {id} encountered an unhandled error while transitioning to {state}: {e}",
|
||||||
|
server.Configuration.Id,
|
||||||
|
state,
|
||||||
|
exception
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
server.StateMachine.AddTransition(ServerState.Offline, ServerState.Starting, ServerState.Offline, async () =>
|
||||||
|
await ServerActionHelper.Start(server, ServiceProvider)
|
||||||
|
);
|
||||||
|
|
||||||
|
lock (Servers)
|
||||||
|
Servers.Add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server? GetServer(int id)
|
||||||
{
|
{
|
||||||
lock (Servers)
|
lock (Servers)
|
||||||
Servers.Add(serverData);
|
return Servers.FirstOrDefault(x => x.Configuration.Id == id);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user