From 8dc37525ce7584b157354524529ad5abbb96345b Mon Sep 17 00:00:00 2001 From: Masu Baumgartner Date: Mon, 6 May 2024 18:34:50 +0200 Subject: [PATCH] Improved egg importing so it should be able to import any egg now --- .../Servers/Helpers/ImageConversionHelper.cs | 206 ++++++++++++++++-- 1 file changed, 182 insertions(+), 24 deletions(-) diff --git a/Moonlight/Features/Servers/Helpers/ImageConversionHelper.cs b/Moonlight/Features/Servers/Helpers/ImageConversionHelper.cs index 775553d..23c3f0d 100644 --- a/Moonlight/Features/Servers/Helpers/ImageConversionHelper.cs +++ b/Moonlight/Features/Servers/Helpers/ImageConversionHelper.cs @@ -9,6 +9,7 @@ using Moonlight.Features.Servers.Entities; using Moonlight.Features.Servers.Models; using Moonlight.Features.Servers.Models.Json; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Moonlight.Features.Servers.Helpers; @@ -30,7 +31,7 @@ public class ImageConversionHelper .Include(x => x.Variables) .First(x => x.Id == image.Id); - + var model = new ImageJson() { Name = imageWithData.Name, @@ -54,7 +55,7 @@ public class ImageConversionHelper foreach (var variable in imageWithData.Variables) model.Variables.Add(Mapper.Map(variable)); - + foreach (var dockerImage in imageWithData.DockerImages) model.DockerImages.Add(Mapper.Map(dockerImage)); @@ -88,20 +89,21 @@ public class ImageConversionHelper foreach (var variable in model.Variables) result.Variables.Add(Mapper.Map(variable)); - + foreach (var dockerImage in model.DockerImages) result.DockerImages.Add(Mapper.Map(dockerImage)); return Task.FromResult(result); } - - public Task ImportFromEggJson(string json) + + // Old import function which used the microsoft json parsing + public Task ImportFromEggJson_Old(string json) { var fixedJson = json; fixedJson = fixedJson.Replace("\\/", "/"); - - // Note: We use microsofts config system here as its dynamic and probably the best parsing method to use here + + // Note: We use microsofts config system instead of newtonsoft.json as its dynamic and probably the best parsing method to use here var eggData = new ConfigurationBuilder() .AddJsonString(fixedJson) .Build(); @@ -114,7 +116,11 @@ public class ImageConversionHelper result.Author = eggData["author"] ?? "Author was missing"; result.StartupCommand = eggData["startup"] ?? "Startup was missing"; result.StopCommand = eggData.GetSection("config")["stop"] ?? "Stop command was missing"; - + + // Some weird eggs use ^^C in as a stop command, so we need to handle this as well + // because moonlight handles power signals correctly, wings does/did not + result.StopCommand = result.StopCommand.Replace("^^C", "^C"); + // Startup detection var startupDetectionData = new ConfigurationBuilder() .AddJsonString(eggData.GetSection("config")["startup"] ?? "{}") @@ -122,14 +128,14 @@ public class ImageConversionHelper // Node Regex: As the online detection uses regex, we want to escape any special chars from egg imports // as eggs dont use regex and as such may contain characters which regex uses as meta characters. - // Without this escaping, many startup detection string wont work + // Without this escaping, many startup detection strings wont work result.OnlineDetection = Regex.Escape(startupDetectionData["done"] ?? "Online detection was missing"); // Docker image method 1: // Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/mindustry/egg-mindustry.json if (eggData["image"] != null) { - result.DockerImages.Add(new () + result.DockerImages.Add(new() { Name = eggData["image"]!, AutoPull = true, @@ -143,14 +149,14 @@ public class ImageConversionHelper foreach (var dockerImage in dockerImagesSection.GetChildren()) { - result.DockerImages.Add(new () + result.DockerImages.Add(new() { Name = dockerImage.Value ?? "Docker image name was missing", AutoPull = true, DisplayName = dockerImage.Key }); } - + // Docker image method 3: // Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/minecraft/java/cuberite/egg-cuberite.json var dockerImagesList = eggData.GetValue>("images"); @@ -159,7 +165,7 @@ public class ImageConversionHelper { foreach (var imageName in dockerImagesList) { - result.DockerImages.Add(new () + result.DockerImages.Add(new() { Name = imageName, AutoPull = true, @@ -167,14 +173,14 @@ public class ImageConversionHelper }); } } - + // Parse config var parseConfigData = new ConfigurationBuilder() .AddJsonString(eggData.GetSection("config")["files"] ?? "{}") .Build(); var parseConfigModels = new List(); - + foreach (var fileSection in parseConfigData.GetChildren()) { var model = new ServerParseConfig() @@ -189,20 +195,23 @@ public class ImageConversionHelper valueWithChecks = valueWithChecks.Replace("server.build.default.port", "SERVER_PORT"); valueWithChecks = valueWithChecks.Replace("server.build.env.", ""); - + model.Configuration.Add(findSection.Key, valueWithChecks); } - + parseConfigModels.Add(model); } result.ParseConfiguration = JsonConvert.SerializeObject(parseConfigModels); - + // Installation - result.InstallShell = "/bin/" + (eggData.GetSection("scripts").GetSection("installation")["entrypoint"] ?? "Install shell was missing"); - result.InstallScript = eggData.GetSection("scripts").GetSection("installation")["script"] ?? "Install script was missing"; - result.InstallDockerImage = eggData.GetSection("scripts").GetSection("installation")["container"] ?? "Install script was missing"; - + result.InstallShell = "/bin/" + (eggData.GetSection("scripts").GetSection("installation")["entrypoint"] ?? + "Install shell was missing"); + result.InstallScript = eggData.GetSection("scripts").GetSection("installation")["script"] ?? + "Install script was missing"; + result.InstallDockerImage = eggData.GetSection("scripts").GetSection("installation")["container"] ?? + "Install script was missing"; + // Variables foreach (var variableSection in eggData.GetSection("variables").GetChildren()) { @@ -225,10 +234,159 @@ public class ImageConversionHelper variable.AllowView = variableSection.GetValue("user_viewable") == 1; variable.AllowEdit = variableSection.GetValue("user_editable") == 1; } - + result.Variables.Add(variable); } - + + return Task.FromResult(result); + } + + public Task ImportFromEggJson(string json) + { + // Prepare json + var fixedJson = json; + fixedJson = fixedJson.Replace("\\/", "/"); + + // Prepare result object and set moonlight native fields + var result = new ServerImage(); + + result.AllocationsNeeded = 1; + result.AllowDockerImageChange = true; + + // + var egg = JObject.Parse(fixedJson); + + result.AllocationsNeeded = 1; // We cannot convert this value as its moonlight native + + // Basic values + result.Name = egg["name"]?.Value() ?? "Name was missing"; + result.Author = egg["author"]?.Value() ?? "Author was missing"; + result.StartupCommand = egg["startup"]?.Value() ?? "Startup was missing"; + result.StopCommand = egg["config"]?["stop"]?.Value() ?? "Stop command was missing"; + + // Some weird eggs use ^^C in as a stop command, so we need to handle this as well + // because moonlight handles power signals correctly, wings does/did not + result.StopCommand = result.StopCommand.Replace("^^C", "^C"); + + // Startup detection + var startup = JObject.Parse(egg["config"]?["startup"]?.Value() ?? "{}"); + + // Node Regex: As the online detection uses regex, we want to escape any special chars from egg imports + // as eggs dont use regex and as such may contain characters which regex uses as meta characters. + // Without this escaping, many startup detection strings wont work + result.OnlineDetection = Regex.Escape(startup["done"]?.Value() ?? "Online detection was missing"); + + // Docker images + + // Docker image method 1: + // Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/mindustry/egg-mindustry.json + if (egg["image"] != null) + { + result.DockerImages.Add(new() + { + Name = egg["image"]?.Value() ?? "Docker image not specified", + AutoPull = true, + DisplayName = egg["image"]?.Value() ?? "Docker image not specified" + }); + } + + // Docker image method 2: + // Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/minecraft/java/cuberite/egg-cuberite.json + if (egg["images"] != null) + { + var images = egg["images"]?.ToObject() ?? JArray.Parse("[]"); + + foreach (var imageName in images) + { + result.DockerImages.Add(new() + { + Name = imageName.Value() ?? "Docker image name not found", + AutoPull = true, + DisplayName = imageName.Value() ?? "Docker image name not found", + }); + } + } + + // Docker image method 3: + // Reference egg: https://github.com/parkervcp/eggs/blob/master/game_eggs/minecraft/java/paper/egg-paper.json + if (egg["docker_images"] != null) + { + var images = egg["docker_images"]?.ToObject() ?? JObject.Parse("{}"); + + foreach (var kvp in images) + { + result.DockerImages.Add(new() + { + Name = kvp.Value?.Value() ?? kvp.Key, + AutoPull = true, + DisplayName = kvp.Key + }); + } + } + + // Parse config + var parseConfig = JObject.Parse(egg["config"]?["files"]?.Value() ?? "{}"); + var parseConfigModels = new List(); + + foreach (var config in parseConfig) + { + var model = new ServerParseConfig() + { + File = config.Key, + Type = config.Value?["parser"]?.Value() ?? "parser was missing" + }; + + if (config.Value?["find"] == null) + continue; + + foreach (var findSection in config.Value!["find"]!.ToObject() ?? JObject.Parse("{}")) + { + var valueWithChecks = findSection.Value?.Value() ?? "Find value was null"; + + valueWithChecks = valueWithChecks.Replace("server.build.default.port", "SERVER_PORT"); + valueWithChecks = valueWithChecks.Replace("server.build.env.", ""); + + model.Configuration.Add(findSection.Key, valueWithChecks); + } + + parseConfigModels.Add(model); + } + + result.ParseConfiguration = JsonConvert.SerializeObject(parseConfigModels); + + // Installation + var installation = egg["scripts"]?["installation"] ?? JObject.Parse("{}"); + + result.InstallShell = "/bin/" + installation.Value("entrypoint") ?? "Install shell was missing"; + result.InstallScript = installation.Value("script") ?? "Install script was missing"; + result.InstallDockerImage = installation.Value("container") ?? "Install container was missing"; + + // Variables + foreach (var variableSection in egg["variables"]?.Children() ?? JEnumerable.Empty) + { + var variable = new ServerImageVariable() + { + DisplayName = variableSection.Value("name") ?? "Name was missing", + Description = variableSection.Value("description") ?? "Description was missing", + Key = variableSection.Value("env_variable") ?? "Environment variable was missing", + DefaultValue = variableSection.Value("default_value") ?? "Default value was missing", + }; + + // Check if it is a bool value + if (bool.TryParse(variableSection["user_viewable"]?.Value(), out _)) + { + variable.AllowView = variableSection.Value("user_viewable"); + variable.AllowEdit = variableSection.Value("user_editable"); + } + else + { + variable.AllowView = variableSection.Value("user_viewable") == 1; + variable.AllowEdit = variableSection.Value("user_editable") == 1; + } + + result.Variables.Add(variable); + } + return Task.FromResult(result); } } \ No newline at end of file