Started implementing server installation
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Models;
|
using MoonCore.Models;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
@@ -96,4 +97,23 @@ public class RemoteServersController : Controller
|
|||||||
TotalPages = total == 0 ? 0 : total / pageSize
|
TotalPages = total == 0 ? 0 : total / pageSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:int}/install")]
|
||||||
|
public async Task<ServerInstallDataResponse> GetInstall([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var server = await ServerRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Star)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
throw new HttpApiException("No server with this id found", 404);
|
||||||
|
|
||||||
|
return new ServerInstallDataResponse()
|
||||||
|
{
|
||||||
|
Script = server.Star.InstallScript,
|
||||||
|
DockerImage = server.Star.InstallDockerImage,
|
||||||
|
Shell = server.Star.InstallShell
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -380,6 +380,7 @@ public class StarImportExportService
|
|||||||
|
|
||||||
// Fix up special variables
|
// Fix up special variables
|
||||||
entry.Value = entry.Value.Replace("server.allocations.default.port", "SERVER_PORT");
|
entry.Value = entry.Value.Replace("server.allocations.default.port", "SERVER_PORT");
|
||||||
|
entry.Value = entry.Value.Replace("server.build.default.port", "SERVER_PORT");
|
||||||
|
|
||||||
pc.Entries.Add(entry);
|
pc.Entries.Add(entry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ public partial class Server
|
|||||||
|
|
||||||
await LogToConsole("Removing container");
|
await LogToConsole("Removing container");
|
||||||
await dockerClient.Containers.RemoveContainerAsync(container.ID, new());
|
await dockerClient.Containers.RemoveContainerAsync(container.ID, new());
|
||||||
|
|
||||||
|
RuntimeContainerId = null;
|
||||||
}
|
}
|
||||||
catch (DockerContainerNotFoundException){}
|
catch (DockerContainerNotFoundException){}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ public partial class Server
|
|||||||
.OnEntryAsync(InternalStop);
|
.OnEntryAsync(InternalStop);
|
||||||
|
|
||||||
StateMachine.Configure(ServerState.Installing)
|
StateMachine.Configure(ServerState.Installing)
|
||||||
.Permit(ServerTrigger.NotifyInstallContainerDied, ServerState.Offline);
|
.Permit(ServerTrigger.NotifyContainerDied, ServerState.Offline)
|
||||||
|
.OnEntryAsync(InternalInstall);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
59
MoonlightServers.Daemon/Abstractions/Server.Installation.cs
Normal file
59
MoonlightServers.Daemon/Abstractions/Server.Installation.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using Docker.DotNet;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Enums;
|
||||||
|
using MoonlightServers.Daemon.Extensions;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Abstractions;
|
||||||
|
|
||||||
|
public partial class Server
|
||||||
|
{
|
||||||
|
public async Task Install() => await StateMachine.FireAsync(ServerTrigger.Reinstall);
|
||||||
|
|
||||||
|
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
|
||||||
|
await File.WriteAllTextAsync(PathBuilder.File(installationHostPath, "install.sh"), installData.Script.Replace("\n\r", "\n") + "\n\n");
|
||||||
|
|
||||||
|
// Creating container configuration
|
||||||
|
var parameters = Configuration.ToInstallationCreateParameters(
|
||||||
|
runtimeHostPath,
|
||||||
|
installationHostPath,
|
||||||
|
InstallationContainerName,
|
||||||
|
installData.DockerImage,
|
||||||
|
installData.Shell
|
||||||
|
);
|
||||||
|
|
||||||
|
var dockerClient = ServiceProvider.GetRequiredService<DockerClient>();
|
||||||
|
|
||||||
|
var container = await dockerClient.Containers.CreateContainerAsync(parameters);
|
||||||
|
InstallationContainerId = container.ID;
|
||||||
|
|
||||||
|
await AttachConsole(InstallationContainerId);
|
||||||
|
|
||||||
|
await dockerClient.Containers.StartContainerAsync(InstallationContainerId, new());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,11 @@ public partial class Server
|
|||||||
var appConfiguration = ServiceProvider.GetRequiredService<AppConfiguration>();
|
var appConfiguration = ServiceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
var hostPath = PathBuilder.Dir(
|
var hostPath = PathBuilder.Dir(
|
||||||
appConfiguration.Storage.Volumes,
|
appConfiguration.Storage.Install,
|
||||||
Configuration.Id.ToString()
|
Configuration.Id.ToString()
|
||||||
);
|
);
|
||||||
|
|
||||||
await LogToConsole("Creating storage");
|
await LogToConsole("Creating installation storage");
|
||||||
|
|
||||||
// Create volume if missing
|
// Create volume if missing
|
||||||
if (!Directory.Exists(hostPath))
|
if (!Directory.Exists(hostPath))
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ public enum ServerTrigger
|
|||||||
Kill = 3,
|
Kill = 3,
|
||||||
Reinstall = 4,
|
Reinstall = 4,
|
||||||
NotifyOnline = 5,
|
NotifyOnline = 5,
|
||||||
NotifyContainerDied = 6,
|
NotifyContainerDied = 6
|
||||||
NotifyInstallContainerDied = 7
|
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,11 @@ namespace MoonlightServers.Daemon.Extensions;
|
|||||||
|
|
||||||
public static class ServerConfigurationExtensions
|
public static class ServerConfigurationExtensions
|
||||||
{
|
{
|
||||||
public static CreateContainerParameters ToRuntimeCreateParameters(this ServerConfiguration configuration, string hostPath, string containerName)
|
public static CreateContainerParameters ToRuntimeCreateParameters(this ServerConfiguration configuration,
|
||||||
|
string hostPath, string containerName)
|
||||||
{
|
{
|
||||||
var parameters = configuration.ToSharedCreateParameters();
|
var parameters = configuration.ToSharedCreateParameters();
|
||||||
|
|
||||||
#region Security
|
#region Security
|
||||||
|
|
||||||
parameters.HostConfig.CapDrop = new List<string>()
|
parameters.HostConfig.CapDrop = new List<string>()
|
||||||
@@ -75,7 +76,7 @@ public static class ServerConfigurationExtensions
|
|||||||
#region Mounts
|
#region Mounts
|
||||||
|
|
||||||
parameters.HostConfig.Mounts = new List<Mount>();
|
parameters.HostConfig.Mounts = new List<Mount>();
|
||||||
|
|
||||||
parameters.HostConfig.Mounts.Add(new()
|
parameters.HostConfig.Mounts.Add(new()
|
||||||
{
|
{
|
||||||
Source = hostPath,
|
Source = hostPath,
|
||||||
@@ -85,7 +86,7 @@ public static class ServerConfigurationExtensions
|
|||||||
});
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Port Bindings
|
#region Port Bindings
|
||||||
|
|
||||||
if (true) // TODO: Add network toggle
|
if (true) // TODO: Add network toggle
|
||||||
@@ -106,7 +107,7 @@ public static class ServerConfigurationExtensions
|
|||||||
HostIP = allocation.IpAddress
|
HostIP = allocation.IpAddress
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/udp", new List<PortBinding>
|
parameters.HostConfig.PortBindings.Add($"{allocation.Port}/udp", new List<PortBinding>
|
||||||
{
|
{
|
||||||
new()
|
new()
|
||||||
@@ -123,6 +124,63 @@ public static class ServerConfigurationExtensions
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CreateContainerParameters ToInstallationCreateParameters(
|
||||||
|
this ServerConfiguration configuration,
|
||||||
|
string runtimeHostPath,
|
||||||
|
string installationHostPath,
|
||||||
|
string containerName,
|
||||||
|
string installDockerImage,
|
||||||
|
string installShell
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var parameters = configuration.ToSharedCreateParameters();
|
||||||
|
|
||||||
|
// - Name
|
||||||
|
parameters.Name = containerName;
|
||||||
|
parameters.Hostname = containerName;
|
||||||
|
|
||||||
|
// - Image
|
||||||
|
parameters.Image = installDockerImage;
|
||||||
|
|
||||||
|
// - Env
|
||||||
|
parameters.Env = configuration
|
||||||
|
.ToEnvironmentVariables()
|
||||||
|
.Select(x => $"{x.Key}={x.Value}")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// -- Working directory
|
||||||
|
parameters.WorkingDir = "/mnt/server";
|
||||||
|
|
||||||
|
// - User
|
||||||
|
// Note: Some images might not work if we set a user here
|
||||||
|
parameters.User = "0:0";
|
||||||
|
|
||||||
|
// -- 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 = [installShell, "/mnt/install/install.sh"];
|
||||||
|
|
||||||
|
parameters.HostConfig.AutoRemove = true;
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
private static CreateContainerParameters ToSharedCreateParameters(this ServerConfiguration configuration)
|
private static CreateContainerParameters ToSharedCreateParameters(this ServerConfiguration configuration)
|
||||||
{
|
{
|
||||||
var parameters = new CreateContainerParameters()
|
var parameters = new CreateContainerParameters()
|
||||||
@@ -158,7 +216,7 @@ public static class ServerConfigurationExtensions
|
|||||||
long swapLimit = -1;
|
long swapLimit = -1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
// If swap is enabled globally and not disabled on this server, set swap
|
// If swap is enabled globally and not disabled on this server, set swap
|
||||||
if (!configuration.Limits.DisableSwap && config.Server.EnableSwap)
|
if (!configuration.Limits.DisableSwap && config.Server.EnableSwap)
|
||||||
swapLimit = (long)(memoryOverhead + memoryOverhead * config.Server.SwapMultiplier);
|
swapLimit = (long)(memoryOverhead + memoryOverhead * config.Server.SwapMultiplier);
|
||||||
@@ -168,7 +226,8 @@ public static class ServerConfigurationExtensions
|
|||||||
// Finalize limits by converting and updating the host config
|
// Finalize limits by converting and updating the host config
|
||||||
parameters.HostConfig.Memory = ByteConverter.FromMegaBytes((long)memoryOverhead, 1000).Bytes;
|
parameters.HostConfig.Memory = ByteConverter.FromMegaBytes((long)memoryOverhead, 1000).Bytes;
|
||||||
parameters.HostConfig.MemoryReservation = ByteConverter.FromMegaBytes(memoryLimit, 1000).Bytes;
|
parameters.HostConfig.MemoryReservation = ByteConverter.FromMegaBytes(memoryLimit, 1000).Bytes;
|
||||||
parameters.HostConfig.MemorySwap = swapLimit == -1 ? swapLimit : ByteConverter.FromMegaBytes(swapLimit, 1000).Bytes;
|
parameters.HostConfig.MemorySwap =
|
||||||
|
swapLimit == -1 ? swapLimit : ByteConverter.FromMegaBytes(swapLimit, 1000).Bytes;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -184,7 +243,7 @@ public static class ServerConfigurationExtensions
|
|||||||
#region DNS
|
#region DNS
|
||||||
|
|
||||||
// TODO: Read hosts dns settings?
|
// TODO: Read hosts dns settings?
|
||||||
|
|
||||||
parameters.HostConfig.DNS = /*config.Docker.DnsServers.Any() ? config.Docker.DnsServers :*/ new List<string>()
|
parameters.HostConfig.DNS = /*config.Docker.DnsServers.Any() ? config.Docker.DnsServers :*/ new List<string>()
|
||||||
{
|
{
|
||||||
"1.1.1.1",
|
"1.1.1.1",
|
||||||
@@ -213,9 +272,9 @@ public static class ServerConfigurationExtensions
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Labels
|
#region Labels
|
||||||
|
|
||||||
parameters.Labels = new Dictionary<string, string>();
|
parameters.Labels = new Dictionary<string, string>();
|
||||||
|
|
||||||
parameters.Labels.Add("Software", "Moonlight-Panel");
|
parameters.Labels.Add("Software", "Moonlight-Panel");
|
||||||
parameters.Labels.Add("ServerId", configuration.Id.ToString());
|
parameters.Labels.Add("ServerId", configuration.Id.ToString());
|
||||||
|
|
||||||
@@ -223,7 +282,7 @@ public static class ServerConfigurationExtensions
|
|||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<string, string> ToEnvironmentVariables(this ServerConfiguration configuration)
|
public static Dictionary<string, string> ToEnvironmentVariables(this ServerConfiguration configuration)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>
|
var result = new Dictionary<string, string>
|
||||||
@@ -236,11 +295,11 @@ public static class ServerConfigurationExtensions
|
|||||||
if (configuration.Allocations.Length > 0)
|
if (configuration.Allocations.Length > 0)
|
||||||
{
|
{
|
||||||
var mainAllocation = configuration.Allocations.First();
|
var mainAllocation = configuration.Allocations.First();
|
||||||
|
|
||||||
result.Add("SERVER_IP", mainAllocation.IpAddress);
|
result.Add("SERVER_IP", mainAllocation.IpAddress);
|
||||||
result.Add("SERVER_PORT", mainAllocation.Port.ToString());
|
result.Add("SERVER_PORT", mainAllocation.Port.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle allocation variables
|
// Handle allocation variables
|
||||||
var i = 1;
|
var i = 1;
|
||||||
foreach (var allocation in configuration.Allocations)
|
foreach (var allocation in configuration.Allocations)
|
||||||
|
|||||||
@@ -37,4 +37,15 @@ public class ServerPowerController : Controller
|
|||||||
|
|
||||||
await server.Stop();
|
await server.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("{serverId:int}/install")]
|
||||||
|
public async Task Install(int serverId, [FromQuery] bool runAsync = true)
|
||||||
|
{
|
||||||
|
var server = ServerService.GetServer(serverId);
|
||||||
|
|
||||||
|
if (server == null)
|
||||||
|
throw new HttpApiException("No server with this id found", 404);
|
||||||
|
|
||||||
|
await server.Install();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ public class ServerService : IHostedLifecycleService
|
|||||||
Server? server;
|
Server? server;
|
||||||
|
|
||||||
lock (Servers)
|
lock (Servers)
|
||||||
server = Servers.FirstOrDefault(x => x.RuntimeContainerId == message.ID);
|
server = Servers.FirstOrDefault(x => x.RuntimeContainerId == message.ID || x.InstallationContainerId == message.ID);
|
||||||
|
|
||||||
// TODO: Maybe implement a lookup for containers which id isn't set in the cache
|
// TODO: Maybe implement a lookup for containers which id isn't set in the cache
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||||
|
|
||||||
|
public class ServerInstallDataResponse
|
||||||
|
{
|
||||||
|
public string Shell { get; set; }
|
||||||
|
public string DockerImage { get; set; }
|
||||||
|
public string Script { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user