mirror of
				https://github.com/chylex/Minecraft-Phantom-Panel.git
				synced 2025-11-03 18:40:15 +01:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			339b958e45
			...
			96456f90bc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						96456f90bc
	
				 | 
					
					
						|||
| 
						
						
							
						
						e69b0245aa
	
				 | 
					
					
						|||
| 
						
						
							
						
						cd332a6571
	
				 | 
					
					
						|||
| 
						
						
							
						
						2a9bb9e6ac
	
				 | 
					
					
						|||
| 
						
						
							
						
						55b853d227
	
				 | 
					
					
						|||
| 
						
						
							
						
						627e7436fd
	
				 | 
					
					
						
@@ -9,10 +9,10 @@
 | 
			
		||||
      <env name="AGENT_NAME" value="Agent 1" />
 | 
			
		||||
      <env name="ALLOWED_RCON_PORTS" value="25575,27000,27001" />
 | 
			
		||||
      <env name="ALLOWED_SERVER_PORTS" value="25565,26000,26001" />
 | 
			
		||||
      <env name="CONTROLLER_HOST" value="localhost" />
 | 
			
		||||
      <env name="JAVA_SEARCH_PATH" value="~/.jdks" />
 | 
			
		||||
      <env name="MAX_INSTANCES" value="3" />
 | 
			
		||||
      <env name="MAX_MEMORY" value="12G" />
 | 
			
		||||
      <env name="SERVER_HOST" value="localhost" />
 | 
			
		||||
    </envs>
 | 
			
		||||
    <option name="USE_EXTERNAL_CONSOLE" value="0" />
 | 
			
		||||
    <option name="USE_MONO" value="0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@
 | 
			
		||||
      <env name="AGENT_NAME" value="Agent 2" />
 | 
			
		||||
      <env name="ALLOWED_RCON_PORTS" value="27002-27006" />
 | 
			
		||||
      <env name="ALLOWED_SERVER_PORTS" value="26002-26006" />
 | 
			
		||||
      <env name="CONTROLLER_HOST" value="localhost" />
 | 
			
		||||
      <env name="JAVA_SEARCH_PATH" value="~/.jdks" />
 | 
			
		||||
      <env name="MAX_INSTANCES" value="5" />
 | 
			
		||||
      <env name="MAX_MEMORY" value="10G" />
 | 
			
		||||
      <env name="SERVER_HOST" value="localhost" />
 | 
			
		||||
    </envs>
 | 
			
		||||
    <option name="USE_EXTERNAL_CONSOLE" value="0" />
 | 
			
		||||
    <option name="USE_MONO" value="0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@
 | 
			
		||||
      <env name="AGENT_NAME" value="Agent 3" />
 | 
			
		||||
      <env name="ALLOWED_RCON_PORTS" value="27007" />
 | 
			
		||||
      <env name="ALLOWED_SERVER_PORTS" value="26007" />
 | 
			
		||||
      <env name="CONTROLLER_HOST" value="localhost" />
 | 
			
		||||
      <env name="JAVA_SEARCH_PATH" value="~/.jdks" />
 | 
			
		||||
      <env name="MAX_INSTANCES" value="1" />
 | 
			
		||||
      <env name="MAX_MEMORY" value="2560M" />
 | 
			
		||||
      <env name="SERVER_HOST" value="localhost" />
 | 
			
		||||
    </envs>
 | 
			
		||||
    <option name="USE_EXTERNAL_CONSOLE" value="0" />
 | 
			
		||||
    <option name="USE_MONO" value="0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Server + Agent x3" type="CompoundRunConfigurationType">
 | 
			
		||||
  <configuration default="false" name="Controller + Web + Agent x3" type="CompoundRunConfigurationType">
 | 
			
		||||
    <toRun name="Agent 1" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Agent 2" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Agent 3" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Server" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Controller" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Web" type="DotNetProject" />
 | 
			
		||||
    <method v="2" />
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										8
									
								
								.run/Controller + Web + Agent.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.run/Controller + Web + Agent.run.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Controller + Web + Agent" type="CompoundRunConfigurationType">
 | 
			
		||||
    <toRun name="Agent 1" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Controller" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Web" type="DotNetProject" />
 | 
			
		||||
    <method v="2" />
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Server" type="DotNetProject" factoryName=".NET Project">
 | 
			
		||||
  <configuration default="false" name="Controller" type="DotNetProject" factoryName=".NET Project">
 | 
			
		||||
    <option name="EXE_PATH" value="$PROJECT_DIR$/.artifacts/bin/Phantom.Controller/debug/Phantom.Controller.exe" />
 | 
			
		||||
    <option name="PROGRAM_PARAMETERS" value="" />
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Server" />
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Controller" />
 | 
			
		||||
    <option name="PASS_PARENT_ENVS" value="1" />
 | 
			
		||||
    <envs>
 | 
			
		||||
      <env name="ASPNETCORE_ENVIRONMENT" value="Development" />
 | 
			
		||||
      <env name="AGENT_RPC_SERVER_HOST" value="localhost" />
 | 
			
		||||
      <env name="PG_DATABASE" value="postgres" />
 | 
			
		||||
      <env name="PG_HOST" value="localhost" />
 | 
			
		||||
      <env name="PG_PASS" value="development" />
 | 
			
		||||
      <env name="PG_PORT" value="9402" />
 | 
			
		||||
      <env name="PG_PORT" value="9403" />
 | 
			
		||||
      <env name="PG_USER" value="postgres" />
 | 
			
		||||
      <env name="RPC_SERVER_HOST" value="localhost" />
 | 
			
		||||
      <env name="WEB_SERVER_HOST" value="localhost" />
 | 
			
		||||
      <env name="WEB_RPC_SERVER_HOST" value="localhost" />
 | 
			
		||||
    </envs>
 | 
			
		||||
    <option name="USE_EXTERNAL_CONSOLE" value="0" />
 | 
			
		||||
    <option name="USE_MONO" value="0" />
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Server + Agent" type="CompoundRunConfigurationType">
 | 
			
		||||
    <toRun name="Agent 1" type="DotNetProject" />
 | 
			
		||||
    <toRun name="Server" type="DotNetProject" />
 | 
			
		||||
    <method v="2" />
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										26
									
								
								.run/Web.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.run/Web.run.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Web" type="DotNetProject" factoryName=".NET Project">
 | 
			
		||||
    <option name="EXE_PATH" value="$PROJECT_DIR$/.artifacts/bin/Phantom.Web/debug/Phantom.Web.exe" />
 | 
			
		||||
    <option name="PROGRAM_PARAMETERS" value="" />
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Web" />
 | 
			
		||||
    <option name="PASS_PARENT_ENVS" value="1" />
 | 
			
		||||
    <envs>
 | 
			
		||||
      <env name="ASPNETCORE_ENVIRONMENT" value="Development" />
 | 
			
		||||
      <env name="CONTROLLER_HOST" value="localhost" />
 | 
			
		||||
      <env name="WEB_KEY" value="BMNHM9RRPMCBBY29D9XHS6KBKZSRY7F5XFN27YZX96XXWJC2NM2D6YRHM9PZN9JGQGCSJ6FMB2GGZ" />
 | 
			
		||||
      <env name="WEB_SERVER_HOST" value="localhost" />
 | 
			
		||||
    </envs>
 | 
			
		||||
    <option name="USE_EXTERNAL_CONSOLE" value="0" />
 | 
			
		||||
    <option name="USE_MONO" value="0" />
 | 
			
		||||
    <option name="RUNTIME_ARGUMENTS" value="" />
 | 
			
		||||
    <option name="PROJECT_PATH" value="$PROJECT_DIR$/Web/Phantom.Web/Phantom.Web.csproj" />
 | 
			
		||||
    <option name="PROJECT_EXE_PATH_TRACKING" value="1" />
 | 
			
		||||
    <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
 | 
			
		||||
    <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
 | 
			
		||||
    <option name="PROJECT_KIND" value="DotNetCore" />
 | 
			
		||||
    <option name="PROJECT_TFM" value="net8.0" />
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Build" />
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										1
									
								
								.workdir/Controller/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.workdir/Controller/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.workdir/Controller/secrets/web.key
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.workdir/Controller/secrets/web.key
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
<EFBFBD><EFBFBD>h?Ο<05>Bx
 | 
			
		||||
<02>
 | 
			
		||||
							
								
								
									
										1
									
								
								.workdir/Controller/secrets/web.secret
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.workdir/Controller/secrets/web.secret
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
T<EFBFBD>./g<11><>N<EFBFBD><4E>t<EFBFBD>$<24>!<21>(<28><>#<23>~<7E><>}<14><:
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Command; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Command;
 | 
			
		||||
 | 
			
		||||
public static class MinecraftCommand {
 | 
			
		||||
	public const string SaveOn = "save-on";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
using Phantom.Utils.Collections;
 | 
			
		||||
using Phantom.Utils.Processes;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Instance; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Instance;
 | 
			
		||||
 | 
			
		||||
public sealed class InstanceProcess : IDisposable {
 | 
			
		||||
	public InstanceProperties InstanceProperties { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Phantom.Common.Data.Java;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Java; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Java;
 | 
			
		||||
 | 
			
		||||
public sealed class JavaRuntimeExecutable {
 | 
			
		||||
	internal string ExecutablePath { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Java;
 | 
			
		||||
using Phantom.Agent.Minecraft.Server;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher;
 | 
			
		||||
 | 
			
		||||
public sealed record LaunchServices(MinecraftServerExecutables ServerExecutables, JavaRuntimeRepository JavaRuntimeRepository);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ using Phantom.Agent.Minecraft.Instance;
 | 
			
		||||
using Phantom.Utils.IO;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher.Types; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher.Types;
 | 
			
		||||
 | 
			
		||||
public sealed class FabricLauncher : BaseLauncher {
 | 
			
		||||
	public FabricLauncher(InstanceProperties instanceProperties) : base(instanceProperties) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Server;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher.Types; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher.Types;
 | 
			
		||||
 | 
			
		||||
public sealed class InvalidLauncher : IServerLauncher {
 | 
			
		||||
	public static InvalidLauncher Instance { get; } = new ();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Instance;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher.Types; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Launcher.Types;
 | 
			
		||||
 | 
			
		||||
public sealed class VanillaLauncher : BaseLauncher {
 | 
			
		||||
	public VanillaLauncher(InstanceProperties instanceProperties) : base(instanceProperties) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Java;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Properties; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Properties;
 | 
			
		||||
 | 
			
		||||
abstract class MinecraftServerProperty<T> {
 | 
			
		||||
	private readonly string key;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Server; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Server;
 | 
			
		||||
 | 
			
		||||
public sealed class DownloadProgressEventArgs : EventArgs {
 | 
			
		||||
	public ulong DownloadedBytes { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Server; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Server;
 | 
			
		||||
 | 
			
		||||
sealed record MinecraftServerExecutableDownloadListener(EventHandler<DownloadProgressEventArgs> DownloadProgressEventHandler, CancellationToken CancellationToken);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ using System.Text;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Server; 
 | 
			
		||||
namespace Phantom.Agent.Minecraft.Server;
 | 
			
		||||
 | 
			
		||||
public sealed class ServerStatusProtocol {
 | 
			
		||||
	private readonly ILogger logger;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								Agent/Phantom.Agent.Rpc/ControllerConnection.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Agent/Phantom.Agent.Rpc/ControllerConnection.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.Agent;
 | 
			
		||||
using Phantom.Utils.Rpc;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Rpc;
 | 
			
		||||
 | 
			
		||||
public sealed class ControllerConnection {
 | 
			
		||||
	private static readonly ILogger Logger = PhantomLogger.Create(nameof(ControllerConnection));
 | 
			
		||||
 | 
			
		||||
	private readonly RpcConnectionToServer<IMessageToControllerListener> connection;
 | 
			
		||||
	
 | 
			
		||||
	public ControllerConnection(RpcConnectionToServer<IMessageToControllerListener> connection) {
 | 
			
		||||
		this.connection = connection;
 | 
			
		||||
		Logger.Information("Connection ready.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Task Send<TMessage>(TMessage message) where TMessage : IMessageToController {
 | 
			
		||||
		return connection.Send(message);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Common.Messages.Agent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Rpc;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Rpc;
 | 
			
		||||
@@ -9,10 +11,10 @@ sealed class KeepAliveLoop {
 | 
			
		||||
 | 
			
		||||
	private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromSeconds(10);
 | 
			
		||||
 | 
			
		||||
	private readonly RpcServerConnection connection;
 | 
			
		||||
	private readonly RpcConnectionToServer<IMessageToControllerListener> connection;
 | 
			
		||||
	private readonly CancellationTokenSource cancellationTokenSource = new ();
 | 
			
		||||
 | 
			
		||||
	public KeepAliveLoop(RpcServerConnection connection) {
 | 
			
		||||
	public KeepAliveLoop(RpcConnectionToServer<IMessageToControllerListener> connection) {
 | 
			
		||||
		this.connection = connection;
 | 
			
		||||
		Task.Run(Run);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\..\Common\Phantom.Common.Messages\Phantom.Common.Messages.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\..\Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
using NetMQ;
 | 
			
		||||
using NetMQ.Sockets;
 | 
			
		||||
using Phantom.Common.Data.Agent;
 | 
			
		||||
using Phantom.Common.Messages.Agent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Rpc;
 | 
			
		||||
using Phantom.Utils.Rpc.Sockets;
 | 
			
		||||
using Phantom.Utils.Tasks;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Rpc;
 | 
			
		||||
 | 
			
		||||
public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> {
 | 
			
		||||
	public static Task Launch(RpcClientSocket<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> socket, AgentInfo agentInfo, IMessageToAgentListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
 | 
			
		||||
		return new RpcClientRuntime(socket, messageListener, disconnectSemaphore, receiveCancellationToken).Launch();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private RpcClientRuntime(RpcClientSocket<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> socket, IMessageToAgentListener messageListener, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket, messageListener, disconnectSemaphore, receiveCancellationToken) {}
 | 
			
		||||
 | 
			
		||||
	protected override void RunWithConnection(ClientSocket socket, RpcConnectionToServer<IMessageToControllerListener> connection, ILogger logger, TaskManager taskManager) {
 | 
			
		||||
		var keepAliveLoop = new KeepAliveLoop(connection);
 | 
			
		||||
		try {
 | 
			
		||||
			base.RunWithConnection(socket, connection, logger, taskManager);
 | 
			
		||||
		} finally {
 | 
			
		||||
			keepAliveLoop.Cancel();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected override async Task Disconnect(ClientSocket socket, ILogger logger) {
 | 
			
		||||
		var unregisterMessageBytes = AgentMessageRegistries.ToController.Write(new UnregisterAgentMessage()).ToArray();
 | 
			
		||||
		try {
 | 
			
		||||
			await socket.SendAsync(unregisterMessageBytes).AsTask().WaitAsync(TimeSpan.FromSeconds(5), CancellationToken.None);
 | 
			
		||||
		} catch (TimeoutException) {
 | 
			
		||||
			logger.Error("Timed out communicating agent shutdown with the controller.");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,107 +0,0 @@
 | 
			
		||||
using NetMQ;
 | 
			
		||||
using NetMQ.Sockets;
 | 
			
		||||
using Phantom.Common.Data.Agent;
 | 
			
		||||
using Phantom.Common.Messages;
 | 
			
		||||
using Phantom.Common.Messages.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Utils.Rpc;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
using Phantom.Utils.Tasks;
 | 
			
		||||
using Serilog;
 | 
			
		||||
using Serilog.Events;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Rpc;
 | 
			
		||||
 | 
			
		||||
public sealed class RpcLauncher : RpcRuntime<ClientSocket> {
 | 
			
		||||
	public static Task Launch(RpcConfiguration config, AgentAuthToken authToken, AgentInfo agentInfo, Func<RpcServerConnection, IMessageToAgentListener> listenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
 | 
			
		||||
		var socket = new ClientSocket();
 | 
			
		||||
		var options = socket.Options;
 | 
			
		||||
 | 
			
		||||
		options.CurveServerCertificate = config.ServerCertificate;
 | 
			
		||||
		options.CurveCertificate = new NetMQCertificate();
 | 
			
		||||
		options.HelloMessage = MessageRegistries.ToServer.Write(new RegisterAgentMessage(authToken, agentInfo)).ToArray();
 | 
			
		||||
 | 
			
		||||
		return new RpcLauncher(config, socket, agentInfo.Guid, listenerFactory, disconnectSemaphore, receiveCancellationToken).Launch();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private readonly RpcConfiguration config;
 | 
			
		||||
	private readonly Guid agentGuid;
 | 
			
		||||
	private readonly Func<RpcServerConnection, IMessageToAgentListener> messageListenerFactory;
 | 
			
		||||
 | 
			
		||||
	private readonly SemaphoreSlim disconnectSemaphore;
 | 
			
		||||
	private readonly CancellationToken receiveCancellationToken;
 | 
			
		||||
 | 
			
		||||
	private RpcLauncher(RpcConfiguration config, ClientSocket socket, Guid agentGuid, Func<RpcServerConnection, IMessageToAgentListener> messageListenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(config, socket) {
 | 
			
		||||
		this.config = config;
 | 
			
		||||
		this.agentGuid = agentGuid;
 | 
			
		||||
		this.messageListenerFactory = messageListenerFactory;
 | 
			
		||||
		this.disconnectSemaphore = disconnectSemaphore;
 | 
			
		||||
		this.receiveCancellationToken = receiveCancellationToken;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected override void Connect(ClientSocket socket) {
 | 
			
		||||
		var logger = config.RuntimeLogger;
 | 
			
		||||
		var url = config.TcpUrl;
 | 
			
		||||
 | 
			
		||||
		logger.Information("Starting ZeroMQ client and connecting to {Url}...", url);
 | 
			
		||||
		socket.Connect(url);
 | 
			
		||||
		logger.Information("ZeroMQ client ready.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected override void Run(ClientSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager) {
 | 
			
		||||
		var connection = new RpcServerConnection(socket, replyTracker);
 | 
			
		||||
		ServerMessaging.SetCurrentConnection(connection);
 | 
			
		||||
		
 | 
			
		||||
		var logger = config.RuntimeLogger;
 | 
			
		||||
		var handler = new MessageToAgentHandler(messageListenerFactory(connection), logger, taskManager, receiveCancellationToken);
 | 
			
		||||
		var keepAliveLoop = new KeepAliveLoop(connection);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			while (!receiveCancellationToken.IsCancellationRequested) {
 | 
			
		||||
				var data = socket.Receive(receiveCancellationToken);
 | 
			
		||||
				
 | 
			
		||||
				LogMessageType(logger, data);
 | 
			
		||||
				
 | 
			
		||||
				if (data.Length > 0) {
 | 
			
		||||
					MessageRegistries.ToAgent.Handle(data, handler);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} catch (OperationCanceledException) {
 | 
			
		||||
			// Ignore.
 | 
			
		||||
		} finally {
 | 
			
		||||
			logger.Debug("ZeroMQ client stopped receiving messages.");
 | 
			
		||||
 | 
			
		||||
			disconnectSemaphore.Wait(CancellationToken.None);
 | 
			
		||||
			keepAliveLoop.Cancel();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void LogMessageType(ILogger logger, ReadOnlyMemory<byte> data) {
 | 
			
		||||
		if (!logger.IsEnabled(LogEventLevel.Verbose)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (data.Length > 0 && MessageRegistries.ToAgent.TryGetType(data, out var type)) {
 | 
			
		||||
			logger.Verbose("Received {MessageType} ({Bytes} B) from server.", type.Name, data.Length);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			logger.Verbose("Received {Bytes} B message from server.", data.Length);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected override async Task Disconnect() {
 | 
			
		||||
		var unregisterTimeoutTask = Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None);
 | 
			
		||||
		var finishedTask = await Task.WhenAny(ServerMessaging.Send(new UnregisterAgentMessage(agentGuid)), unregisterTimeoutTask);
 | 
			
		||||
		if (finishedTask == unregisterTimeoutTask) {
 | 
			
		||||
			config.RuntimeLogger.Error("Timed out communicating agent shutdown with the server.");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private sealed class MessageToAgentHandler : MessageHandler<IMessageToAgentListener> {
 | 
			
		||||
		public MessageToAgentHandler(IMessageToAgentListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) {}
 | 
			
		||||
		
 | 
			
		||||
		protected override Task SendReply(uint sequenceId, byte[] serializedReply) {
 | 
			
		||||
			return ServerMessaging.Send(new ReplyMessage(sequenceId, serializedReply));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
using NetMQ;
 | 
			
		||||
using NetMQ.Sockets;
 | 
			
		||||
using Phantom.Common.Messages;
 | 
			
		||||
using Phantom.Common.Messages.BiDirectional;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Rpc; 
 | 
			
		||||
 | 
			
		||||
public sealed class RpcServerConnection {
 | 
			
		||||
	private readonly ClientSocket socket;
 | 
			
		||||
	private readonly MessageReplyTracker replyTracker;
 | 
			
		||||
 | 
			
		||||
	internal RpcServerConnection(ClientSocket socket, MessageReplyTracker replyTracker) {
 | 
			
		||||
		this.socket = socket;
 | 
			
		||||
		this.replyTracker = replyTracker;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal async Task Send<TMessage>(TMessage message) where TMessage : IMessageToServer {
 | 
			
		||||
		var bytes = MessageRegistries.ToServer.Write(message).ToArray();
 | 
			
		||||
		if (bytes.Length > 0) {
 | 
			
		||||
			await socket.SendAsync(bytes);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal async Task<TReply?> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToServer<TReply> where TReply : class {
 | 
			
		||||
		var sequenceId = replyTracker.RegisterReply();
 | 
			
		||||
		
 | 
			
		||||
		var bytes = MessageRegistries.ToServer.Write<TMessage, TReply>(sequenceId, message).ToArray();
 | 
			
		||||
		if (bytes.Length == 0) {
 | 
			
		||||
			replyTracker.ForgetReply(sequenceId);
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await socket.SendAsync(bytes);
 | 
			
		||||
		return await replyTracker.WaitForReply<TReply>(sequenceId, waitForReplyTime, waitForReplyCancellationToken);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void Receive(ReplyMessage message) {
 | 
			
		||||
		replyTracker.ReceiveReply(message.SequenceId, message.SerializedReply);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Rpc;
 | 
			
		||||
 | 
			
		||||
public static class ServerMessaging {
 | 
			
		||||
	private static readonly ILogger Logger = PhantomLogger.Create(nameof(ServerMessaging));
 | 
			
		||||
	
 | 
			
		||||
	private static RpcServerConnection? CurrentConnection { get; set; }
 | 
			
		||||
	private static RpcServerConnection CurrentConnectionOrThrow => CurrentConnection ?? throw new InvalidOperationException("Server connection not ready.");
 | 
			
		||||
	
 | 
			
		||||
	private static readonly object SetCurrentConnectionLock = new ();
 | 
			
		||||
 | 
			
		||||
	internal static void SetCurrentConnection(RpcServerConnection connection) {
 | 
			
		||||
		lock (SetCurrentConnectionLock) {
 | 
			
		||||
			if (CurrentConnection != null) {
 | 
			
		||||
				throw new InvalidOperationException("Server connection can only be set once.");
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			CurrentConnection = connection;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Logger.Information("Server connection ready.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Task Send<TMessage>(TMessage message) where TMessage : IMessageToServer {
 | 
			
		||||
		return CurrentConnectionOrThrow.Send(message);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Task<TReply?> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToServer<TReply> where TReply : class {
 | 
			
		||||
		return CurrentConnectionOrThrow.Send<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
namespace Phantom.Agent.Services; 
 | 
			
		||||
namespace Phantom.Agent.Services;
 | 
			
		||||
 | 
			
		||||
public readonly record struct AgentServiceConfiguration(int MaxConcurrentCompressionTasks);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Java;
 | 
			
		||||
using Phantom.Agent.Rpc;
 | 
			
		||||
using Phantom.Agent.Services.Backups;
 | 
			
		||||
using Phantom.Agent.Services.Instances;
 | 
			
		||||
using Phantom.Common.Data.Agent;
 | 
			
		||||
@@ -18,12 +19,12 @@ public sealed class AgentServices {
 | 
			
		||||
	internal JavaRuntimeRepository JavaRuntimeRepository { get; }
 | 
			
		||||
	internal InstanceSessionManager InstanceSessionManager { get; }
 | 
			
		||||
 | 
			
		||||
	public AgentServices(AgentInfo agentInfo, AgentFolders agentFolders, AgentServiceConfiguration serviceConfiguration) {
 | 
			
		||||
	public AgentServices(AgentInfo agentInfo, AgentFolders agentFolders, AgentServiceConfiguration serviceConfiguration, ControllerConnection controllerConnection) {
 | 
			
		||||
		this.AgentFolders = agentFolders;
 | 
			
		||||
		this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, AgentServices>());
 | 
			
		||||
		this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks);
 | 
			
		||||
		this.JavaRuntimeRepository = new JavaRuntimeRepository();
 | 
			
		||||
		this.InstanceSessionManager = new InstanceSessionManager(agentInfo, agentFolders, JavaRuntimeRepository, TaskManager, BackupManager);
 | 
			
		||||
		this.InstanceSessionManager = new InstanceSessionManager(controllerConnection, agentInfo, agentFolders, JavaRuntimeRepository, TaskManager, BackupManager);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async Task Initialize() {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Utils.IO;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Backups; 
 | 
			
		||||
namespace Phantom.Agent.Services.Backups;
 | 
			
		||||
 | 
			
		||||
sealed class BackupArchiver {
 | 
			
		||||
	private readonly string destinationBasePath;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
using Phantom.Utils.Processes;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Backups; 
 | 
			
		||||
namespace Phantom.Agent.Services.Backups;
 | 
			
		||||
 | 
			
		||||
static class BackupCompressor {
 | 
			
		||||
	private static ILogger Logger { get; } = PhantomLogger.Create(nameof(BackupCompressor));
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ sealed class BackupScheduler : CancellableBackgroundTask {
 | 
			
		||||
	private readonly ServerStatusProtocol serverStatusProtocol;
 | 
			
		||||
	private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new ();
 | 
			
		||||
	
 | 
			
		||||
	public event EventHandler<BackupCreationResult>? BackupCompleted; 
 | 
			
		||||
	public event EventHandler<BackupCreationResult>? BackupCompleted;
 | 
			
		||||
 | 
			
		||||
	public BackupScheduler(TaskManager taskManager, BackupManager backupManager, InstanceProcess process, IInstanceContext context, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName), taskManager, "Backup scheduler for " + context.ShortName) {
 | 
			
		||||
		this.backupManager = backupManager;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ using Phantom.Agent.Services.Instances.States;
 | 
			
		||||
using Phantom.Common.Data.Instance;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Instances; 
 | 
			
		||||
namespace Phantom.Agent.Services.Instances;
 | 
			
		||||
 | 
			
		||||
interface IInstanceContext {
 | 
			
		||||
	string ShortName { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Launcher;
 | 
			
		||||
using Phantom.Agent.Rpc;
 | 
			
		||||
using Phantom.Agent.Services.Instances.Procedures;
 | 
			
		||||
using Phantom.Agent.Services.Instances.States;
 | 
			
		||||
using Phantom.Common.Data.Instance;
 | 
			
		||||
using Phantom.Common.Data.Minecraft;
 | 
			
		||||
using Phantom.Common.Data.Replies;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Instances;
 | 
			
		||||
@@ -27,7 +26,7 @@ sealed class Instance : IAsyncDisposable {
 | 
			
		||||
	private IInstanceState currentState;
 | 
			
		||||
	public bool IsRunning => currentState is not InstanceNotRunningState;
 | 
			
		||||
	
 | 
			
		||||
	public event EventHandler? IsRunningChanged; 
 | 
			
		||||
	public event EventHandler? IsRunningChanged;
 | 
			
		||||
	
 | 
			
		||||
	private readonly InstanceProcedureManager procedureManager;
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +56,7 @@ sealed class Instance : IAsyncDisposable {
 | 
			
		||||
 | 
			
		||||
	public void ReportLastStatus() {
 | 
			
		||||
		TryUpdateStatus("Report last status of instance " + shortName, async () => {
 | 
			
		||||
			await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, currentStatus));
 | 
			
		||||
			await Services.ControllerConnection.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, currentStatus));
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -65,14 +64,14 @@ sealed class Instance : IAsyncDisposable {
 | 
			
		||||
		TryUpdateStatus("Report status of instance " + shortName + " as " + status.GetType().Name, async () => {
 | 
			
		||||
			if (status != currentStatus) {
 | 
			
		||||
				currentStatus = status;
 | 
			
		||||
				await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status));
 | 
			
		||||
				await Services.ControllerConnection.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status));
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void ReportEvent(IInstanceEvent instanceEvent) {
 | 
			
		||||
		var message = new ReportInstanceEventMessage(Guid.NewGuid(), DateTime.UtcNow, Configuration.InstanceGuid, instanceEvent);
 | 
			
		||||
		Services.TaskManager.Run("Report event for instance " + shortName, async () => await ServerMessaging.Send(message));
 | 
			
		||||
		Services.TaskManager.Run("Report event for instance " + shortName, async () => await Services.ControllerConnection.Send(message));
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	internal void TransitionState(IInstanceState newState) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ using System.Collections.Immutable;
 | 
			
		||||
using System.Threading.Channels;
 | 
			
		||||
using Phantom.Agent.Rpc;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Instances;
 | 
			
		||||
@@ -16,12 +16,14 @@ sealed class InstanceLogSender : CancellableBackgroundTask {
 | 
			
		||||
	
 | 
			
		||||
	private static readonly TimeSpan SendDelay = TimeSpan.FromMilliseconds(200);
 | 
			
		||||
 | 
			
		||||
	private readonly ControllerConnection controllerConnection;
 | 
			
		||||
	private readonly Guid instanceGuid;
 | 
			
		||||
	private readonly Channel<string> outputChannel;
 | 
			
		||||
	
 | 
			
		||||
	private int droppedLinesSinceLastSend;
 | 
			
		||||
 | 
			
		||||
	public InstanceLogSender(TaskManager taskManager, Guid instanceGuid, string loggerName) : base(PhantomLogger.Create<InstanceLogSender>(loggerName), taskManager, "Instance log sender for " + loggerName) {
 | 
			
		||||
	public InstanceLogSender(ControllerConnection controllerConnection, TaskManager taskManager, Guid instanceGuid, string loggerName) : base(PhantomLogger.Create<InstanceLogSender>(loggerName), taskManager, "Instance log sender for " + loggerName) {
 | 
			
		||||
		this.controllerConnection = controllerConnection;
 | 
			
		||||
		this.instanceGuid = instanceGuid;
 | 
			
		||||
		this.outputChannel = Channel.CreateBounded<string>(BufferOptions, OnLineDropped);
 | 
			
		||||
		Start();
 | 
			
		||||
@@ -61,7 +63,7 @@ sealed class InstanceLogSender : CancellableBackgroundTask {
 | 
			
		||||
 | 
			
		||||
	private async Task SendOutputToServer(ImmutableArray<string> lines) {
 | 
			
		||||
		if (!lines.IsEmpty) {
 | 
			
		||||
			await ServerMessaging.Send(new InstanceOutputMessage(instanceGuid, lines));
 | 
			
		||||
			await controllerConnection.Send(new InstanceOutputMessage(instanceGuid, lines));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
using Phantom.Agent.Minecraft.Launcher;
 | 
			
		||||
using Phantom.Agent.Rpc;
 | 
			
		||||
using Phantom.Agent.Services.Backups;
 | 
			
		||||
using Phantom.Utils.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Instances; 
 | 
			
		||||
namespace Phantom.Agent.Services.Instances;
 | 
			
		||||
 | 
			
		||||
sealed record InstanceServices(TaskManager TaskManager, PortManager PortManager, BackupManager BackupManager, LaunchServices LaunchServices);
 | 
			
		||||
sealed record InstanceServices(ControllerConnection ControllerConnection, TaskManager TaskManager, PortManager PortManager, BackupManager BackupManager, LaunchServices LaunchServices);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ using Phantom.Common.Data.Instance;
 | 
			
		||||
using Phantom.Common.Data.Minecraft;
 | 
			
		||||
using Phantom.Common.Data.Replies;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.IO;
 | 
			
		||||
using Phantom.Utils.Tasks;
 | 
			
		||||
using Serilog;
 | 
			
		||||
@@ -24,6 +24,7 @@ namespace Phantom.Agent.Services.Instances;
 | 
			
		||||
sealed class InstanceSessionManager : IAsyncDisposable {
 | 
			
		||||
	private static readonly ILogger Logger = PhantomLogger.Create<InstanceSessionManager>();
 | 
			
		||||
 | 
			
		||||
	private readonly ControllerConnection controllerConnection;
 | 
			
		||||
	private readonly AgentInfo agentInfo;
 | 
			
		||||
	private readonly string basePath;
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +37,8 @@ sealed class InstanceSessionManager : IAsyncDisposable {
 | 
			
		||||
 | 
			
		||||
	private uint instanceLoggerSequenceId = 0;
 | 
			
		||||
 | 
			
		||||
	public InstanceSessionManager(AgentInfo agentInfo, AgentFolders agentFolders, JavaRuntimeRepository javaRuntimeRepository, TaskManager taskManager, BackupManager backupManager) {
 | 
			
		||||
	public InstanceSessionManager(ControllerConnection controllerConnection, AgentInfo agentInfo, AgentFolders agentFolders, JavaRuntimeRepository javaRuntimeRepository, TaskManager taskManager, BackupManager backupManager) {
 | 
			
		||||
		this.controllerConnection = controllerConnection;
 | 
			
		||||
		this.agentInfo = agentInfo;
 | 
			
		||||
		this.basePath = agentFolders.InstancesFolderPath;
 | 
			
		||||
		this.shutdownCancellationToken = shutdownCancellationTokenSource.Token;
 | 
			
		||||
@@ -45,7 +47,7 @@ sealed class InstanceSessionManager : IAsyncDisposable {
 | 
			
		||||
		var launchServices = new LaunchServices(minecraftServerExecutables, javaRuntimeRepository);
 | 
			
		||||
		var portManager = new PortManager(agentInfo.AllowedServerPorts, agentInfo.AllowedRconPorts);
 | 
			
		||||
 | 
			
		||||
		this.instanceServices = new InstanceServices(taskManager, portManager, backupManager, launchServices);
 | 
			
		||||
		this.instanceServices = new InstanceServices(controllerConnection, taskManager, portManager, backupManager, launchServices);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private async Task<InstanceActionResult<T>> AcquireSemaphoreAndRun<T>(Func<Task<InstanceActionResult<T>>> func) {
 | 
			
		||||
@@ -146,7 +148,7 @@ sealed class InstanceSessionManager : IAsyncDisposable {
 | 
			
		||||
				var runningInstances = GetRunningInstancesInternal();
 | 
			
		||||
				var runningInstanceCount = runningInstances.Length;
 | 
			
		||||
				var runningInstanceMemory = runningInstances.Aggregate(RamAllocationUnits.Zero, static (total, instance) => total + instance.Configuration.MemoryAllocation);
 | 
			
		||||
				await ServerMessaging.Send(new ReportAgentStatusMessage(runningInstanceCount, runningInstanceMemory));
 | 
			
		||||
				await controllerConnection.Send(new ReportAgentStatusMessage(runningInstanceCount, runningInstanceMemory));
 | 
			
		||||
			} finally {
 | 
			
		||||
				semaphore.Release();
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Phantom.Agent.Services.Instances.States;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent.Services.Instances.Procedures; 
 | 
			
		||||
namespace Phantom.Agent.Services.Instances.Procedures;
 | 
			
		||||
 | 
			
		||||
interface IInstanceProcedure {
 | 
			
		||||
	Task<IInstanceState?> Run(IInstanceContext context, CancellationToken cancellationToken);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Agent.Services.Instances.States; 
 | 
			
		||||
namespace Phantom.Agent.Services.Instances.States;
 | 
			
		||||
 | 
			
		||||
interface IInstanceState {
 | 
			
		||||
	void Initialize();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Agent.Services.Instances.States; 
 | 
			
		||||
namespace Phantom.Agent.Services.Instances.States;
 | 
			
		||||
 | 
			
		||||
sealed class InstanceNotRunningState : IInstanceState {
 | 
			
		||||
	public void Initialize() {}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ sealed class InstanceRunningState : IInstanceState, IDisposable {
 | 
			
		||||
		this.context = context;
 | 
			
		||||
		this.Process = process;
 | 
			
		||||
 | 
			
		||||
		this.logSender = new InstanceLogSender(context.Services.TaskManager, configuration.InstanceGuid, context.ShortName);
 | 
			
		||||
		this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.Services.TaskManager, configuration.InstanceGuid, context.ShortName);
 | 
			
		||||
 | 
			
		||||
		this.backupScheduler = new BackupScheduler(context.Services.TaskManager, context.Services.BackupManager, process, context, configuration.ServerPort);
 | 
			
		||||
		this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\..\Common\Phantom.Common.Messages\Phantom.Common.Messages.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\..\Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\Phantom.Agent.Minecraft\Phantom.Agent.Minecraft.csproj" />
 | 
			
		||||
    <ProjectReference Include="..\Phantom.Agent.Rpc\Phantom.Agent.Rpc.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
using Phantom.Agent.Rpc;
 | 
			
		||||
using Phantom.Common.Data.Instance;
 | 
			
		||||
using Phantom.Common.Data.Instance;
 | 
			
		||||
using Phantom.Common.Data.Replies;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages;
 | 
			
		||||
using Phantom.Common.Messages.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.ToAgent;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Common.Messages.Agent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToAgent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Rpc;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
@@ -14,11 +14,11 @@ namespace Phantom.Agent.Services.Rpc;
 | 
			
		||||
public sealed class MessageListener : IMessageToAgentListener {
 | 
			
		||||
	private static ILogger Logger { get; } = PhantomLogger.Create<MessageListener>();
 | 
			
		||||
 | 
			
		||||
	private readonly RpcServerConnection connection;
 | 
			
		||||
	private readonly RpcConnectionToServer<IMessageToControllerListener> connection;
 | 
			
		||||
	private readonly AgentServices agent;
 | 
			
		||||
	private readonly CancellationTokenSource shutdownTokenSource;
 | 
			
		||||
 | 
			
		||||
	public MessageListener(RpcServerConnection connection, AgentServices agent, CancellationTokenSource shutdownTokenSource) {
 | 
			
		||||
	public MessageListener(RpcConnectionToServer<IMessageToControllerListener> connection, AgentServices agent, CancellationTokenSource shutdownTokenSource) {
 | 
			
		||||
		this.connection = connection;
 | 
			
		||||
		this.agent = agent;
 | 
			
		||||
		this.shutdownTokenSource = shutdownTokenSource;
 | 
			
		||||
@@ -40,7 +40,7 @@ public sealed class MessageListener : IMessageToAgentListener {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await ServerMessaging.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All));
 | 
			
		||||
		await connection.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All));
 | 
			
		||||
		await agent.InstanceSessionManager.RefreshAgentStatus();
 | 
			
		||||
		
 | 
			
		||||
		return NoReply.Instance;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
using NetMQ;
 | 
			
		||||
using Phantom.Common.Data.Agent;
 | 
			
		||||
using Phantom.Common.Data;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Utils.Cryptography;
 | 
			
		||||
using Phantom.Utils.IO;
 | 
			
		||||
@@ -10,7 +10,7 @@ namespace Phantom.Agent;
 | 
			
		||||
static class AgentKey {
 | 
			
		||||
	private static ILogger Logger { get; } = PhantomLogger.Create(nameof(AgentKey));
 | 
			
		||||
 | 
			
		||||
	public static Task<(NetMQCertificate, AgentAuthToken)?> Load(string? agentKeyToken, string? agentKeyFilePath) {
 | 
			
		||||
	public static Task<(NetMQCertificate, AuthToken)?> Load(string? agentKeyToken, string? agentKeyFilePath) {
 | 
			
		||||
		if (agentKeyFilePath != null) {
 | 
			
		||||
			return LoadFromFile(agentKeyFilePath);
 | 
			
		||||
		}
 | 
			
		||||
@@ -22,7 +22,7 @@ static class AgentKey {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static async Task<(NetMQCertificate, AgentAuthToken)?> LoadFromFile(string agentKeyFilePath) {
 | 
			
		||||
	private static async Task<(NetMQCertificate, AuthToken)?> LoadFromFile(string agentKeyFilePath) {
 | 
			
		||||
		if (!File.Exists(agentKeyFilePath)) {
 | 
			
		||||
			Logger.Fatal("Missing agent key file: {AgentKeyFilePath}", agentKeyFilePath);
 | 
			
		||||
			return null;
 | 
			
		||||
@@ -41,7 +41,7 @@ static class AgentKey {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static (NetMQCertificate, AgentAuthToken)? LoadFromToken(string agentKey) {
 | 
			
		||||
	private static (NetMQCertificate, AuthToken)? LoadFromToken(string agentKey) {
 | 
			
		||||
		try {
 | 
			
		||||
			return LoadFromBytes(TokenGenerator.DecodeBytes(agentKey));
 | 
			
		||||
		} catch (Exception) {
 | 
			
		||||
@@ -50,11 +50,11 @@ static class AgentKey {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static (NetMQCertificate, AgentAuthToken)? LoadFromBytes(byte[] agentKey) {
 | 
			
		||||
		var (publicKey, agentToken) = AgentKeyData.FromBytes(agentKey);
 | 
			
		||||
		var serverCertificate = NetMQCertificate.FromPublicKey(publicKey);
 | 
			
		||||
	private static (NetMQCertificate, AuthToken)? LoadFromBytes(byte[] agentKey) {
 | 
			
		||||
		var (publicKey, agentToken) = ConnectionCommonKey.FromBytes(agentKey);
 | 
			
		||||
		var controllerCertificate = NetMQCertificate.FromPublicKey(publicKey);
 | 
			
		||||
		
 | 
			
		||||
		Logger.Information("Loaded agent key.");
 | 
			
		||||
		return (serverCertificate, agentToken);
 | 
			
		||||
		return (controllerCertificate, agentToken);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Utils.IO;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Agent; 
 | 
			
		||||
namespace Phantom.Agent;
 | 
			
		||||
 | 
			
		||||
static class GuidFile {
 | 
			
		||||
	private static ILogger Logger { get; } = PhantomLogger.Create(nameof(GuidFile));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,15 @@
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using NetMQ;
 | 
			
		||||
using Phantom.Agent;
 | 
			
		||||
using Phantom.Agent.Rpc;
 | 
			
		||||
using Phantom.Agent.Services;
 | 
			
		||||
using Phantom.Agent.Services.Rpc;
 | 
			
		||||
using Phantom.Common.Data.Agent;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.Agent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Rpc;
 | 
			
		||||
using Phantom.Utils.Rpc.Sockets;
 | 
			
		||||
using Phantom.Utils.Runtime;
 | 
			
		||||
using Phantom.Utils.Tasks;
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +30,7 @@ try {
 | 
			
		||||
	PhantomLogger.Root.InformationHeading("Initializing Phantom Panel agent...");
 | 
			
		||||
	PhantomLogger.Root.Information("Agent version: {Version}", fullVersion);
 | 
			
		||||
 | 
			
		||||
	var (serverHost, serverPort, javaSearchPath, agentKeyToken, agentKeyFilePath, agentName, maxInstances, maxMemory, allowedServerPorts, allowedRconPorts, maxConcurrentBackupCompressionTasks) = Variables.LoadOrStop();
 | 
			
		||||
	var (controllerHost, controllerPort, javaSearchPath, agentKeyToken, agentKeyFilePath, agentName, maxInstances, maxMemory, allowedServerPorts, allowedRconPorts, maxConcurrentBackupCompressionTasks) = Variables.LoadOrStop();
 | 
			
		||||
 | 
			
		||||
	var agentKey = await AgentKey.Load(agentKeyToken, agentKeyFilePath);
 | 
			
		||||
	if (agentKey == null) {
 | 
			
		||||
@@ -43,21 +47,20 @@ try {
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (serverCertificate, agentToken) = agentKey.Value;
 | 
			
		||||
	var (controllerCertificate, agentToken) = agentKey.Value;
 | 
			
		||||
	var agentInfo = new AgentInfo(agentGuid.Value, agentName, ProtocolVersion, fullVersion, maxInstances, maxMemory, allowedServerPorts, allowedRconPorts);
 | 
			
		||||
	var agentServices = new AgentServices(agentInfo, folders, new AgentServiceConfiguration(maxConcurrentBackupCompressionTasks));
 | 
			
		||||
 | 
			
		||||
	MessageListener MessageListenerFactory(RpcServerConnection connection) {
 | 
			
		||||
		return new MessageListener(connection, agentServices, shutdownCancellationTokenSource);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	PhantomLogger.Root.InformationHeading("Launching Phantom Panel agent...");
 | 
			
		||||
	
 | 
			
		||||
	var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), controllerHost, controllerPort, controllerCertificate);
 | 
			
		||||
	var rpcSocket = RpcClientSocket.Connect(rpcConfiguration, AgentMessageRegistries.Definitions, new RegisterAgentMessage(agentToken, agentInfo));
 | 
			
		||||
 | 
			
		||||
	var agentServices = new AgentServices(agentInfo, folders, new AgentServiceConfiguration(maxConcurrentBackupCompressionTasks), new ControllerConnection(rpcSocket.Connection));
 | 
			
		||||
	await agentServices.Initialize();
 | 
			
		||||
 | 
			
		||||
	var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1);
 | 
			
		||||
	var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), serverHost, serverPort, serverCertificate);
 | 
			
		||||
	var rpcTask = RpcLauncher.Launch(rpcConfiguration, agentToken, agentInfo, MessageListenerFactory, rpcDisconnectSemaphore, shutdownCancellationToken);
 | 
			
		||||
	var rpcMessageListener = new MessageListener(rpcSocket.Connection, agentServices, shutdownCancellationTokenSource);
 | 
			
		||||
	var rpcTask = RpcClientRuntime.Launch(rpcSocket, agentInfo, rpcMessageListener, rpcDisconnectSemaphore, shutdownCancellationToken);
 | 
			
		||||
	try {
 | 
			
		||||
		await rpcTask.WaitAsync(shutdownCancellationToken);
 | 
			
		||||
	} finally {
 | 
			
		||||
@@ -67,6 +70,8 @@ try {
 | 
			
		||||
		rpcDisconnectSemaphore.Release();
 | 
			
		||||
		await rpcTask;
 | 
			
		||||
		rpcDisconnectSemaphore.Dispose();
 | 
			
		||||
		
 | 
			
		||||
		NetMQConfig.Cleanup();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,8 @@ using Phantom.Utils.Runtime;
 | 
			
		||||
namespace Phantom.Agent;
 | 
			
		||||
 | 
			
		||||
sealed record Variables(
 | 
			
		||||
	string ServerHost,
 | 
			
		||||
	ushort ServerPort,
 | 
			
		||||
	string ControllerHost,
 | 
			
		||||
	ushort ControllerPort,
 | 
			
		||||
	string JavaSearchPath,
 | 
			
		||||
	string? AgentKeyToken,
 | 
			
		||||
	string? AgentKeyFilePath,
 | 
			
		||||
@@ -23,8 +23,8 @@ sealed record Variables(
 | 
			
		||||
		var javaSearchPath = EnvironmentVariables.GetString("JAVA_SEARCH_PATH").WithDefaultGetter(GetDefaultJavaSearchPath);
 | 
			
		||||
 | 
			
		||||
		return new Variables(
 | 
			
		||||
			EnvironmentVariables.GetString("SERVER_HOST").Require,
 | 
			
		||||
			EnvironmentVariables.GetPortNumber("SERVER_PORT").WithDefault(9401),
 | 
			
		||||
			EnvironmentVariables.GetString("CONTROLLER_HOST").Require,
 | 
			
		||||
			EnvironmentVariables.GetPortNumber("CONTROLLER_PORT").WithDefault(9401),
 | 
			
		||||
			javaSearchPath,
 | 
			
		||||
			agentKeyToken,
 | 
			
		||||
			agentKeyFilePath,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using NUnit.Framework;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Tests; 
 | 
			
		||||
namespace Phantom.Common.Data.Tests;
 | 
			
		||||
 | 
			
		||||
[TestFixture]
 | 
			
		||||
public sealed class AllowedPortsTests {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								Common/Phantom.Common.Data.Web/Agent/AgentWithStats.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Common/Phantom.Common.Data.Web/Agent/AgentWithStats.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Agent;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Agent;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record AgentWithStats(
 | 
			
		||||
	[property: MemoryPackOrder(0)] Guid Guid,
 | 
			
		||||
	[property: MemoryPackOrder(1)] string Name,
 | 
			
		||||
	[property: MemoryPackOrder(2)] ushort ProtocolVersion,
 | 
			
		||||
	[property: MemoryPackOrder(3)] string BuildVersion,
 | 
			
		||||
	[property: MemoryPackOrder(4)] ushort MaxInstances,
 | 
			
		||||
	[property: MemoryPackOrder(5)] RamAllocationUnits MaxMemory,
 | 
			
		||||
	[property: MemoryPackOrder(6)] AllowedPorts? AllowedServerPorts,
 | 
			
		||||
	[property: MemoryPackOrder(7)] AllowedPorts? AllowedRconPorts,
 | 
			
		||||
	[property: MemoryPackOrder(8)] AgentStats? Stats,
 | 
			
		||||
	[property: MemoryPackOrder(9)] DateTimeOffset? LastPing,
 | 
			
		||||
	[property: MemoryPackOrder(10)] bool IsOnline
 | 
			
		||||
) {
 | 
			
		||||
	[MemoryPackIgnore]
 | 
			
		||||
	public RamAllocationUnits? AvailableMemory => MaxMemory - Stats?.RunningInstanceMemory;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Controller.Database.Enums;
 | 
			
		||||
namespace Phantom.Common.Data.Web.AuditLog;
 | 
			
		||||
 | 
			
		||||
public enum AuditLogEventType {
 | 
			
		||||
	AdministratorUserCreated,
 | 
			
		||||
@@ -6,6 +6,7 @@ public enum AuditLogEventType {
 | 
			
		||||
	UserLoggedIn,
 | 
			
		||||
	UserLoggedOut,
 | 
			
		||||
	UserCreated,
 | 
			
		||||
	UserPasswordChanged,
 | 
			
		||||
	UserRolesChanged,
 | 
			
		||||
	UserDeleted,
 | 
			
		||||
	InstanceCreated,
 | 
			
		||||
@@ -15,13 +16,14 @@ public enum AuditLogEventType {
 | 
			
		||||
	InstanceCommandExecuted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static class AuditLogEventTypeExtensions {
 | 
			
		||||
public static class AuditLogEventTypeExtensions {
 | 
			
		||||
	private static readonly Dictionary<AuditLogEventType, AuditLogSubjectType> SubjectTypes = new () {
 | 
			
		||||
		{ AuditLogEventType.AdministratorUserCreated,  AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.AdministratorUserModified, AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.UserLoggedIn,              AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.UserLoggedOut,             AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.UserCreated,               AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.UserPasswordChanged,       AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.UserRolesChanged,          AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.UserDeleted,               AuditLogSubjectType.User },
 | 
			
		||||
		{ AuditLogEventType.InstanceCreated,           AuditLogSubjectType.Instance },
 | 
			
		||||
@@ -39,7 +41,7 @@ static class AuditLogEventTypeExtensions {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal static AuditLogSubjectType GetSubjectType(this AuditLogEventType type) {
 | 
			
		||||
	public static AuditLogSubjectType GetSubjectType(this AuditLogEventType type) {
 | 
			
		||||
		return SubjectTypes[type];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								Common/Phantom.Common.Data.Web/AuditLog/AuditLogItem.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Common/Phantom.Common.Data.Web/AuditLog/AuditLogItem.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.AuditLog;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record AuditLogItem(
 | 
			
		||||
	[property: MemoryPackOrder(0)] DateTime UtcTime,
 | 
			
		||||
	[property: MemoryPackOrder(1)] Guid? UserGuid,
 | 
			
		||||
	[property: MemoryPackOrder(2)] string? UserName,
 | 
			
		||||
	[property: MemoryPackOrder(3)] AuditLogEventType EventType,
 | 
			
		||||
	[property: MemoryPackOrder(4)] AuditLogSubjectType SubjectType,
 | 
			
		||||
	[property: MemoryPackOrder(5)] string? SubjectId,
 | 
			
		||||
	[property: MemoryPackOrder(6)] string? JsonData
 | 
			
		||||
);
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Controller.Database.Enums; 
 | 
			
		||||
namespace Phantom.Common.Data.Web.AuditLog;
 | 
			
		||||
 | 
			
		||||
public enum AuditLogSubjectType {
 | 
			
		||||
	User,
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Controller.Database.Enums;
 | 
			
		||||
namespace Phantom.Common.Data.Web.EventLog;
 | 
			
		||||
 | 
			
		||||
public enum EventLogEventType {
 | 
			
		||||
	InstanceLaunchSucceded,
 | 
			
		||||
@@ -10,7 +10,7 @@ public enum EventLogEventType {
 | 
			
		||||
	InstanceBackupFailed,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static class EventLogEventTypeExtensions {
 | 
			
		||||
public static class EventLogEventTypeExtensions {
 | 
			
		||||
	private static readonly Dictionary<EventLogEventType, EventLogSubjectType> SubjectTypes = new () {
 | 
			
		||||
		{ EventLogEventType.InstanceLaunchSucceded, EventLogSubjectType.Instance },
 | 
			
		||||
		{ EventLogEventType.InstanceLaunchFailed, EventLogSubjectType.Instance },
 | 
			
		||||
							
								
								
									
										13
									
								
								Common/Phantom.Common.Data.Web/EventLog/EventLogItem.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Common/Phantom.Common.Data.Web/EventLog/EventLogItem.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.EventLog;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record EventLogItem(
 | 
			
		||||
	[property: MemoryPackOrder(0)] DateTime UtcTime,
 | 
			
		||||
	[property: MemoryPackOrder(1)] Guid? AgentGuid,
 | 
			
		||||
	[property: MemoryPackOrder(2)] EventLogEventType EventType,
 | 
			
		||||
	[property: MemoryPackOrder(3)] EventLogSubjectType SubjectType,
 | 
			
		||||
	[property: MemoryPackOrder(4)] string SubjectId,
 | 
			
		||||
	[property: MemoryPackOrder(5)] string? JsonData
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
namespace Phantom.Common.Data.Web.EventLog;
 | 
			
		||||
 | 
			
		||||
public enum EventLogSubjectType {
 | 
			
		||||
	Instance
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
namespace Phantom.Common.Data.Web.Instance;
 | 
			
		||||
 | 
			
		||||
public enum CreateOrUpdateInstanceResult : byte {
 | 
			
		||||
	UnknownError,
 | 
			
		||||
	Success,
 | 
			
		||||
	InstanceNameMustNotBeEmpty,
 | 
			
		||||
	InstanceMemoryMustNotBeZero,
 | 
			
		||||
	MinecraftVersionDownloadInfoNotFound,
 | 
			
		||||
	AgentNotFound
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public static class CreateOrUpdateInstanceResultExtensions {
 | 
			
		||||
	public static string ToSentence(this CreateOrUpdateInstanceResult reason) {
 | 
			
		||||
		return reason switch {
 | 
			
		||||
			CreateOrUpdateInstanceResult.Success                              => "Success.",
 | 
			
		||||
			CreateOrUpdateInstanceResult.InstanceNameMustNotBeEmpty           => "Instance name must not be empty.",
 | 
			
		||||
			CreateOrUpdateInstanceResult.InstanceMemoryMustNotBeZero          => "Memory must not be 0 MB.",
 | 
			
		||||
			CreateOrUpdateInstanceResult.MinecraftVersionDownloadInfoNotFound => "Could not find download information for the selected Minecraft version.",
 | 
			
		||||
			CreateOrUpdateInstanceResult.AgentNotFound                        => "Agent not found.",
 | 
			
		||||
			_                                                                 => "Unknown error."
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								Common/Phantom.Common.Data.Web/Instance/Instance.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Common/Phantom.Common.Data.Web/Instance/Instance.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Instance;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Instance;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record Instance(
 | 
			
		||||
	[property: MemoryPackOrder(0)] InstanceConfiguration Configuration,
 | 
			
		||||
	[property: MemoryPackOrder(1)] IInstanceStatus Status,
 | 
			
		||||
	[property: MemoryPackOrder(2)] bool LaunchAutomatically
 | 
			
		||||
) {
 | 
			
		||||
	public static Instance Offline(InstanceConfiguration configuration, bool launchAutomatically = false) {
 | 
			
		||||
		return new Instance(configuration, InstanceStatus.Offline, launchAutomatically);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Controller.Minecraft;
 | 
			
		||||
namespace Phantom.Common.Data.Web.Minecraft;
 | 
			
		||||
 | 
			
		||||
public static class JvmArgumentsHelper {
 | 
			
		||||
	public static ImmutableArray<string> Split(string arguments) {
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  
 | 
			
		||||
  <PropertyGroup>
 | 
			
		||||
    <ImplicitUsings>enable</ImplicitUsings>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
  
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <PackageReference Include="BCrypt.Net-Next.StrongName" />
 | 
			
		||||
    <PackageReference Include="MemoryPack" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\Phantom.Common.Data\Phantom.Common.Data.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Controller.Services.Users; 
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
public enum AddRoleError : byte {
 | 
			
		||||
	NameIsEmpty,
 | 
			
		||||
							
								
								
									
										28
									
								
								Common/Phantom.Common.Data.Web/Users/AddUserError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Common/Phantom.Common.Data.Web/Users/AddUserError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Web.Users.AddUserErrors;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users {
 | 
			
		||||
	[MemoryPackable]
 | 
			
		||||
	[MemoryPackUnion(0, typeof(NameIsInvalid))]
 | 
			
		||||
	[MemoryPackUnion(1, typeof(PasswordIsInvalid))]
 | 
			
		||||
	[MemoryPackUnion(2, typeof(NameAlreadyExists))]
 | 
			
		||||
	[MemoryPackUnion(3, typeof(UnknownError))]
 | 
			
		||||
	public abstract partial record AddUserError {
 | 
			
		||||
		internal AddUserError() {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users.AddUserErrors {
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record NameIsInvalid([property: MemoryPackOrder(0)] UsernameRequirementViolation Violation) : AddUserError;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record NameAlreadyExists : AddUserError;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record UnknownError : AddUserError;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users; 
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record ChangeUserRolesResult(
 | 
			
		||||
	[property: MemoryPackOrder(0)] ImmutableHashSet<Guid> AddedToRoleGuids,
 | 
			
		||||
	[property: MemoryPackOrder(1)] ImmutableHashSet<Guid> RemovedFromRoleGuids
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users {
 | 
			
		||||
	[MemoryPackable]
 | 
			
		||||
	[MemoryPackUnion(0, typeof(Success))]
 | 
			
		||||
	[MemoryPackUnion(1, typeof(CreationFailed))]
 | 
			
		||||
	[MemoryPackUnion(2, typeof(UpdatingFailed))]
 | 
			
		||||
	[MemoryPackUnion(3, typeof(AddingToRoleFailed))]
 | 
			
		||||
	[MemoryPackUnion(4, typeof(UnknownError))]
 | 
			
		||||
	public abstract partial record CreateOrUpdateAdministratorUserResult {
 | 
			
		||||
		internal CreateOrUpdateAdministratorUserResult() {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults {
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record Success([property: MemoryPackOrder(0)] UserInfo User) : CreateOrUpdateAdministratorUserResult;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record CreationFailed([property: MemoryPackOrder(0)] AddUserError Error) : CreateOrUpdateAdministratorUserResult;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record UpdatingFailed([property: MemoryPackOrder(0)] SetUserPasswordError Error) : CreateOrUpdateAdministratorUserResult;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record AddingToRoleFailed : CreateOrUpdateAdministratorUserResult;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record UnknownError : CreateOrUpdateAdministratorUserResult;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								Common/Phantom.Common.Data.Web/Users/CreateUserResult.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Common/Phantom.Common.Data.Web/Users/CreateUserResult.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Web.Users.CreateUserResults;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users {
 | 
			
		||||
	[MemoryPackable]
 | 
			
		||||
	[MemoryPackUnion(0, typeof(Success))]
 | 
			
		||||
	[MemoryPackUnion(1, typeof(CreationFailed))]
 | 
			
		||||
	[MemoryPackUnion(2, typeof(UnknownError))]
 | 
			
		||||
	public abstract partial record CreateUserResult {
 | 
			
		||||
		internal CreateUserResult() {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users.CreateUserResults {
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record Success([property: MemoryPackOrder(0)] UserInfo User) : CreateUserResult;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record CreationFailed([property: MemoryPackOrder(0)] AddUserError Error) : CreateUserResult;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record UnknownError : CreateUserResult;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Controller.Services.Users; 
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
public enum DeleteUserResult : byte {
 | 
			
		||||
	Deleted,
 | 
			
		||||
							
								
								
									
										11
									
								
								Common/Phantom.Common.Data.Web/Users/LogInSuccess.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Common/Phantom.Common.Data.Web/Users/LogInSuccess.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record LogInSuccess (
 | 
			
		||||
	[property: MemoryPackOrder(0)] Guid UserGuid,
 | 
			
		||||
	[property: MemoryPackOrder(1)] PermissionSet Permissions,
 | 
			
		||||
	[property: MemoryPackOrder(2)] ImmutableArray<byte> Token
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Web.Users.PasswordRequirementViolations;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users {
 | 
			
		||||
	[MemoryPackable]
 | 
			
		||||
	[MemoryPackUnion(0, typeof(TooShort))]
 | 
			
		||||
	[MemoryPackUnion(1, typeof(MustContainLowercaseLetter))]
 | 
			
		||||
	[MemoryPackUnion(2, typeof(MustContainUppercaseLetter))]
 | 
			
		||||
	[MemoryPackUnion(3, typeof(MustContainDigit))]
 | 
			
		||||
	public abstract partial record PasswordRequirementViolation {
 | 
			
		||||
		internal PasswordRequirementViolation() {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users.PasswordRequirementViolations {
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record TooShort([property: MemoryPackOrder(0)] int MinimumLength) : PasswordRequirementViolation;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record MustContainLowercaseLetter : PasswordRequirementViolation;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record MustContainUppercaseLetter : PasswordRequirementViolation;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record MustContainDigit : PasswordRequirementViolation;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Web.Identity.Data;
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
public sealed record Permission(string Id, Permission? Parent) {
 | 
			
		||||
	private static readonly List<Permission> AllPermissions = new ();
 | 
			
		||||
							
								
								
									
										29
									
								
								Common/Phantom.Common.Data.Web/Users/PermissionSet.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Common/Phantom.Common.Data.Web/Users/PermissionSet.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial class PermissionSet {
 | 
			
		||||
	public static PermissionSet None { get; } = new (ImmutableHashSet<string>.Empty);
 | 
			
		||||
	
 | 
			
		||||
	[MemoryPackOrder(0)]
 | 
			
		||||
	[MemoryPackInclude]
 | 
			
		||||
	private readonly ImmutableHashSet<string> permissionIds;
 | 
			
		||||
 | 
			
		||||
	public PermissionSet(ImmutableHashSet<string> permissionIds) {
 | 
			
		||||
		this.permissionIds = permissionIds;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public bool Check(Permission? permission) {
 | 
			
		||||
		while (permission != null) {
 | 
			
		||||
			if (!permissionIds.Contains(permission.Id)) {
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			permission = permission.Parent;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								Common/Phantom.Common.Data.Web/Users/RoleInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Common/Phantom.Common.Data.Web/Users/RoleInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record RoleInfo(
 | 
			
		||||
	[property: MemoryPackOrder(0)] Guid Guid,
 | 
			
		||||
	[property: MemoryPackOrder(1)] string Name
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										24
									
								
								Common/Phantom.Common.Data.Web/Users/SetUserPasswordError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Common/Phantom.Common.Data.Web/Users/SetUserPasswordError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Web.Users.SetUserPasswordErrors;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users {
 | 
			
		||||
	[MemoryPackable]
 | 
			
		||||
	[MemoryPackUnion(0, typeof(UserNotFound))]
 | 
			
		||||
	[MemoryPackUnion(1, typeof(PasswordIsInvalid))]
 | 
			
		||||
	[MemoryPackUnion(2, typeof(UnknownError))]
 | 
			
		||||
	public abstract partial record SetUserPasswordError {
 | 
			
		||||
		internal SetUserPasswordError() {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users.SetUserPasswordErrors {
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record UserNotFound : SetUserPasswordError;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : SetUserPasswordError;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record UnknownError : SetUserPasswordError;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								Common/Phantom.Common.Data.Web/Users/UserInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Common/Phantom.Common.Data.Web/Users/UserInfo.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record UserInfo(
 | 
			
		||||
	[property: MemoryPackOrder(0)] Guid Guid,
 | 
			
		||||
	[property: MemoryPackOrder(1)] string Name
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										12
									
								
								Common/Phantom.Common.Data.Web/Users/UserPasswords.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Common/Phantom.Common.Data.Web/Users/UserPasswords.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users;
 | 
			
		||||
 | 
			
		||||
public static class UserPasswords {
 | 
			
		||||
	public static string Hash(string password) {
 | 
			
		||||
		return BCrypt.Net.BCrypt.HashPassword(password, workFactor: 12);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static bool Verify(string password, string hash) {
 | 
			
		||||
		// TODO rehash
 | 
			
		||||
		return BCrypt.Net.BCrypt.Verify(password, hash);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Web.Users.UsernameRequirementViolations;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users {
 | 
			
		||||
	[MemoryPackable]
 | 
			
		||||
	[MemoryPackUnion(0, typeof(IsEmpty))]
 | 
			
		||||
	[MemoryPackUnion(1, typeof(TooLong))]
 | 
			
		||||
	public abstract partial record UsernameRequirementViolation {
 | 
			
		||||
		internal UsernameRequirementViolation() {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Web.Users.UsernameRequirementViolations {
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record IsEmpty : UsernameRequirementViolation;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
	public sealed partial record TooLong([property: MemoryPackOrder(0)] int MaxLength) : UsernameRequirementViolation;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
namespace Phantom.Common.Data.Agent;
 | 
			
		||||
 | 
			
		||||
public static class AgentKeyData {
 | 
			
		||||
	private const byte TokenLength = AgentAuthToken.Length;
 | 
			
		||||
 | 
			
		||||
	public static byte[] ToBytes(byte[] publicKey, AgentAuthToken agentToken) {
 | 
			
		||||
		Span<byte> agentKey = stackalloc byte[TokenLength + publicKey.Length];
 | 
			
		||||
		agentToken.WriteTo(agentKey[..TokenLength]);
 | 
			
		||||
		publicKey.CopyTo(agentKey[TokenLength..]);
 | 
			
		||||
		return agentKey.ToArray();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static (byte[] PublicKey, AgentAuthToken AgentToken) FromBytes(byte[] agentKey) {
 | 
			
		||||
		var token = new AgentAuthToken(agentKey[..TokenLength]);
 | 
			
		||||
		var publicKey = agentKey[TokenLength..];
 | 
			
		||||
		return (publicKey, token);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								Common/Phantom.Common.Data/Agent/AgentStats.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Common/Phantom.Common.Data/Agent/AgentStats.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Agent;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record AgentStats(
 | 
			
		||||
	[property: MemoryPackOrder(0)] int RunningInstanceCount,
 | 
			
		||||
	[property: MemoryPackOrder(1)] RamAllocationUnits RunningInstanceMemory
 | 
			
		||||
);
 | 
			
		||||
@@ -2,18 +2,18 @@
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Agent;
 | 
			
		||||
namespace Phantom.Common.Data;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
 | 
			
		||||
public sealed partial class AgentAuthToken {
 | 
			
		||||
public sealed partial class AuthToken {
 | 
			
		||||
	internal const int Length = 12;
 | 
			
		||||
 | 
			
		||||
	[MemoryPackOrder(0)]
 | 
			
		||||
	[MemoryPackInclude]
 | 
			
		||||
	private readonly byte[] bytes;
 | 
			
		||||
 | 
			
		||||
	internal AgentAuthToken(byte[]? bytes) {
 | 
			
		||||
	internal AuthToken(byte[]? bytes) {
 | 
			
		||||
		if (bytes == null) {
 | 
			
		||||
			throw new ArgumentNullException(nameof(bytes));
 | 
			
		||||
		}
 | 
			
		||||
@@ -25,7 +25,7 @@ public sealed partial class AgentAuthToken {
 | 
			
		||||
		this.bytes = bytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public bool FixedTimeEquals(AgentAuthToken providedAuthToken) {
 | 
			
		||||
	public bool FixedTimeEquals(AuthToken providedAuthToken) {
 | 
			
		||||
		return CryptographicOperations.FixedTimeEquals(bytes, providedAuthToken.bytes);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -33,7 +33,7 @@ public sealed partial class AgentAuthToken {
 | 
			
		||||
		bytes.CopyTo(span);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static AgentAuthToken Generate() {
 | 
			
		||||
		return new AgentAuthToken(RandomNumberGenerator.GetBytes(Length));
 | 
			
		||||
	public static AuthToken Generate() {
 | 
			
		||||
		return new AuthToken(RandomNumberGenerator.GetBytes(Length));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								Common/Phantom.Common.Data/ConnectionCommonKey.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Common/Phantom.Common.Data/ConnectionCommonKey.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
namespace Phantom.Common.Data;
 | 
			
		||||
 | 
			
		||||
public readonly record struct ConnectionCommonKey(byte[] CertificatePublicKey, AuthToken AuthToken) {
 | 
			
		||||
	private const byte TokenLength = AuthToken.Length;
 | 
			
		||||
 | 
			
		||||
	public byte[] ToBytes() {
 | 
			
		||||
		Span<byte> result = stackalloc byte[TokenLength + CertificatePublicKey.Length];
 | 
			
		||||
		AuthToken.WriteTo(result[..TokenLength]);
 | 
			
		||||
		CertificatePublicKey.CopyTo(result[TokenLength..]);
 | 
			
		||||
		return result.ToArray();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ConnectionCommonKey FromBytes(byte[] agentKey) {
 | 
			
		||||
		var authToken = new AuthToken(agentKey[..TokenLength]);
 | 
			
		||||
		var certificatePublicKey = agentKey[TokenLength..];
 | 
			
		||||
		return new ConnectionCommonKey(certificatePublicKey, authToken);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Common.Data.Instance; 
 | 
			
		||||
namespace Phantom.Common.Data.Instance;
 | 
			
		||||
 | 
			
		||||
public interface IInstanceEventVisitor {
 | 
			
		||||
	void OnLaunchSucceeded(InstanceLaunchSuccededEvent e);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Common.Data.Minecraft;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Instance; 
 | 
			
		||||
namespace Phantom.Common.Data.Instance;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record InstanceLaunchProperties(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Java; 
 | 
			
		||||
namespace Phantom.Common.Data.Java;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record TaggedJavaRuntime(
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
using Phantom.Utils.Cryptography;
 | 
			
		||||
using Phantom.Utils.IO;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Data.Minecraft; 
 | 
			
		||||
namespace Phantom.Common.Data.Minecraft;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial class FileDownloadInfo {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Common.Data.Minecraft; 
 | 
			
		||||
namespace Phantom.Common.Data.Minecraft;
 | 
			
		||||
 | 
			
		||||
public enum MinecraftServerKind : ushort {
 | 
			
		||||
	Vanilla = 1,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
namespace Phantom.Common.Data.Minecraft; 
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
 | 
			
		||||
public sealed record MinecraftVersion(
 | 
			
		||||
	string Id,
 | 
			
		||||
	MinecraftVersionType Type,
 | 
			
		||||
	string MetadataUrl
 | 
			
		||||
namespace Phantom.Common.Data.Minecraft;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record MinecraftVersion(
 | 
			
		||||
	[property: MemoryPackOrder(0)] string Id,
 | 
			
		||||
	[property: MemoryPackOrder(1)] MinecraftVersionType Type,
 | 
			
		||||
	[property: MemoryPackOrder(2)] string MetadataUrl
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Common.Data.Replies; 
 | 
			
		||||
namespace Phantom.Common.Data.Replies;
 | 
			
		||||
 | 
			
		||||
public enum ConfigureInstanceResult : byte {
 | 
			
		||||
	Success
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
namespace Phantom.Common.Data.Replies; 
 | 
			
		||||
namespace Phantom.Common.Data.Replies;
 | 
			
		||||
 | 
			
		||||
public enum RegisterAgentFailure : byte {
 | 
			
		||||
	ConnectionAlreadyHasAnAgent,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using Serilog.Events;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Logging; 
 | 
			
		||||
namespace Phantom.Common.Logging;
 | 
			
		||||
 | 
			
		||||
static class DefaultLogLevel {
 | 
			
		||||
	private const string ENVIRONMENT_VARIABLE = "LOG_LEVEL";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Logging; 
 | 
			
		||||
namespace Phantom.Common.Logging;
 | 
			
		||||
 | 
			
		||||
public static class LoggerExtensions {
 | 
			
		||||
	private static readonly string HeadingPadding = new (' ', 23);
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ public static class PhantomLogger {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ILogger Create(string name1, string name2) {
 | 
			
		||||
		return Create(name1 + ":" + name2);
 | 
			
		||||
		return Create(ConcatNames(name1, name2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ILogger Create<T>() {
 | 
			
		||||
@@ -37,11 +37,19 @@ public static class PhantomLogger {
 | 
			
		||||
	public static ILogger Create<T>(string name) {
 | 
			
		||||
		return Create(typeof(T).Name, name);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static ILogger Create<T>(string name1, string name2) {
 | 
			
		||||
		return Create(typeof(T).Name, ConcatNames(name1, name2));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ILogger Create<T1, T2>() {
 | 
			
		||||
		return Create(typeof(T1).Name, typeof(T2).Name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static string ConcatNames(string name1, string name2) {
 | 
			
		||||
		return name1 + ":" + name2;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static void Dispose() {
 | 
			
		||||
		Root.Dispose();
 | 
			
		||||
		Base.Dispose();
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
using Phantom.Common.Data.Replies;
 | 
			
		||||
using Phantom.Common.Logging;
 | 
			
		||||
using Phantom.Common.Messages.Agent.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToAgent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Messages.Agent;
 | 
			
		||||
 | 
			
		||||
public static class AgentMessageRegistries {
 | 
			
		||||
	public static MessageRegistry<IMessageToAgentListener> ToAgent { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToAgent)));
 | 
			
		||||
	public static MessageRegistry<IMessageToControllerListener> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
 | 
			
		||||
	
 | 
			
		||||
	public static IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> Definitions { get; } = new MessageDefinitions();
 | 
			
		||||
 | 
			
		||||
	static AgentMessageRegistries() {
 | 
			
		||||
		ToAgent.Add<RegisterAgentSuccessMessage>(0);
 | 
			
		||||
		ToAgent.Add<RegisterAgentFailureMessage>(1);
 | 
			
		||||
		ToAgent.Add<ConfigureInstanceMessage, InstanceActionResult<ConfigureInstanceResult>>(2);
 | 
			
		||||
		ToAgent.Add<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(3);
 | 
			
		||||
		ToAgent.Add<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(4);
 | 
			
		||||
		ToAgent.Add<SendCommandToInstanceMessage, InstanceActionResult<SendCommandToInstanceResult>>(5);
 | 
			
		||||
		ToAgent.Add<ReplyMessage>(127);
 | 
			
		||||
		
 | 
			
		||||
		ToController.Add<RegisterAgentMessage>(0);
 | 
			
		||||
		ToController.Add<UnregisterAgentMessage>(1);
 | 
			
		||||
		ToController.Add<AgentIsAliveMessage>(2);
 | 
			
		||||
		ToController.Add<AdvertiseJavaRuntimesMessage>(3);
 | 
			
		||||
		ToController.Add<ReportInstanceStatusMessage>(4);
 | 
			
		||||
		ToController.Add<InstanceOutputMessage>(5);
 | 
			
		||||
		ToController.Add<ReportAgentStatusMessage>(6);
 | 
			
		||||
		ToController.Add<ReportInstanceEventMessage>(7);
 | 
			
		||||
		ToController.Add<ReplyMessage>(127);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener, ReplyMessage> {
 | 
			
		||||
		public MessageRegistry<IMessageToAgentListener> ToClient => ToAgent;
 | 
			
		||||
		public MessageRegistry<IMessageToControllerListener> ToServer => ToController;
 | 
			
		||||
 | 
			
		||||
		public bool IsRegistrationMessage(Type messageType) {
 | 
			
		||||
			return messageType == typeof(RegisterAgentMessage);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public ReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply) {
 | 
			
		||||
			return new ReplyMessage(sequenceId, serializedReply);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
using MemoryPack;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Messages.BiDirectional;
 | 
			
		||||
namespace Phantom.Common.Messages.Agent.BiDirectional;
 | 
			
		||||
 | 
			
		||||
[MemoryPackable(GenerateType.VersionTolerant)]
 | 
			
		||||
public sealed partial record ReplyMessage(
 | 
			
		||||
	[property: MemoryPackOrder(0)] uint SequenceId,
 | 
			
		||||
	[property: MemoryPackOrder(1)] byte[] SerializedReply
 | 
			
		||||
) : IMessageToServer, IMessageToAgent {
 | 
			
		||||
	public Task<NoReply> Accept(IMessageToServerListener listener) {
 | 
			
		||||
) : IMessageToController, IMessageToAgent, IReply {
 | 
			
		||||
	public Task<NoReply> Accept(IMessageToControllerListener listener) {
 | 
			
		||||
		return listener.HandleReply(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Messages;
 | 
			
		||||
namespace Phantom.Common.Messages.Agent;
 | 
			
		||||
 | 
			
		||||
public interface IMessageToAgent<TReply> : IMessage<IMessageToAgentListener, TReply> {}
 | 
			
		||||
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
using Phantom.Common.Data.Replies;
 | 
			
		||||
using Phantom.Common.Messages.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.ToAgent;
 | 
			
		||||
using Phantom.Common.Messages.Agent.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToAgent;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Messages;
 | 
			
		||||
namespace Phantom.Common.Messages.Agent;
 | 
			
		||||
 | 
			
		||||
public interface IMessageToAgentListener {
 | 
			
		||||
	Task<NoReply> HandleRegisterAgentSuccess(RegisterAgentSuccessMessage message);
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Messages.Agent;
 | 
			
		||||
 | 
			
		||||
public interface IMessageToController<TReply> : IMessage<IMessageToControllerListener, TReply> {}
 | 
			
		||||
 | 
			
		||||
public interface IMessageToController : IMessageToController<NoReply> {}
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
using Phantom.Common.Messages.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.ToServer;
 | 
			
		||||
using Phantom.Common.Messages.Agent.BiDirectional;
 | 
			
		||||
using Phantom.Common.Messages.Agent.ToController;
 | 
			
		||||
using Phantom.Utils.Rpc.Message;
 | 
			
		||||
 | 
			
		||||
namespace Phantom.Common.Messages; 
 | 
			
		||||
namespace Phantom.Common.Messages.Agent;
 | 
			
		||||
 | 
			
		||||
public interface IMessageToServerListener {
 | 
			
		||||
	bool IsDisposed { get; }
 | 
			
		||||
public interface IMessageToControllerListener {
 | 
			
		||||
	Task<NoReply> HandleRegisterAgent(RegisterAgentMessage message);
 | 
			
		||||
	Task<NoReply> HandleUnregisterAgent(UnregisterAgentMessage message);
 | 
			
		||||
	Task<NoReply> HandleAgentIsAlive(AgentIsAliveMessage message);
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user