2025-03-23 17:30:50 -05:00
|
|
|
using AutoMapper;
|
|
|
|
|
using Docker.DotNet;
|
|
|
|
|
using Docker.DotNet.Models;
|
|
|
|
|
using LANCommander.SDK;
|
2025-09-24 20:58:23 -05:00
|
|
|
using LANCommander.SDK.Abstractions;
|
2025-03-23 17:30:50 -05:00
|
|
|
using LANCommander.SDK.Enums;
|
|
|
|
|
using LANCommander.SDK.PowerShell;
|
2025-03-24 00:19:33 -05:00
|
|
|
using LANCommander.Server.Data.Enums;
|
2025-03-23 17:30:50 -05:00
|
|
|
using LANCommander.Server.Services.Abstractions;
|
|
|
|
|
using LANCommander.Server.Services.Enums;
|
|
|
|
|
using LANCommander.Server.Services.Models;
|
2025-11-16 12:31:25 -06:00
|
|
|
using LANCommander.Server.Settings.Enums;
|
|
|
|
|
using LANCommander.Server.Settings.Models;
|
2025-03-23 17:30:50 -05:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2025-09-26 00:51:02 -05:00
|
|
|
using Microsoft.Extensions.Options;
|
2025-03-23 17:30:50 -05:00
|
|
|
|
|
|
|
|
namespace LANCommander.Server.Services.ServerEngines;
|
|
|
|
|
|
|
|
|
|
public class DockerServerEngine(
|
|
|
|
|
ILogger<DockerServerEngine> logger,
|
2025-11-16 12:31:25 -06:00
|
|
|
SettingsProvider<Settings.Settings> settingsProvider,
|
2025-11-26 01:09:52 -06:00
|
|
|
PowerShellScriptFactory powerShellScriptFactory,
|
2025-03-23 17:30:50 -05:00
|
|
|
IServiceProvider serviceProvider,
|
|
|
|
|
IMapper mapper,
|
2025-09-26 00:51:02 -05:00
|
|
|
IOptions<SDK.Models.Settings> settings) : IServerEngine
|
2025-03-23 17:30:50 -05:00
|
|
|
{
|
2025-03-25 18:07:03 -05:00
|
|
|
private ServerEngineConfiguration _config;
|
2025-03-24 00:19:33 -05:00
|
|
|
private Dictionary<Guid, DockerClient> _dockerClients = new();
|
2025-07-30 19:29:18 -05:00
|
|
|
private Dictionary<Guid, DockerContainer> _tracked { get; set; } = new();
|
2025-03-23 17:30:50 -05:00
|
|
|
public event EventHandler<ServerStatusUpdateEventArgs>? OnServerStatusUpdate;
|
|
|
|
|
public event EventHandler<ServerLogEventArgs>? OnServerLog;
|
|
|
|
|
|
2025-03-24 00:19:33 -05:00
|
|
|
public async Task InitializeAsync()
|
2025-03-23 17:30:50 -05:00
|
|
|
{
|
2025-11-16 12:31:25 -06:00
|
|
|
foreach (var serverEngineConfig in settingsProvider.CurrentValue.Server.GameServers.ServerEngines.Where(se => se.Type == ServerEngine.Docker))
|
2025-03-24 00:19:33 -05:00
|
|
|
{
|
2025-03-25 18:07:03 -05:00
|
|
|
if (serverEngineConfig != null && !String.IsNullOrWhiteSpace(serverEngineConfig.Address) && Uri.TryCreate(serverEngineConfig.Address, UriKind.Absolute, out var hostAddress))
|
|
|
|
|
_dockerClients[serverEngineConfig.Id] = new DockerClientConfiguration(hostAddress).CreateClient();
|
2025-03-24 00:19:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var scope = serviceProvider.CreateScope())
|
|
|
|
|
{
|
|
|
|
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
|
|
|
|
|
|
|
|
var servers = await serverService.GetAsync(s =>
|
|
|
|
|
s.Engine == ServerEngine.Docker);
|
|
|
|
|
|
|
|
|
|
foreach (var server in servers)
|
|
|
|
|
{
|
2025-03-25 01:09:21 -05:00
|
|
|
if (server.DockerHostId.HasValue && _dockerClients.ContainsKey(server.DockerHostId.Value))
|
2025-07-30 19:29:18 -05:00
|
|
|
_tracked[server.Id] = new DockerContainer
|
2025-03-25 20:48:19 -05:00
|
|
|
{
|
|
|
|
|
HostId = server.DockerHostId.Value,
|
|
|
|
|
Id = server.ContainerId,
|
|
|
|
|
Name = server.Name,
|
|
|
|
|
};
|
2025-03-24 00:19:33 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-23 21:17:05 -05:00
|
|
|
}
|
|
|
|
|
|
2025-03-25 20:48:19 -05:00
|
|
|
public async Task<IEnumerable<DockerContainer>> GetContainersAsync(Guid dockerHostId)
|
2025-03-23 21:17:05 -05:00
|
|
|
{
|
2025-03-24 00:19:33 -05:00
|
|
|
if (!_dockerClients.ContainsKey(dockerHostId))
|
2025-03-25 20:48:19 -05:00
|
|
|
return Enumerable.Empty<DockerContainer>();
|
2025-03-24 00:19:33 -05:00
|
|
|
|
|
|
|
|
var response = await _dockerClients[dockerHostId].Containers.ListContainersAsync(new ContainersListParameters()
|
2025-03-23 21:17:05 -05:00
|
|
|
{
|
|
|
|
|
All = true
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-25 20:48:19 -05:00
|
|
|
return response.Where(i => i != null).Select(i => new DockerContainer
|
|
|
|
|
{
|
|
|
|
|
Id = i.ID,
|
|
|
|
|
HostId = dockerHostId,
|
|
|
|
|
Name = i.Names.FirstOrDefault()
|
|
|
|
|
});
|
2025-03-23 21:17:05 -05:00
|
|
|
}
|
2025-03-24 00:19:33 -05:00
|
|
|
|
|
|
|
|
public bool IsManaging(Guid serverId)
|
|
|
|
|
{
|
2025-07-30 19:29:18 -05:00
|
|
|
return _tracked.ContainsKey(serverId);
|
2025-03-24 00:19:33 -05:00
|
|
|
}
|
|
|
|
|
|
2025-03-23 17:30:50 -05:00
|
|
|
public async Task StartAsync(Guid serverId)
|
|
|
|
|
{
|
|
|
|
|
Data.Models.Server server;
|
|
|
|
|
|
2025-07-30 19:29:18 -05:00
|
|
|
if (!_tracked.ContainsKey(serverId))
|
2025-03-24 00:19:33 -05:00
|
|
|
throw new Exception("Server is not being tracked by this engine.");
|
|
|
|
|
|
2025-03-23 17:30:50 -05:00
|
|
|
using (var scope = serviceProvider.CreateScope())
|
|
|
|
|
{
|
|
|
|
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
|
|
|
|
|
|
|
|
server = await serverService
|
|
|
|
|
.Query(q =>
|
|
|
|
|
{
|
|
|
|
|
return q
|
|
|
|
|
.Include(s => s.Scripts)
|
|
|
|
|
.Include(s => s.Game)
|
|
|
|
|
.Include(s => s.ServerConsoles);
|
|
|
|
|
}).GetAsync(serverId);
|
|
|
|
|
|
|
|
|
|
logger?.LogInformation("Starting server container \"{ServerName}\" for game {GameName}", server.Name, server.Game?.Title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var serverScript in server.Scripts.Where(s => s.Type == ScriptType.BeforeStart))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-11-26 01:09:52 -06:00
|
|
|
var script = powerShellScriptFactory.Create(ScriptType.BeforeStart);
|
2025-03-23 17:30:50 -05:00
|
|
|
|
|
|
|
|
script.AddVariable("Server", mapper.Map<SDK.Models.Server>(server));
|
|
|
|
|
|
|
|
|
|
script.UseWorkingDirectory(server.WorkingDirectory);
|
|
|
|
|
script.UseInline(serverScript.Contents);
|
|
|
|
|
script.UseShellExecute();
|
|
|
|
|
|
|
|
|
|
logger?.LogInformation("Executing script \"{ScriptName}\"", serverScript.Name);
|
|
|
|
|
|
2025-11-16 12:31:25 -06:00
|
|
|
if (settingsProvider.CurrentValue.Debug.EnableScriptDebugging)
|
2025-03-23 17:30:50 -05:00
|
|
|
script.EnableDebug();
|
|
|
|
|
|
|
|
|
|
await script.ExecuteAsync<int>();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger?.LogError(ex, "Error running script \"{ScriptName}\" for server \"{ServerName}\"", serverScript.Name, server.Name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-03-25 01:09:21 -05:00
|
|
|
if (server.DockerHostId.HasValue)
|
|
|
|
|
await _dockerClients[server.DockerHostId.Value].Containers
|
|
|
|
|
.StartContainerAsync(server.ContainerId, new ContainerStartParameters());
|
2025-03-23 17:30:50 -05:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger?.LogError(ex, "Could not start server container {ServerName} ({ServerId})", server.Name, server.Id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StopAsync(Guid serverId)
|
|
|
|
|
{
|
2025-07-30 19:29:18 -05:00
|
|
|
if (!_tracked.ContainsKey(serverId))
|
2025-03-24 00:19:33 -05:00
|
|
|
throw new Exception("Server is not being tracked by this engine.");
|
|
|
|
|
|
2025-03-23 17:30:50 -05:00
|
|
|
using (var scope = serviceProvider.CreateScope())
|
|
|
|
|
{
|
|
|
|
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
|
|
|
|
|
|
|
|
var server = await serverService.GetAsync(serverId);
|
|
|
|
|
|
|
|
|
|
logger?.LogInformation("Stopping server container \"{ServerName}\" for game {GameName}", server.Name, server.Game?.Title);
|
|
|
|
|
|
2025-03-25 01:09:21 -05:00
|
|
|
if (server.DockerHostId.HasValue)
|
|
|
|
|
await _dockerClients[server.DockerHostId.Value].Containers.StopContainerAsync(server.ContainerId, new ContainerStopParameters());
|
2025-03-23 17:30:50 -05:00
|
|
|
|
|
|
|
|
foreach (var serverScript in server.Scripts.Where(s => s.Type == ScriptType.AfterStop))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-11-26 01:09:52 -06:00
|
|
|
var script = powerShellScriptFactory.Create(ScriptType.AfterStop);
|
2025-03-23 17:30:50 -05:00
|
|
|
|
|
|
|
|
script.AddVariable("Server", mapper.Map<SDK.Models.Server>(server));
|
|
|
|
|
|
|
|
|
|
script.UseWorkingDirectory(server.WorkingDirectory);
|
|
|
|
|
script.UseInline(serverScript.Contents);
|
|
|
|
|
script.UseShellExecute();
|
|
|
|
|
|
|
|
|
|
logger?.LogInformation("Executing script \"{ScriptName}\"", serverScript.Name);
|
|
|
|
|
|
2025-11-16 12:31:25 -06:00
|
|
|
if (settingsProvider.CurrentValue.Debug.EnableScriptDebugging)
|
2025-03-23 17:30:50 -05:00
|
|
|
script.EnableDebug();
|
|
|
|
|
|
|
|
|
|
await script.ExecuteAsync<int>();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger?.LogError(ex, "Error running script \"{ScriptName}\" for server \"{ServerName}\"", serverScript.Name, server.Name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 19:29:18 -05:00
|
|
|
public async Task<ServerProcessStatus> GetStatusAsync(Guid serverId)
|
2025-03-23 17:30:50 -05:00
|
|
|
{
|
2025-03-24 00:19:33 -05:00
|
|
|
Data.Models.Server server = null;
|
2025-03-23 17:30:50 -05:00
|
|
|
|
|
|
|
|
using (var scope = serviceProvider.CreateScope())
|
|
|
|
|
{
|
2025-07-30 19:29:18 -05:00
|
|
|
if (!_tracked.ContainsKey(serverId))
|
2025-03-23 21:17:05 -05:00
|
|
|
{
|
|
|
|
|
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
2025-03-23 17:30:50 -05:00
|
|
|
|
2025-03-23 21:17:05 -05:00
|
|
|
server = await serverService.GetAsync(serverId);
|
|
|
|
|
|
|
|
|
|
if (server == null)
|
|
|
|
|
return ServerProcessStatus.Stopped;
|
2025-03-23 17:30:50 -05:00
|
|
|
|
2025-03-25 01:09:21 -05:00
|
|
|
if (!server.DockerHostId.HasValue)
|
|
|
|
|
return ServerProcessStatus.Stopped;
|
|
|
|
|
|
2025-03-23 21:17:05 -05:00
|
|
|
if (String.IsNullOrWhiteSpace(server.ContainerId))
|
|
|
|
|
return ServerProcessStatus.Stopped;
|
|
|
|
|
|
2025-07-30 19:29:18 -05:00
|
|
|
_tracked[serverId] = new DockerContainer
|
2025-03-25 20:48:19 -05:00
|
|
|
{
|
|
|
|
|
Id = server.ContainerId,
|
|
|
|
|
HostId = server.DockerHostId.Value,
|
|
|
|
|
Name = server.Name,
|
|
|
|
|
};
|
2025-03-23 21:17:05 -05:00
|
|
|
}
|
2025-03-23 17:30:50 -05:00
|
|
|
|
2025-03-24 00:19:33 -05:00
|
|
|
try
|
|
|
|
|
{
|
2025-07-30 19:29:18 -05:00
|
|
|
var container = await _dockerClients[_tracked[serverId].HostId].Containers.InspectContainerAsync(_tracked[serverId].Id);
|
2025-03-23 17:30:50 -05:00
|
|
|
|
2025-03-24 00:19:33 -05:00
|
|
|
if (container.State.Running)
|
|
|
|
|
return ServerProcessStatus.Running;
|
|
|
|
|
|
|
|
|
|
if (container.State.Paused)
|
|
|
|
|
return ServerProcessStatus.Stopped;
|
|
|
|
|
|
|
|
|
|
if (container.State.Dead)
|
|
|
|
|
return ServerProcessStatus.Stopped;
|
|
|
|
|
|
|
|
|
|
if (container.State.Restarting)
|
|
|
|
|
return ServerProcessStatus.Starting;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
}
|
2025-03-23 17:30:50 -05:00
|
|
|
|
|
|
|
|
return ServerProcessStatus.Stopped;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|