using Docker.DotNet.Models; using MoonlightServers.Daemon.Models; namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker; public class ContainerConfigMapper { public CreateContainerParameters GetRuntimeConfig( string uuid, string name, RuntimeConfiguration configuration, string runtimeStoragePath ) { var parameters = new CreateContainerParameters() { HostConfig = new() }; ApplySharedOptions(parameters, configuration); // Limits if (configuration.Limits.CpuPercent.HasValue) { parameters.HostConfig.CPUQuota = configuration.Limits.CpuPercent.Value * 1000; parameters.HostConfig.CPUPeriod = 100000; parameters.HostConfig.CPUShares = 1024; } if (configuration.Limits.MemoryMb.HasValue) { var memoryLimit = configuration.Limits.MemoryMb.Value; // The overhead multiplier gives the container a little bit more memory to prevent crashes var memoryOverhead = memoryLimit + memoryLimit * 0.05f; parameters.HostConfig.Memory = (long)memoryOverhead * 1024L * 1024L; parameters.HostConfig.MemoryReservation = (long)memoryLimit * 1024L * 1024L; if (configuration.Limits.SwapMb.HasValue) { var rawSwap = configuration.Limits.SwapMb.Value * 1024L * 1024L; parameters.HostConfig.MemorySwap = rawSwap + (long)memoryOverhead; } } parameters.HostConfig.BlkioWeight = 100; parameters.HostConfig.OomKillDisable = true; // Storage parameters.HostConfig.Tmpfs = new Dictionary() { { "/tmp", "rw,exec,nosuid,size=100M" } // TODO: Config }; parameters.WorkingDir = "/home/container"; parameters.HostConfig.Mounts = new List(); parameters.HostConfig.Mounts.Add(new Mount() { Source = runtimeStoragePath, Target = "/home/container", Type = "bind", ReadOnly = false }); // Labels parameters.Labels = new Dictionary() { { "dev.moonlightpanel", "true" }, { "dev.moonlightpanel.id", uuid } }; foreach (var label in configuration.Environment.Labels) parameters.Labels.Add(label.Key, label.Value); // Security parameters.HostConfig.CapDrop = new List() { "setpcap", "mknod", "audit_write", "net_raw", "dac_override", "fowner", "fsetid", "net_bind_service", "sys_chroot", "setfcap" }; parameters.HostConfig.ReadonlyRootfs = true; parameters.HostConfig.SecurityOpt = new List() { "no-new-privileges" }; // Name parameters.Name = name; // Docker Image parameters.Image = configuration.Template.DockerImage; // Networking if (configuration.Network.Ports.Length > 0 && !string.IsNullOrWhiteSpace(configuration.Network.FriendlyName)) parameters.Hostname = configuration.Network.FriendlyName; parameters.ExposedPorts = new Dictionary(); parameters.HostConfig.PortBindings = new Dictionary>(); foreach (var port in configuration.Network.Ports) { parameters.ExposedPorts.Add($"{port.Port}/tcp", new()); parameters.ExposedPorts.Add($"{port.Port}/udp", new()); parameters.HostConfig.PortBindings.Add($"{port.Port}/tcp", new List { new() { HostPort = port.Port.ToString(), HostIP = port.IpAddress } }); parameters.HostConfig.PortBindings.Add($"{port.Port}/udp", new List { new() { HostPort = port.Port.ToString(), HostIP = port.IpAddress } }); } // TODO: Force outgoing ip stuff // User parameters.User = "1000:1000"; return parameters; } public CreateContainerParameters GetInstallConfig( string uuid, string name, RuntimeConfiguration runtimeConfiguration, InstallConfiguration installConfiguration, string runtimeStoragePath, string installStoragePath ) { var parameters = new CreateContainerParameters() { HostConfig = new() }; ApplySharedOptions(parameters, runtimeConfiguration); // Labels parameters.Labels = new Dictionary() { { "dev.moonlightpanel", "true" }, { "dev.moonlightpanel.id", uuid } }; foreach (var label in runtimeConfiguration.Environment.Labels) parameters.Labels.Add(label.Key, label.Value); // Name parameters.Name = name; // Docker Image parameters.Image = installConfiguration.DockerImage; // User parameters.User = "1000:1000"; // Storage parameters.WorkingDir = "/mnt/server"; parameters.HostConfig.Mounts = new List(); parameters.HostConfig.Mounts.Add(new Mount() { Source = runtimeStoragePath, Target = "/mnt/server", ReadOnly = false, Type = "bind" }); parameters.HostConfig.Mounts.Add(new Mount() { Source = installStoragePath, Target = "/mnt/install", ReadOnly = false, Type = "bind" }); // Command parameters.Cmd = [installConfiguration.Shell, "/mnt/install/install.sh"]; return parameters; } private void ApplySharedOptions( CreateContainerParameters parameters, RuntimeConfiguration configuration ) { // Input, output & error streams and TTY parameters.Tty = true; parameters.AttachStderr = true; parameters.AttachStdin = true; parameters.AttachStdout = true; parameters.OpenStdin = true; // Logging parameters.HostConfig.LogConfig = new() { Type = "json-file", // We need to use this provider, as the GetLogs endpoint needs it Config = new Dictionary() }; // Environment variables parameters.Env = new List() { $"STARTUP={configuration.Template.StartupCommand}", //TODO: Add timezone, add server ip }; if (configuration.Limits.MemoryMb.HasValue) parameters.Env.Add($"SERVER_MEMORY={configuration.Limits.MemoryMb.Value}"); if (configuration.Network.MainPort != null) { parameters.Env.Add($"SERVER_IP={configuration.Network.MainPort.IpAddress}"); parameters.Env.Add($"SERVER_PORT={configuration.Network.MainPort.Port}"); } // Handle port variables var i = 1; foreach (var port in configuration.Network.Ports) { parameters.Env.Add($"ML_PORT_{i}={port.Port}"); i++; } // Copy variables as env vars foreach (var variable in configuration.Environment.Variables) parameters.Env.Add($"{variable.Key}={variable.Value}"); } }