Implemented power state and task streaming over signalr
This commit is contained in:
@@ -8,5 +8,6 @@ public static class ServerMetaExtensions
|
||||
public static async Task NotifyTask(this Server server, ServerTask task)
|
||||
{
|
||||
server.Logger.LogInformation("Task: {task}", task);
|
||||
await server.InvokeTaskAdded(task.ToString());
|
||||
}
|
||||
}
|
||||
22
MoonlightServers.Daemon/Helpers/AccessTokenHelper.cs
Normal file
22
MoonlightServers.Daemon/Helpers/AccessTokenHelper.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json;
|
||||
using MoonCore.Attributes;
|
||||
using MoonCore.Extended.Helpers;
|
||||
using MoonlightServers.Daemon.Configuration;
|
||||
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
[Singleton]
|
||||
public class AccessTokenHelper
|
||||
{
|
||||
private readonly AppConfiguration Configuration;
|
||||
|
||||
public AccessTokenHelper(AppConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public bool Process(string accessToken, out Dictionary<string, JsonElement> data)
|
||||
{
|
||||
return JwtHelper.TryVerifyAndDecodePayload(Configuration.Security.Token, accessToken, out data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
public class ServerConsoleConnection
|
||||
{
|
||||
public DateTime AuthenticatedUntil { get; set; }
|
||||
public int ServerId { get; set; }
|
||||
}
|
||||
44
MoonlightServers.Daemon/Helpers/ServerConsoleMonitor.cs
Normal file
44
MoonlightServers.Daemon/Helpers/ServerConsoleMonitor.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using MoonlightServers.Daemon.Models;
|
||||
using MoonlightServers.DaemonShared.Enums;
|
||||
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
public class ServerConsoleMonitor
|
||||
{
|
||||
private readonly Server Server;
|
||||
private readonly IHubClients Clients;
|
||||
|
||||
public ServerConsoleMonitor(Server server, IHubClients clients)
|
||||
{
|
||||
Server = server;
|
||||
Clients = clients;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Server.StateMachine.OnTransitioned += OnPowerStateChanged;
|
||||
Server.OnTaskAdded += OnTaskNotify;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
Server.StateMachine.OnTransitioned -= OnPowerStateChanged;
|
||||
}
|
||||
|
||||
private async Task OnTaskNotify(string task)
|
||||
{
|
||||
await Clients.Group($"server-{Server.Configuration.Id}").SendAsync(
|
||||
"TaskNotify",
|
||||
task
|
||||
);
|
||||
}
|
||||
|
||||
private async Task OnPowerStateChanged(ServerState serverState)
|
||||
{
|
||||
await Clients.Group($"server-{Server.Configuration.Id}").SendAsync(
|
||||
"PowerStateChanged",
|
||||
serverState.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
35
MoonlightServers.Daemon/Http/Hubs/ServerConsoleHub.cs
Normal file
35
MoonlightServers.Daemon/Http/Hubs/ServerConsoleHub.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.Models;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
using MoonlightServers.DaemonShared.Enums;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Hubs;
|
||||
|
||||
public class ServerConsoleHub : Hub
|
||||
{
|
||||
private readonly ILogger<ServerConsoleHub> Logger;
|
||||
private readonly ServerConsoleService ConsoleService;
|
||||
|
||||
public ServerConsoleHub(ILogger<ServerConsoleHub> logger, ServerConsoleService consoleService)
|
||||
{
|
||||
Logger = logger;
|
||||
ConsoleService = consoleService;
|
||||
}
|
||||
|
||||
[HubMethodName("Authenticate")]
|
||||
public async Task Authenticate(string accessToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConsoleService.Authenticate(Context, accessToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("An unhandled error occured in the Authenticate method: {e}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
=> await ConsoleService.OnClientDisconnected(Context);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ public class Server
|
||||
public StateMachine<ServerState> StateMachine { get; set; }
|
||||
public ServerConfiguration Configuration { get; set; }
|
||||
public string? ContainerId { get; set; }
|
||||
public event Func<string, Task> OnTaskAdded;
|
||||
|
||||
// This can be used to stop streaming when the server gets destroyed or something
|
||||
public CancellationTokenSource Cancellation { get; set; }
|
||||
@@ -59,4 +60,16 @@ public class Server
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event invokers
|
||||
|
||||
public async Task InvokeTaskAdded(string task)
|
||||
{
|
||||
if(OnTaskAdded == null)
|
||||
return;
|
||||
|
||||
await OnTaskAdded.Invoke(task).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="MoonCore" Version="1.8.1" />
|
||||
<PackageReference Include="MoonCore.Extended" Version="1.2.4" />
|
||||
<PackageReference Include="MoonCore.Unix" Version="1.0.0" />
|
||||
|
||||
159
MoonlightServers.Daemon/Services/ServerConsoleService.cs
Normal file
159
MoonlightServers.Daemon/Services/ServerConsoleService.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using MoonCore.Attributes;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.Http.Hubs;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
[Singleton]
|
||||
public class ServerConsoleService
|
||||
{
|
||||
private readonly Dictionary<int, ServerConsoleMonitor> Monitors = new();
|
||||
private readonly Dictionary<string, ServerConsoleConnection> Connections = new();
|
||||
|
||||
private readonly ILogger<ServerConsoleService> Logger;
|
||||
private readonly AccessTokenHelper AccessTokenHelper;
|
||||
private readonly ServerService ServerService;
|
||||
|
||||
private readonly IHubContext<ServerConsoleHub> HubContext;
|
||||
|
||||
public ServerConsoleService(
|
||||
ILogger<ServerConsoleService> logger,
|
||||
AccessTokenHelper accessTokenHelper,
|
||||
ServerService serverService, IHubContext<ServerConsoleHub> hubContext)
|
||||
{
|
||||
Logger = logger;
|
||||
AccessTokenHelper = accessTokenHelper;
|
||||
ServerService = serverService;
|
||||
HubContext = hubContext;
|
||||
}
|
||||
|
||||
public Task OnClientDisconnected(HubCallerContext context)
|
||||
{
|
||||
ServerConsoleConnection? removedConnection;
|
||||
|
||||
lock (Connections)
|
||||
{
|
||||
removedConnection = Connections.GetValueOrDefault(context.ConnectionId);
|
||||
|
||||
if(removedConnection != null)
|
||||
Connections.Remove(context.ConnectionId);
|
||||
}
|
||||
|
||||
// Client never authenticated themselves, nothing to do
|
||||
if(removedConnection == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
Logger.LogDebug("Authenticated client {id} disconnected", context.ConnectionId);
|
||||
|
||||
// Count remaining clients requesting the same resource
|
||||
int count;
|
||||
|
||||
lock (Connections)
|
||||
{
|
||||
count = Connections
|
||||
.Values
|
||||
.Count(x => x.ServerId == removedConnection.ServerId);
|
||||
}
|
||||
|
||||
if(count > 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
ServerConsoleMonitor? monitor;
|
||||
|
||||
lock (Monitors)
|
||||
monitor = Monitors.GetValueOrDefault(removedConnection.ServerId);
|
||||
|
||||
if(monitor == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
Logger.LogDebug("Destroying console monitor for server {id}", removedConnection.ServerId);
|
||||
monitor.Destroy();
|
||||
|
||||
lock (Monitors)
|
||||
Monitors.Remove(removedConnection.ServerId);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Authenticate(HubCallerContext context, string accessToken)
|
||||
{
|
||||
// Validate access token
|
||||
if (!AccessTokenHelper.Process(accessToken, out var accessData))
|
||||
{
|
||||
Logger.LogDebug("Received invalid or expired access token. Closing connection");
|
||||
context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate access token data
|
||||
if (!accessData.ContainsKey("type") || !accessData.ContainsKey("serverId"))
|
||||
{
|
||||
Logger.LogDebug("Received invalid access token: Required parameters are missing. Closing connection");
|
||||
context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate access token type
|
||||
var type = accessData["type"].GetString()!;
|
||||
|
||||
if (type != "console")
|
||||
{
|
||||
Logger.LogDebug("Received invalid access token: Invalid type '{type}'. Closing connection", type);
|
||||
context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
var serverId = accessData["serverId"].GetInt32();
|
||||
var server = ServerService.GetServer(serverId);
|
||||
|
||||
if (server == null)
|
||||
{
|
||||
Logger.LogDebug("Received invalid access token: No server found with the requested id. Closing connection");
|
||||
context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
ServerConsoleConnection? connection;
|
||||
|
||||
lock (Connections)
|
||||
{
|
||||
connection = Connections
|
||||
.GetValueOrDefault(context.ConnectionId);
|
||||
}
|
||||
|
||||
if (connection == null) // If no existing connection has been found, we create a new one
|
||||
{
|
||||
connection = new()
|
||||
{
|
||||
ServerId = server.Configuration.Id,
|
||||
AuthenticatedUntil = DateTime.UtcNow.AddMinutes(10)
|
||||
};
|
||||
|
||||
lock (Connections)
|
||||
Connections.Add(context.ConnectionId, connection);
|
||||
|
||||
Logger.LogDebug("Connection {id} authenticated successfully", context.ConnectionId);
|
||||
}
|
||||
else
|
||||
Logger.LogDebug("Connection {id} re-authenticated successfully", context.ConnectionId);
|
||||
|
||||
ServerConsoleMonitor? monitor;
|
||||
|
||||
lock (Monitors)
|
||||
monitor = Monitors.GetValueOrDefault(server.Configuration.Id);
|
||||
|
||||
if (monitor == null)
|
||||
{
|
||||
Logger.LogDebug("Initializing console monitor for server {id}", server.Configuration.Id);
|
||||
|
||||
monitor = new ServerConsoleMonitor(server, HubContext.Clients);
|
||||
monitor.Initialize();
|
||||
|
||||
lock (Monitors)
|
||||
Monitors.Add(server.Configuration.Id, monitor);
|
||||
}
|
||||
|
||||
await HubContext.Groups.AddToGroupAsync(context.ConnectionId, $"server-{server.Configuration.Id}");
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using MoonCore.Extensions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Services;
|
||||
using MoonlightServers.Daemon.Configuration;
|
||||
using MoonlightServers.Daemon.Http.Hubs;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
|
||||
namespace MoonlightServers.Daemon;
|
||||
@@ -43,13 +44,17 @@ public class Startup
|
||||
await RegisterBase();
|
||||
await RegisterDocker();
|
||||
await RegisterServers();
|
||||
await RegisterSignalR();
|
||||
await RegisterCors();
|
||||
|
||||
await BuildWebApplication();
|
||||
|
||||
await UseBase();
|
||||
await UseCors();
|
||||
await UseBaseMiddleware();
|
||||
|
||||
await MapBase();
|
||||
await MapHubs();
|
||||
|
||||
await WebApplication.RunAsync();
|
||||
}
|
||||
@@ -71,7 +76,6 @@ public class Startup
|
||||
WebApplicationBuilder.Services.AddControllers();
|
||||
|
||||
WebApplicationBuilder.Services.AddApiExceptionHandler();
|
||||
WebApplicationBuilder.Services.AddSignalR();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -248,7 +252,7 @@ public class Startup
|
||||
WebApplicationBuilder.Services.AddHostedService<ApplicationStateService>(
|
||||
sp => sp.GetRequiredService<ApplicationStateService>()
|
||||
);
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -258,4 +262,43 @@ public class Startup
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Maps
|
||||
|
||||
private Task RegisterSignalR()
|
||||
{
|
||||
WebApplicationBuilder.Services.AddSignalR();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task MapHubs()
|
||||
{
|
||||
WebApplication.MapHub<ServerConsoleHub>("api/servers/console");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cors
|
||||
|
||||
private Task RegisterCors()
|
||||
{
|
||||
//TODO: IMPORTANT: CHANGE !!!
|
||||
WebApplicationBuilder.Services.AddCors(x =>
|
||||
x.AddDefaultPolicy(builder =>
|
||||
builder.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod().Build()
|
||||
)
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task UseCors()
|
||||
{
|
||||
WebApplication.UseCors();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user