Recreated plugin with new project template. Started implementing server system daemon
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using MoonCore.Events;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
public class DockerEventService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<DockerEventService> Logger;
|
||||
private readonly DockerClient DockerClient;
|
||||
|
||||
private readonly EventSource<Message> ContainerSource = new();
|
||||
private readonly EventSource<Message> ImageSource = new();
|
||||
private readonly EventSource<Message> NetworkSource = new();
|
||||
|
||||
public DockerEventService(
|
||||
ILogger<DockerEventService> logger,
|
||||
DockerClient dockerClient
|
||||
)
|
||||
{
|
||||
Logger = logger;
|
||||
DockerClient = dockerClient;
|
||||
}
|
||||
|
||||
public async ValueTask<IAsyncDisposable> SubscribeContainerAsync(Func<Message, ValueTask> callback)
|
||||
=> await ContainerSource.SubscribeAsync(callback);
|
||||
|
||||
public async ValueTask<IAsyncDisposable> SubscribeImageAsync(Func<Message, ValueTask> callback)
|
||||
=> await ImageSource.SubscribeAsync(callback);
|
||||
|
||||
public async ValueTask<IAsyncDisposable> SubscribeNetworkAsync(Func<Message, ValueTask> callback)
|
||||
=> await NetworkSource.SubscribeAsync(callback);
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Logger.LogInformation("Starting docker event service");
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DockerClient.System.MonitorEventsAsync(
|
||||
new ContainerEventsParameters(),
|
||||
new Progress<Message>(async message =>
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
case "container":
|
||||
await ContainerSource.InvokeAsync(message);
|
||||
break;
|
||||
|
||||
case "image":
|
||||
await ImageSource.InvokeAsync(message);
|
||||
break;
|
||||
|
||||
case "network":
|
||||
await NetworkSource.InvokeAsync(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while processing docker event");
|
||||
}
|
||||
}),
|
||||
stoppingToken
|
||||
);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while listening for docker events: {message}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInformation("Stopping docker event service");
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using MoonCore.Attributes;
|
||||
using MoonlightServers.Daemon.Configuration;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
[Singleton]
|
||||
public class DockerImageService
|
||||
{
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly AppConfiguration Configuration;
|
||||
private readonly ILogger<DockerImageService> Logger;
|
||||
|
||||
private readonly Dictionary<string, TaskCompletionSource> PendingDownloads = new();
|
||||
|
||||
public DockerImageService(
|
||||
DockerClient dockerClient,
|
||||
ILogger<DockerImageService> logger,
|
||||
AppConfiguration configuration
|
||||
)
|
||||
{
|
||||
Configuration = configuration;
|
||||
DockerClient = dockerClient;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task DownloadAsync(string name, Action<string>? onProgressUpdated = null)
|
||||
{
|
||||
// If there is already a download for this image occuring, we want to wait for this to complete instead
|
||||
// of calling docker to download it again
|
||||
if (PendingDownloads.TryGetValue(name, out var downloadTaskCompletion))
|
||||
{
|
||||
await downloadTaskCompletion.Task;
|
||||
return;
|
||||
}
|
||||
|
||||
var tsc = new TaskCompletionSource();
|
||||
PendingDownloads.Add(name, tsc);
|
||||
|
||||
try
|
||||
{
|
||||
// Figure out if and which credentials to use by checking for the domain
|
||||
AuthConfig credentials = new();
|
||||
|
||||
var domain = GetDomainFromDockerImageName(name);
|
||||
|
||||
var configuredCredentials = Configuration.Docker.Credentials.FirstOrDefault(x =>
|
||||
x.Domain.Equals(domain, StringComparison.InvariantCultureIgnoreCase)
|
||||
);
|
||||
|
||||
// Apply credentials configuration if specified
|
||||
if (configuredCredentials != null)
|
||||
{
|
||||
credentials.Username = configuredCredentials.Username;
|
||||
credentials.Password = configuredCredentials.Password;
|
||||
credentials.Email = configuredCredentials.Email;
|
||||
}
|
||||
|
||||
// Now we want to pull the image
|
||||
await DockerClient.Images.CreateImageAsync(new()
|
||||
{
|
||||
FromImage = name
|
||||
},
|
||||
credentials,
|
||||
new Progress<JSONMessage>(async message =>
|
||||
{
|
||||
if (message.Progress == null)
|
||||
return;
|
||||
|
||||
var line = $"[{message.ID}] {message.ProgressMessage}";
|
||||
|
||||
Logger.LogDebug("{line}", line);
|
||||
|
||||
if (onProgressUpdated != null)
|
||||
onProgressUpdated.Invoke(line);
|
||||
})
|
||||
);
|
||||
|
||||
tsc.SetResult();
|
||||
PendingDownloads.Remove(name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("An error occured while download image {name}: {e}", name, e);
|
||||
|
||||
tsc.SetException(e);
|
||||
PendingDownloads.Remove(name);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetDomainFromDockerImageName(string name) // Method names are my passion ;)
|
||||
{
|
||||
var nameParts = name.Split("/");
|
||||
|
||||
// If it has 1 part -> just the image name (e.g., "ubuntu")
|
||||
// If it has 2 parts -> usually "user/image" (e.g., "library/ubuntu")
|
||||
// If it has 3 or more -> assume first part is the registry domain
|
||||
|
||||
if (nameParts.Length >= 3 ||
|
||||
(nameParts.Length >= 2 && nameParts[0].Contains('.') || nameParts[0].Contains(':')))
|
||||
return nameParts[0]; // Registry domain is explicitly specified
|
||||
|
||||
return "docker.io"; // Default Docker registry
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Docker.DotNet;
|
||||
using MoonCore.Attributes;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.Models.UnsafeDocker;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
[Singleton]
|
||||
public class DockerInfoService
|
||||
{
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly UnsafeDockerClient UnsafeDockerClient;
|
||||
|
||||
public DockerInfoService(DockerClient dockerClient, UnsafeDockerClient unsafeDockerClient)
|
||||
{
|
||||
DockerClient = dockerClient;
|
||||
UnsafeDockerClient = unsafeDockerClient;
|
||||
}
|
||||
|
||||
public async Task<string> GetDockerVersionAsync()
|
||||
{
|
||||
var version = await DockerClient.System.GetVersionAsync();
|
||||
|
||||
return $"{version.Version} commit {version.GitCommit} ({version.APIVersion})";
|
||||
}
|
||||
|
||||
public async Task<UsageDataReport> GetDataUsageAsync()
|
||||
{
|
||||
var response = await UnsafeDockerClient.GetDataUsageAsync();
|
||||
|
||||
var report = new UsageDataReport()
|
||||
{
|
||||
Containers = new UsageData()
|
||||
{
|
||||
Used = response.Containers.Sum(x => x.SizeRw),
|
||||
Reclaimable = 0
|
||||
},
|
||||
Images = new UsageData()
|
||||
{
|
||||
Used = response.Images.Sum(x => x.Size),
|
||||
Reclaimable = response.Images.Where(x => x.Containers == 0).Sum(x => x.Size)
|
||||
},
|
||||
BuildCache = new UsageData()
|
||||
{
|
||||
Used = response.BuildCache.Sum(x => x.Size),
|
||||
Reclaimable = response.BuildCache.Where(x => !x.InUse).Sum(x => x.Size)
|
||||
}
|
||||
};
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using MoonCore.Attributes;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Models;
|
||||
using MoonlightServers.Daemon.Configuration;
|
||||
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
[Singleton]
|
||||
public class RemoteService
|
||||
{
|
||||
private readonly HttpApiClient ApiClient;
|
||||
|
||||
public RemoteService(AppConfiguration configuration)
|
||||
{
|
||||
ApiClient = CreateHttpClient(configuration);
|
||||
}
|
||||
|
||||
public async Task GetStatusAsync()
|
||||
{
|
||||
await ApiClient.Get("api/remote/servers/node/trip");
|
||||
}
|
||||
|
||||
public async Task<CountedData<ServerDataResponse>> GetServersAsync(int startIndex, int count)
|
||||
{
|
||||
return await ApiClient.GetJson<CountedData<ServerDataResponse>>(
|
||||
$"api/remote/servers?startIndex={startIndex}&count={count}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<ServerDataResponse> GetServerAsync(int serverId)
|
||||
{
|
||||
return await ApiClient.GetJson<ServerDataResponse>(
|
||||
$"api/remote/servers/{serverId}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<ServerInstallDataResponse> GetServerInstallationAsync(int serverId)
|
||||
{
|
||||
return await ApiClient.GetJson<ServerInstallDataResponse>(
|
||||
$"api/remote/servers/{serverId}/install"
|
||||
);
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
private HttpApiClient CreateHttpClient(AppConfiguration configuration)
|
||||
{
|
||||
var formattedUrl = configuration.Remote.Url.EndsWith('/')
|
||||
? configuration.Remote.Url
|
||||
: configuration.Remote.Url + "/";
|
||||
|
||||
var httpClient = new HttpClient()
|
||||
{
|
||||
BaseAddress = new Uri(formattedUrl)
|
||||
};
|
||||
|
||||
httpClient.DefaultRequestHeaders.Add(
|
||||
"Authorization",
|
||||
$"Bearer {configuration.Security.TokenId}.{configuration.Security.Token}"
|
||||
);
|
||||
|
||||
return new HttpApiClient(httpClient);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using MoonlightServers.Daemon.Models;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
public class ServerConfigurationService
|
||||
{
|
||||
public async Task<RuntimeConfiguration> GetRuntimeConfigurationAsync(string uuid)
|
||||
{
|
||||
return new RuntimeConfiguration(
|
||||
new RuntimeLimitsConfig(400, null, 4096, null),
|
||||
new RuntimeStorageConfig("local", [], 10240),
|
||||
new RuntimeTemplateConfig(
|
||||
"ghcr.io/pterodactyl/yolks:java_21",
|
||||
"java -jar -Xmx{{SERVER_MEMORY}}M server.jar",
|
||||
"stop",
|
||||
["Done"]
|
||||
),
|
||||
new RuntimeNetworkConfig(
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
new RuntimePortConfig("0.0.0.0", 25565),
|
||||
[
|
||||
new RuntimePortConfig("0.0.0.0", 25565)
|
||||
]
|
||||
),
|
||||
new RuntimeEnvironmentConfig(
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "testy", "ytset" }
|
||||
},
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{ "SERVER_MEMORY", "4096" },
|
||||
{"MINECRAFT_VERSION", "latest"},
|
||||
{"SERVER_JARFILE", "server.jar"}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<InstallConfiguration> GetInstallConfigurationAsync(string uuid)
|
||||
{
|
||||
return new InstallConfiguration(
|
||||
"bash",
|
||||
"installer",
|
||||
await File.ReadAllTextAsync("/home/chiara/Documents/daemonScripts/install.sh")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using MoonCore.Models;
|
||||
using MoonlightServers.Daemon.Mappers;
|
||||
using MoonlightServers.Daemon.Models.Cache;
|
||||
using MoonlightServers.Daemon.ServerSystem;
|
||||
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
public class ServerService : IHostedLifecycleService
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, Server> Servers = new();
|
||||
private readonly ILogger<ServerService> Logger;
|
||||
private readonly ServerFactory ServerFactory;
|
||||
private readonly RemoteService RemoteService;
|
||||
private readonly ServerConfigurationMapper ConfigurationMapper;
|
||||
|
||||
public ServerService(
|
||||
ILogger<ServerService> logger,
|
||||
ServerFactory serverFactory,
|
||||
RemoteService remoteService,
|
||||
ServerConfigurationMapper configurationMapper
|
||||
)
|
||||
{
|
||||
Logger = logger;
|
||||
ServerFactory = serverFactory;
|
||||
RemoteService = remoteService;
|
||||
ConfigurationMapper = configurationMapper;
|
||||
}
|
||||
|
||||
public Server? GetById(int id)
|
||||
=> Servers.GetValueOrDefault(id);
|
||||
|
||||
public async Task InitializeAsync(ServerConfiguration configuration)
|
||||
{
|
||||
var existingServer = Servers.GetValueOrDefault(configuration.Id);
|
||||
|
||||
if (existingServer != null)
|
||||
{
|
||||
existingServer.Context.Configuration = configuration;
|
||||
// TODO: Implement a way for components to get notified
|
||||
}
|
||||
else
|
||||
{
|
||||
var server = await ServerFactory.CreateAsync(configuration);
|
||||
Servers[configuration.Id] = server;
|
||||
|
||||
await server.InitializeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitializeByIdAsync(int id)
|
||||
{
|
||||
var serverData = await RemoteService.GetServerAsync(id);
|
||||
var config = ConfigurationMapper.FromServerDataResponse(serverData);
|
||||
|
||||
await InitializeAsync(config);
|
||||
}
|
||||
|
||||
private async Task InitializeAllAsync()
|
||||
{
|
||||
Logger.LogDebug("Initialing servers from panel");
|
||||
|
||||
var servers = await CountedData<ServerDataResponse>.LoadAllAsync(async (startIndex, count) =>
|
||||
await RemoteService.GetServersAsync(startIndex, count)
|
||||
);
|
||||
|
||||
foreach (var serverData in servers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = ConfigurationMapper.FromServerDataResponse(serverData);
|
||||
|
||||
await InitializeAsync(config);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An error occured while initializing server {id}", serverData.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Lifetime handlers
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public async Task StartedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await InitializeAllAsync();
|
||||
}
|
||||
|
||||
public Task StartingAsync(CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task StoppedAsync(CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public async Task StoppingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogDebug("Stopping server service. Disposing servers");
|
||||
|
||||
foreach (var server in Servers.Values)
|
||||
await server.DisposeAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user