mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-09-15 15:32:10 +02:00
Compare commits
1 Commits
wip-rpc
...
599177409e
Author | SHA1 | Date | |
---|---|---|---|
599177409e
|
@@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "8.0.3",
|
"version": "7.0.0-rc.1.22426.7",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"dotnet-ef"
|
||||||
]
|
]
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -192,6 +192,7 @@ ClientBin/
|
|||||||
*.dbmdl
|
*.dbmdl
|
||||||
*.dbproj.schemaview
|
*.dbproj.schemaview
|
||||||
*.jfm
|
*.jfm
|
||||||
|
*.pfx
|
||||||
*.publishsettings
|
*.publishsettings
|
||||||
orleans.codegen.cs
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Agent1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Agent1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="AGENT_KEY" value="K5ZRZYYJ9GWM2FS6XH5N5QQ7WZRPNDGHYMN5QP7RP6PPY27KRPMSYGCN" />
|
<env name="AGENT_KEY" value="JXBQQYG5T267RQS75MXWBTCJZY5CKTCCGQY22MCZPHSQQSJYCHH2NG2TCNXQY6TBSXM9NQDRS2CMX" />
|
||||||
<env name="AGENT_NAME" value="Agent 1" />
|
<env name="AGENT_NAME" value="Agent 1" />
|
||||||
<env name="ALLOWED_RCON_PORTS" value="25575,27000,27001" />
|
<env name="ALLOWED_RCON_PORTS" value="25575,27000,27001" />
|
||||||
<env name="ALLOWED_SERVER_PORTS" value="25565,26000,26001" />
|
<env name="ALLOWED_SERVER_PORTS" value="25565,26000,26001" />
|
||||||
@@ -15,19 +15,14 @@
|
|||||||
<env name="MAX_MEMORY" value="12G" />
|
<env name="MAX_MEMORY" value="12G" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="ENV_FILE_PATHS" value="" />
|
|
||||||
<option name="REDIRECT_INPUT_PATH" value="" />
|
|
||||||
<option name="PTY_MODE" value="Auto" />
|
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
<option name="AUTO_ATTACH_CHILDREN" value="0" />
|
|
||||||
<option name="MIXED_MODE_DEBUG" value="0" />
|
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Agent/Phantom.Agent/Phantom.Agent.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Agent/Phantom.Agent/Phantom.Agent.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net9.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Agent2" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Agent2" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="AGENT_KEY" value="K5ZRZYYJ9GWM2FS6XH5N5QQ7WZRPNDGHYMN5QP7RP6PPY27KRPMSYGCN" />
|
<env name="AGENT_KEY" value="JXBQQYG5T267RQS75MXWBTCJZY5CKTCCGQY22MCZPHSQQSJYCHH2NG2TCNXQY6TBSXM9NQDRS2CMX" />
|
||||||
<env name="AGENT_NAME" value="Agent 2" />
|
<env name="AGENT_NAME" value="Agent 2" />
|
||||||
<env name="ALLOWED_RCON_PORTS" value="27002-27006" />
|
<env name="ALLOWED_RCON_PORTS" value="27002-27006" />
|
||||||
<env name="ALLOWED_SERVER_PORTS" value="26002-26006" />
|
<env name="ALLOWED_SERVER_PORTS" value="26002-26006" />
|
||||||
@@ -15,19 +15,14 @@
|
|||||||
<env name="MAX_MEMORY" value="10G" />
|
<env name="MAX_MEMORY" value="10G" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="ENV_FILE_PATHS" value="" />
|
|
||||||
<option name="REDIRECT_INPUT_PATH" value="" />
|
|
||||||
<option name="PTY_MODE" value="Auto" />
|
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
<option name="AUTO_ATTACH_CHILDREN" value="0" />
|
|
||||||
<option name="MIXED_MODE_DEBUG" value="0" />
|
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Agent/Phantom.Agent/Phantom.Agent.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Agent/Phantom.Agent/Phantom.Agent.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net9.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Agent3" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Agent3" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="AGENT_KEY" value="K5ZRZYYJ9GWM2FS6XH5N5QQ7WZRPNDGHYMN5QP7RP6PPY27KRPMSYGCN" />
|
<env name="AGENT_KEY" value="JXBQQYG5T267RQS75MXWBTCJZY5CKTCCGQY22MCZPHSQQSJYCHH2NG2TCNXQY6TBSXM9NQDRS2CMX" />
|
||||||
<env name="AGENT_NAME" value="Agent 3" />
|
<env name="AGENT_NAME" value="Agent 3" />
|
||||||
<env name="ALLOWED_RCON_PORTS" value="27007" />
|
<env name="ALLOWED_RCON_PORTS" value="27007" />
|
||||||
<env name="ALLOWED_SERVER_PORTS" value="26007" />
|
<env name="ALLOWED_SERVER_PORTS" value="26007" />
|
||||||
@@ -15,19 +15,14 @@
|
|||||||
<env name="MAX_MEMORY" value="2560M" />
|
<env name="MAX_MEMORY" value="2560M" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="ENV_FILE_PATHS" value="" />
|
|
||||||
<option name="REDIRECT_INPUT_PATH" value="" />
|
|
||||||
<option name="PTY_MODE" value="Auto" />
|
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
<option name="AUTO_ATTACH_CHILDREN" value="0" />
|
|
||||||
<option name="MIXED_MODE_DEBUG" value="0" />
|
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Agent/Phantom.Agent/Phantom.Agent.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Agent/Phantom.Agent/Phantom.Agent.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net9.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@@ -14,19 +14,14 @@
|
|||||||
<env name="WEB_RPC_SERVER_HOST" value="localhost" />
|
<env name="WEB_RPC_SERVER_HOST" value="localhost" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="ENV_FILE_PATHS" value="" />
|
|
||||||
<option name="REDIRECT_INPUT_PATH" value="" />
|
|
||||||
<option name="PTY_MODE" value="Auto" />
|
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
<option name="AUTO_ATTACH_CHILDREN" value="0" />
|
|
||||||
<option name="MIXED_MODE_DEBUG" value="0" />
|
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Controller/Phantom.Controller/Phantom.Controller.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Controller/Phantom.Controller/Phantom.Controller.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net9.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@@ -7,23 +7,18 @@
|
|||||||
<envs>
|
<envs>
|
||||||
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
||||||
<env name="CONTROLLER_HOST" value="localhost" />
|
<env name="CONTROLLER_HOST" value="localhost" />
|
||||||
<env name="WEB_KEY" value="T5Y722D2GZBXT2H27QS95P2YQRFB2GCTKHSWT5CZFDTFKW52TCM9GDRW" />
|
<env name="WEB_KEY" value="BMNHM9RRPMCBBY29D9XHS6KBKZSRY7F5XFN27YZX96XXWJC2NM2D6YRHM9PZN9JGQGCSJ6FMB2GGZ" />
|
||||||
<env name="WEB_SERVER_HOST" value="localhost" />
|
<env name="WEB_SERVER_HOST" value="localhost" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="ENV_FILE_PATHS" value="" />
|
|
||||||
<option name="REDIRECT_INPUT_PATH" value="" />
|
|
||||||
<option name="PTY_MODE" value="Auto" />
|
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
<option name="AUTO_ATTACH_CHILDREN" value="0" />
|
|
||||||
<option name="MIXED_MODE_DEBUG" value="0" />
|
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Web/Phantom.Web/Phantom.Web.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Web/Phantom.Web/Phantom.Web.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net9.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
满<EFBFBD>H<EFBFBD>c<EFBFBD>og<EFBFBD>
|
|
BIN
.workdir/Controller/secrets/agent.key
Normal file
BIN
.workdir/Controller/secrets/agent.key
Normal file
Binary file not shown.
Binary file not shown.
1
.workdir/Controller/secrets/agent.secret
Normal file
1
.workdir/Controller/secrets/agent.secret
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<EFBFBD>Z<EFBFBD>t<>MPI<49>GMZ<4D><5A><EFBFBD><EFBFBD>kN<6B>VF1X<><58>p
|
@@ -1 +0,0 @@
|
|||||||
<07>U<EFBFBD>/<2F><04><><EFBFBD>q
|
|
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>
|
Binary file not shown.
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,5 @@
|
|||||||
using System.Text;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text;
|
||||||
using Phantom.Agent.Minecraft.Instance;
|
using Phantom.Agent.Minecraft.Instance;
|
||||||
using Phantom.Agent.Minecraft.Java;
|
using Phantom.Agent.Minecraft.Java;
|
||||||
using Phantom.Agent.Minecraft.Server;
|
using Phantom.Agent.Minecraft.Server;
|
||||||
@@ -11,6 +12,7 @@ public abstract class BaseLauncher : IServerLauncher {
|
|||||||
private readonly InstanceProperties instanceProperties;
|
private readonly InstanceProperties instanceProperties;
|
||||||
|
|
||||||
protected string MinecraftVersion => instanceProperties.ServerVersion;
|
protected string MinecraftVersion => instanceProperties.ServerVersion;
|
||||||
|
protected string InstanceFolder => instanceProperties.InstanceFolder;
|
||||||
|
|
||||||
private protected BaseLauncher(InstanceProperties instanceProperties) {
|
private protected BaseLauncher(InstanceProperties instanceProperties) {
|
||||||
this.instanceProperties = instanceProperties;
|
this.instanceProperties = instanceProperties;
|
||||||
@@ -51,16 +53,14 @@ public abstract class BaseLauncher : IServerLauncher {
|
|||||||
|
|
||||||
var processConfigurator = new ProcessConfigurator {
|
var processConfigurator = new ProcessConfigurator {
|
||||||
FileName = javaRuntimeExecutable.ExecutablePath,
|
FileName = javaRuntimeExecutable.ExecutablePath,
|
||||||
WorkingDirectory = instanceProperties.InstanceFolder,
|
WorkingDirectory = InstanceFolder,
|
||||||
RedirectInput = true,
|
RedirectInput = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var processArguments = processConfigurator.ArgumentList;
|
var processArguments = processConfigurator.ArgumentList;
|
||||||
PrepareJvmArguments(serverJar).Build(processArguments);
|
PrepareJvmArguments(serverJar).Build(processArguments);
|
||||||
processArguments.Add("-jar");
|
PrepareJavaProcessArguments(processArguments, serverJar.FilePath);
|
||||||
processArguments.Add(serverJar.FilePath);
|
|
||||||
processArguments.Add("nogui");
|
|
||||||
|
|
||||||
var process = processConfigurator.CreateProcess();
|
var process = processConfigurator.CreateProcess();
|
||||||
var instanceProcess = new InstanceProcess(instanceProperties, process);
|
var instanceProcess = new InstanceProcess(instanceProperties, process);
|
||||||
@@ -99,13 +99,19 @@ public abstract class BaseLauncher : IServerLauncher {
|
|||||||
|
|
||||||
private protected virtual void CustomizeJvmArguments(JvmArgumentBuilder arguments) {}
|
private protected virtual void CustomizeJvmArguments(JvmArgumentBuilder arguments) {}
|
||||||
|
|
||||||
|
protected virtual void PrepareJavaProcessArguments(Collection<string> processArguments, string serverJarFilePath) {
|
||||||
|
processArguments.Add("-jar");
|
||||||
|
processArguments.Add(serverJarFilePath);
|
||||||
|
processArguments.Add("nogui");
|
||||||
|
}
|
||||||
|
|
||||||
private protected virtual Task<ServerJarInfo> PrepareServerJar(ILogger logger, string serverJarPath, CancellationToken cancellationToken) {
|
private protected virtual Task<ServerJarInfo> PrepareServerJar(ILogger logger, string serverJarPath, CancellationToken cancellationToken) {
|
||||||
return Task.FromResult(new ServerJarInfo(serverJarPath));
|
return Task.FromResult(new ServerJarInfo(serverJarPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task AcceptEula(InstanceProperties instanceProperties) {
|
private static async Task AcceptEula(InstanceProperties instanceProperties) {
|
||||||
var eulaFilePath = Path.Combine(instanceProperties.InstanceFolder, "eula.txt");
|
var eulaFilePath = Path.Combine(instanceProperties.InstanceFolder, "eula.txt");
|
||||||
await File.WriteAllLinesAsync(eulaFilePath, new[] { "# EULA", "eula=true" }, Encoding.UTF8);
|
await File.WriteAllLinesAsync(eulaFilePath, new [] { "# EULA", "eula=true" }, Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task UpdateServerProperties(InstanceProperties instanceProperties) {
|
private static async Task UpdateServerProperties(InstanceProperties instanceProperties) {
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Phantom.Agent.Minecraft.Instance;
|
||||||
|
using Phantom.Agent.Minecraft.Java;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Phantom.Agent.Minecraft.Launcher.Types;
|
||||||
|
|
||||||
|
public sealed class ForgeLauncher : BaseLauncher {
|
||||||
|
public ForgeLauncher(InstanceProperties instanceProperties) : base(instanceProperties) {}
|
||||||
|
|
||||||
|
private protected override void CustomizeJvmArguments(JvmArgumentBuilder arguments) {
|
||||||
|
arguments.AddProperty("terminal.ansi", "true"); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareJavaProcessArguments(Collection<string> processArguments, string serverJarFilePath) {
|
||||||
|
if (OperatingSystem.IsWindows()) {
|
||||||
|
processArguments.Add("@libraries/net/minecraftforge/forge/1.20.1-47.2.0/win_args.txt");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
processArguments.Add("@libraries/net/minecraftforge/forge/1.20.1-47.2.0/unix_args.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
processArguments.Add("nogui");
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected override Task<ServerJarInfo> PrepareServerJar(ILogger logger, string serverJarPath, CancellationToken cancellationToken) {
|
||||||
|
return Task.FromResult(new ServerJarInfo(Path.Combine(InstanceFolder, "run.sh")));
|
||||||
|
}
|
||||||
|
}
|
@@ -18,5 +18,4 @@ static class MinecraftServerProperties {
|
|||||||
public static readonly MinecraftServerProperty<ushort> ServerPort = new UnsignedShort("server-port");
|
public static readonly MinecraftServerProperty<ushort> ServerPort = new UnsignedShort("server-port");
|
||||||
public static readonly MinecraftServerProperty<ushort> RconPort = new UnsignedShort("rcon.port");
|
public static readonly MinecraftServerProperty<ushort> RconPort = new UnsignedShort("rcon.port");
|
||||||
public static readonly MinecraftServerProperty<bool> EnableRcon = new Boolean("enable-rcon");
|
public static readonly MinecraftServerProperty<bool> EnableRcon = new Boolean("enable-rcon");
|
||||||
public static readonly MinecraftServerProperty<bool> SyncChunkWrites = new Boolean("sync-chunk-writes");
|
|
||||||
}
|
}
|
||||||
|
@@ -5,13 +5,11 @@ namespace Phantom.Agent.Minecraft.Properties;
|
|||||||
public sealed record ServerProperties(
|
public sealed record ServerProperties(
|
||||||
ushort ServerPort,
|
ushort ServerPort,
|
||||||
ushort RconPort,
|
ushort RconPort,
|
||||||
bool EnableRcon = true,
|
bool EnableRcon = true
|
||||||
bool SyncChunkWrites = false
|
|
||||||
) {
|
) {
|
||||||
internal void SetTo(JavaPropertiesFileEditor properties) {
|
internal void SetTo(JavaPropertiesFileEditor properties) {
|
||||||
MinecraftServerProperties.ServerPort.Set(properties, ServerPort);
|
MinecraftServerProperties.ServerPort.Set(properties, ServerPort);
|
||||||
MinecraftServerProperties.RconPort.Set(properties, RconPort);
|
MinecraftServerProperties.RconPort.Set(properties, RconPort);
|
||||||
MinecraftServerProperties.EnableRcon.Set(properties, EnableRcon);
|
MinecraftServerProperties.EnableRcon.Set(properties, EnableRcon);
|
||||||
MinecraftServerProperties.SyncChunkWrites.Set(properties, SyncChunkWrites);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,12 +3,28 @@ using System.Buffers.Binary;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Phantom.Common.Data.Instance;
|
using Phantom.Utils.Logging;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Agent.Minecraft.Server;
|
namespace Phantom.Agent.Minecraft.Server;
|
||||||
|
|
||||||
public static class ServerStatusProtocol {
|
public sealed class ServerStatusProtocol {
|
||||||
public static async Task<InstancePlayerCounts> GetPlayerCounts(ushort serverPort, CancellationToken cancellationToken) {
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
public ServerStatusProtocol(string loggerName) {
|
||||||
|
this.logger = PhantomLogger.Create<ServerStatusProtocol>(loggerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int?> GetOnlinePlayerCount(int serverPort, CancellationToken cancellationToken) {
|
||||||
|
try {
|
||||||
|
return await GetOnlinePlayerCountOrThrow(serverPort, cancellationToken);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.Error(e, "Caught exception while checking if players are online.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int?> GetOnlinePlayerCountOrThrow(int serverPort, CancellationToken cancellationToken) {
|
||||||
using var tcpClient = new TcpClient();
|
using var tcpClient = new TcpClient();
|
||||||
await tcpClient.ConnectAsync(IPAddress.Loopback, serverPort, cancellationToken);
|
await tcpClient.ConnectAsync(IPAddress.Loopback, serverPort, cancellationToken);
|
||||||
var tcpStream = tcpClient.GetStream();
|
var tcpStream = tcpClient.GetStream();
|
||||||
@@ -17,22 +33,24 @@ public static class ServerStatusProtocol {
|
|||||||
tcpStream.WriteByte(0xFE);
|
tcpStream.WriteByte(0xFE);
|
||||||
await tcpStream.FlushAsync(cancellationToken);
|
await tcpStream.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
short messageLength = await ReadStreamHeader(tcpStream, cancellationToken);
|
short? messageLength = await ReadStreamHeader(tcpStream, cancellationToken);
|
||||||
return await ReadPlayerCounts(tcpStream, messageLength * 2, cancellationToken);
|
return messageLength == null ? null : await ReadOnlinePlayerCount(tcpStream, messageLength.Value * 2, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<short> ReadStreamHeader(NetworkStream tcpStream, CancellationToken cancellationToken) {
|
private async Task<short?> ReadStreamHeader(NetworkStream tcpStream, CancellationToken cancellationToken) {
|
||||||
var headerBuffer = ArrayPool<byte>.Shared.Rent(3);
|
var headerBuffer = ArrayPool<byte>.Shared.Rent(3);
|
||||||
try {
|
try {
|
||||||
await tcpStream.ReadExactlyAsync(headerBuffer, 0, 3, cancellationToken);
|
await tcpStream.ReadExactlyAsync(headerBuffer, 0, 3, cancellationToken);
|
||||||
|
|
||||||
if (headerBuffer[0] != 0xFF) {
|
if (headerBuffer[0] != 0xFF) {
|
||||||
throw new ProtocolException("Unexpected first byte in response from server: " + headerBuffer[0]);
|
logger.Error("Unexpected first byte in response from server: {FirstByte}.", headerBuffer[0]);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
short messageLength = BinaryPrimitives.ReadInt16BigEndian(headerBuffer.AsSpan(1));
|
short messageLength = BinaryPrimitives.ReadInt16BigEndian(headerBuffer.AsSpan(1));
|
||||||
if (messageLength <= 0) {
|
if (messageLength <= 0) {
|
||||||
throw new ProtocolException("Unexpected message length in response from server: " + messageLength);
|
logger.Error("Unexpected message length in response from server: {MessageLength}.", messageLength);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageLength;
|
return messageLength;
|
||||||
@@ -41,54 +59,35 @@ public static class ServerStatusProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<InstancePlayerCounts> ReadPlayerCounts(NetworkStream tcpStream, int messageLength, CancellationToken cancellationToken) {
|
private async Task<int?> ReadOnlinePlayerCount(NetworkStream tcpStream, int messageLength, CancellationToken cancellationToken) {
|
||||||
var messageBuffer = ArrayPool<byte>.Shared.Rent(messageLength);
|
var messageBuffer = ArrayPool<byte>.Shared.Rent(messageLength);
|
||||||
try {
|
try {
|
||||||
await tcpStream.ReadExactlyAsync(messageBuffer, 0, messageLength, cancellationToken);
|
await tcpStream.ReadExactlyAsync(messageBuffer, 0, messageLength, cancellationToken);
|
||||||
return ReadPlayerCountsFromResponse(messageBuffer.AsSpan(0, messageLength));
|
|
||||||
|
// Valid response separator encoded in UTF-16BE is 0x00 0xA7 (§).
|
||||||
|
const byte SeparatorSecondByte = 0xA7;
|
||||||
|
|
||||||
|
static bool IsValidSeparator(ReadOnlySpan<byte> buffer, int index) {
|
||||||
|
return index > 0 && buffer[index - 1] == 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
int separator2 = Array.LastIndexOf(messageBuffer, SeparatorSecondByte);
|
||||||
|
int separator1 = separator2 == -1 ? -1 : Array.LastIndexOf(messageBuffer, SeparatorSecondByte, separator2 - 1);
|
||||||
|
if (!IsValidSeparator(messageBuffer, separator1) || !IsValidSeparator(messageBuffer, separator2)) {
|
||||||
|
logger.Error("Could not find message separators in response from server.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string onlinePlayerCountStr = Encoding.BigEndianUnicode.GetString(messageBuffer.AsSpan((separator1 + 1)..(separator2 - 1)));
|
||||||
|
if (!int.TryParse(onlinePlayerCountStr, out int onlinePlayerCount)) {
|
||||||
|
logger.Error("Could not parse online player count in response from server: {OnlinePlayerCount}.", onlinePlayerCountStr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Detected {OnlinePlayerCount} online player(s).", onlinePlayerCount);
|
||||||
|
return onlinePlayerCount;
|
||||||
} finally {
|
} finally {
|
||||||
ArrayPool<byte>.Shared.Return(messageBuffer);
|
ArrayPool<byte>.Shared.Return(messageBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Legacy query protocol uses the paragraph symbol (§) as separator encoded in UTF-16BE.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly byte[] Separator = { 0x00, 0xA7 };
|
|
||||||
|
|
||||||
private static InstancePlayerCounts ReadPlayerCountsFromResponse(ReadOnlySpan<byte> messageBuffer) {
|
|
||||||
int lastSeparator = messageBuffer.LastIndexOf(Separator);
|
|
||||||
int middleSeparator = messageBuffer[..lastSeparator].LastIndexOf(Separator);
|
|
||||||
|
|
||||||
if (lastSeparator == -1 || middleSeparator == -1) {
|
|
||||||
throw new ProtocolException("Could not find message separators in response from server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var onlinePlayerCountBuffer = messageBuffer[(middleSeparator + Separator.Length)..lastSeparator];
|
|
||||||
var maximumPlayerCountBuffer = messageBuffer[(lastSeparator + Separator.Length)..];
|
|
||||||
|
|
||||||
// Player counts are integers, whose maximum string length is 10 characters.
|
|
||||||
Span<char> integerStringBuffer = stackalloc char[10];
|
|
||||||
|
|
||||||
return new InstancePlayerCounts(
|
|
||||||
DecodeAndParsePlayerCount(onlinePlayerCountBuffer, integerStringBuffer, "online"),
|
|
||||||
DecodeAndParsePlayerCount(maximumPlayerCountBuffer, integerStringBuffer, "maximum")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int DecodeAndParsePlayerCount(ReadOnlySpan<byte> inputBuffer, Span<char> tempCharBuffer, string countType) {
|
|
||||||
if (!Encoding.BigEndianUnicode.TryGetChars(inputBuffer, tempCharBuffer, out int charCount)) {
|
|
||||||
throw new ProtocolException("Could not decode " + countType + " player count in response from server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!int.TryParse(tempCharBuffer, out int playerCount)) {
|
|
||||||
throw new ProtocolException("Could not parse " + countType + " player count in response from server: " + tempCharBuffer[..charCount].ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return playerCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ProtocolException : Exception {
|
|
||||||
internal ProtocolException(string message) : base(message) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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.Messages.Agent;
|
||||||
|
using Phantom.Utils.Logging;
|
||||||
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Phantom.Agent.Rpc;
|
||||||
|
|
||||||
|
public sealed class ControllerConnection {
|
||||||
|
private static readonly ILogger Logger = PhantomLogger.Create(nameof(ControllerConnection));
|
||||||
|
|
||||||
|
private readonly RpcConnectionToServer<IMessageToController> connection;
|
||||||
|
|
||||||
|
public ControllerConnection(RpcConnectionToServer<IMessageToController> connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
Logger.Information("Connection ready.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Send<TMessage>(TMessage message) where TMessage : IMessageToController {
|
||||||
|
return connection.Send(message);
|
||||||
|
}
|
||||||
|
}
|
44
Agent/Phantom.Agent.Rpc/KeepAliveLoop.cs
Normal file
44
Agent/Phantom.Agent.Rpc/KeepAliveLoop.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Phantom.Common.Messages.Agent;
|
||||||
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
|
using Phantom.Utils.Logging;
|
||||||
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Phantom.Agent.Rpc;
|
||||||
|
|
||||||
|
sealed class KeepAliveLoop {
|
||||||
|
private static readonly ILogger Logger = PhantomLogger.Create<KeepAliveLoop>();
|
||||||
|
|
||||||
|
private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
private readonly RpcConnectionToServer<IMessageToController> connection;
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new ();
|
||||||
|
|
||||||
|
public KeepAliveLoop(RpcConnectionToServer<IMessageToController> connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
Task.Run(Run);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run() {
|
||||||
|
var cancellationToken = cancellationTokenSource.Token;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connection.IsReady.WaitAsync(cancellationToken);
|
||||||
|
Logger.Information("Started keep-alive loop.");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
await Task.Delay(KeepAliveInterval, cancellationToken);
|
||||||
|
await connection.Send(new AgentIsAliveMessage()).WaitAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
} catch (OperationCanceledException) {
|
||||||
|
// Ignore.
|
||||||
|
} finally {
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
Logger.Information("Stopped keep-alive loop.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel() {
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
12
Agent/Phantom.Agent.Rpc/Phantom.Agent.Rpc.csproj
Normal file
12
Agent/Phantom.Agent.Rpc/Phantom.Agent.Rpc.csproj
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
37
Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs
Normal file
37
Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using NetMQ;
|
||||||
|
using NetMQ.Sockets;
|
||||||
|
using Phantom.Common.Messages.Agent;
|
||||||
|
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||||
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
|
using Phantom.Utils.Actor;
|
||||||
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
using Phantom.Utils.Rpc.Sockets;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Phantom.Agent.Rpc;
|
||||||
|
|
||||||
|
public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToAgent, IMessageToController, ReplyMessage> {
|
||||||
|
public static Task Launch(RpcClientSocket<IMessageToAgent, IMessageToController, ReplyMessage> socket, ActorRef<IMessageToAgent> handlerActorRef, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
|
||||||
|
return new RpcClientRuntime(socket, handlerActorRef, disconnectSemaphore, receiveCancellationToken).Launch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RpcClientRuntime(RpcClientSocket<IMessageToAgent, IMessageToController, ReplyMessage> socket, ActorRef<IMessageToAgent> handlerActor, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(socket, handlerActor, disconnectSemaphore, receiveCancellationToken) {}
|
||||||
|
|
||||||
|
protected override async Task RunWithConnection(ClientSocket socket, RpcConnectionToServer<IMessageToController> connection) {
|
||||||
|
var keepAliveLoop = new KeepAliveLoop(connection);
|
||||||
|
try {
|
||||||
|
await base.RunWithConnection(socket, connection);
|
||||||
|
} finally {
|
||||||
|
keepAliveLoop.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task SendDisconnectMessage(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,13 +1,9 @@
|
|||||||
using System.Collections.Immutable;
|
using Akka.Actor;
|
||||||
using Akka.Actor;
|
|
||||||
using Phantom.Agent.Minecraft.Java;
|
using Phantom.Agent.Minecraft.Java;
|
||||||
|
using Phantom.Agent.Rpc;
|
||||||
using Phantom.Agent.Services.Backups;
|
using Phantom.Agent.Services.Backups;
|
||||||
using Phantom.Agent.Services.Instances;
|
using Phantom.Agent.Services.Instances;
|
||||||
using Phantom.Agent.Services.Rpc;
|
|
||||||
using Phantom.Common.Data.Agent;
|
using Phantom.Common.Data.Agent;
|
||||||
using Phantom.Common.Data.Replies;
|
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
|
||||||
using Phantom.Common.Messages.Agent.ToController;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -19,7 +15,6 @@ public sealed class AgentServices {
|
|||||||
|
|
||||||
public ActorSystem ActorSystem { get; }
|
public ActorSystem ActorSystem { get; }
|
||||||
|
|
||||||
private AgentInfo AgentInfo { get; }
|
|
||||||
private AgentFolders AgentFolders { get; }
|
private AgentFolders AgentFolders { get; }
|
||||||
private AgentState AgentState { get; }
|
private AgentState AgentState { get; }
|
||||||
private BackupManager BackupManager { get; }
|
private BackupManager BackupManager { get; }
|
||||||
@@ -31,7 +26,6 @@ public sealed class AgentServices {
|
|||||||
public AgentServices(AgentInfo agentInfo, AgentFolders agentFolders, AgentServiceConfiguration serviceConfiguration, ControllerConnection controllerConnection) {
|
public AgentServices(AgentInfo agentInfo, AgentFolders agentFolders, AgentServiceConfiguration serviceConfiguration, ControllerConnection controllerConnection) {
|
||||||
this.ActorSystem = ActorSystemFactory.Create("Agent");
|
this.ActorSystem = ActorSystemFactory.Create("Agent");
|
||||||
|
|
||||||
this.AgentInfo = agentInfo;
|
|
||||||
this.AgentFolders = agentFolders;
|
this.AgentFolders = agentFolders;
|
||||||
this.AgentState = new AgentState();
|
this.AgentState = new AgentState();
|
||||||
this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks);
|
this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks);
|
||||||
@@ -49,41 +43,6 @@ public sealed class AgentServices {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> Register(ControllerConnection connection, CancellationToken cancellationToken) {
|
|
||||||
Logger.Information("Registering with the controller...");
|
|
||||||
|
|
||||||
// TODO NEED TO SEND WHEN SERVER RESTARTS!!!
|
|
||||||
|
|
||||||
ImmutableArray<ConfigureInstanceMessage> configureInstanceMessages;
|
|
||||||
try {
|
|
||||||
configureInstanceMessages = await connection.Send<RegisterAgentMessage, ImmutableArray<ConfigureInstanceMessage>>(new RegisterAgentMessage(AgentInfo), TimeSpan.FromMinutes(1), cancellationToken);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.Fatal(e, "Registration failed.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var configureInstanceMessage in configureInstanceMessages) {
|
|
||||||
var configureInstanceCommand = new InstanceManagerActor.ConfigureInstanceCommand(
|
|
||||||
configureInstanceMessage.InstanceGuid,
|
|
||||||
configureInstanceMessage.Configuration,
|
|
||||||
configureInstanceMessage.LaunchProperties,
|
|
||||||
configureInstanceMessage.LaunchNow,
|
|
||||||
AlwaysReportStatus: true
|
|
||||||
);
|
|
||||||
|
|
||||||
var configureInstanceResult = await InstanceManager.Request(configureInstanceCommand, cancellationToken);
|
|
||||||
if (!configureInstanceResult.Is(ConfigureInstanceResult.Success)) {
|
|
||||||
Logger.Fatal("Unable to configure instance \"{Name}\" (GUID {Guid}), shutting down.", configureInstanceMessage.Configuration.InstanceName, configureInstanceMessage.InstanceGuid);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.Send(new AdvertiseJavaRuntimesMessage(JavaRuntimeRepository.All), cancellationToken);
|
|
||||||
InstanceTicketManager.RefreshAgentStatus();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Shutdown() {
|
public async Task Shutdown() {
|
||||||
Logger.Information("Stopping services...");
|
Logger.Information("Stopping services...");
|
||||||
|
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
using Phantom.Agent.Services.Instances;
|
using Phantom.Agent.Minecraft.Instance;
|
||||||
using Phantom.Agent.Services.Instances.State;
|
using Phantom.Agent.Minecraft.Server;
|
||||||
|
using Phantom.Agent.Services.Instances;
|
||||||
using Phantom.Common.Data.Backups;
|
using Phantom.Common.Data.Backups;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Tasks;
|
using Phantom.Utils.Tasks;
|
||||||
|
using Phantom.Utils.Threading;
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Backups;
|
namespace Phantom.Agent.Services.Backups;
|
||||||
|
|
||||||
@@ -14,16 +16,20 @@ sealed class BackupScheduler : CancellableBackgroundTask {
|
|||||||
|
|
||||||
private readonly BackupManager backupManager;
|
private readonly BackupManager backupManager;
|
||||||
private readonly InstanceContext context;
|
private readonly InstanceContext context;
|
||||||
|
private readonly InstanceProcess process;
|
||||||
private readonly SemaphoreSlim backupSemaphore = new (1, 1);
|
private readonly SemaphoreSlim backupSemaphore = new (1, 1);
|
||||||
|
private readonly int serverPort;
|
||||||
|
private readonly ServerStatusProtocol serverStatusProtocol;
|
||||||
private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new ();
|
private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new ();
|
||||||
private readonly InstancePlayerCountTracker playerCountTracker;
|
|
||||||
|
|
||||||
public event EventHandler<BackupCreationResult>? BackupCompleted;
|
public event EventHandler<BackupCreationResult>? BackupCompleted;
|
||||||
|
|
||||||
public BackupScheduler(InstanceContext context, InstancePlayerCountTracker playerCountTracker) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName)) {
|
public BackupScheduler(InstanceContext context, InstanceProcess process, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName)) {
|
||||||
this.backupManager = context.Services.BackupManager;
|
this.backupManager = context.Services.BackupManager;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.playerCountTracker = playerCountTracker;
|
this.process = process;
|
||||||
|
this.serverPort = serverPort;
|
||||||
|
this.serverStatusProtocol = new ServerStatusProtocol(context.ShortName);
|
||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,18 +69,43 @@ sealed class BackupScheduler : CancellableBackgroundTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task WaitForOnlinePlayers() {
|
private async Task WaitForOnlinePlayers() {
|
||||||
var task = playerCountTracker.WaitForOnlinePlayers(CancellationToken);
|
bool needsToLogOfflinePlayersMessage = true;
|
||||||
if (!task.IsCompleted) {
|
|
||||||
Logger.Information("Waiting for someone to join before starting a new backup.");
|
process.AddOutputListener(ServerOutputListener, maxLinesToReadFromHistory: 0);
|
||||||
|
try {
|
||||||
|
while (!CancellationToken.IsCancellationRequested) {
|
||||||
|
serverOutputWhileWaitingForOnlinePlayers.Reset();
|
||||||
|
|
||||||
|
var onlinePlayerCount = await serverStatusProtocol.GetOnlinePlayerCount(serverPort, CancellationToken);
|
||||||
|
if (onlinePlayerCount == null) {
|
||||||
|
Logger.Warning("Could not detect whether any players are online, starting a new backup.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (onlinePlayerCount > 0) {
|
||||||
await task;
|
|
||||||
Logger.Information("Players are online, starting a new backup.");
|
Logger.Information("Players are online, starting a new backup.");
|
||||||
} catch (OperationCanceledException) {
|
break;
|
||||||
throw;
|
}
|
||||||
} catch (Exception) {
|
|
||||||
Logger.Warning("Could not detect whether any players are online, starting a new backup.");
|
if (needsToLogOfflinePlayersMessage) {
|
||||||
|
needsToLogOfflinePlayersMessage = false;
|
||||||
|
Logger.Information("No players are online, waiting for someone to join before starting a new backup.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
|
||||||
|
|
||||||
|
Logger.Debug("Waiting for server output before checking for online players again...");
|
||||||
|
await serverOutputWhileWaitingForOnlinePlayers.WaitHandle.WaitOneAsync(CancellationToken);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
process.RemoveOutputListener(ServerOutputListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ServerOutputListener(object? sender, string line) {
|
||||||
|
if (!serverOutputWhileWaitingForOnlinePlayers.IsSet) {
|
||||||
|
serverOutputWhileWaitingForOnlinePlayers.Set();
|
||||||
|
Logger.Debug("Detected server output, signalling to check for online players again.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -55,6 +55,7 @@ sealed partial class BackupServerCommandDispatcher : IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAllChunks() {
|
public async Task SaveAllChunks() {
|
||||||
|
// TODO Try if not flushing and waiting a few seconds before flushing reduces lag.
|
||||||
await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken);
|
await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken);
|
||||||
await savedTheGame.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
|
await savedTheGame.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
|
||||||
}
|
}
|
||||||
|
@@ -58,7 +58,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
|
|
||||||
private void ReportCurrentStatus() {
|
private void ReportCurrentStatus() {
|
||||||
agentState.UpdateInstance(new Instance(instanceGuid, currentStatus));
|
agentState.UpdateInstance(new Instance(instanceGuid, currentStatus));
|
||||||
instanceServices.ControllerConnection.TrySend(new ReportInstanceStatusMessage(instanceGuid, currentStatus));
|
instanceServices.ControllerConnection.Send(new ReportInstanceStatusMessage(instanceGuid, currentStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TransitionState(InstanceRunningState? newState) {
|
private void TransitionState(InstanceRunningState? newState) {
|
||||||
|
@@ -7,6 +7,6 @@ namespace Phantom.Agent.Services.Instances;
|
|||||||
|
|
||||||
sealed record InstanceContext(Guid InstanceGuid, string ShortName, ILogger Logger, InstanceServices Services, ActorRef<InstanceActor.ICommand> Actor, CancellationToken ActorCancellationToken) {
|
sealed record InstanceContext(Guid InstanceGuid, string ShortName, ILogger Logger, InstanceServices Services, ActorRef<InstanceActor.ICommand> Actor, CancellationToken ActorCancellationToken) {
|
||||||
public void ReportEvent(IInstanceEvent instanceEvent) {
|
public void ReportEvent(IInstanceEvent instanceEvent) {
|
||||||
Services.ControllerConnection.TrySend(new ReportInstanceEventMessage(Guid.NewGuid(), DateTime.UtcNow, InstanceGuid, instanceEvent));
|
Services.ControllerConnection.Send(new ReportInstanceEventMessage(Guid.NewGuid(), DateTime.UtcNow, InstanceGuid, instanceEvent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ using Phantom.Agent.Minecraft.Launcher;
|
|||||||
using Phantom.Agent.Minecraft.Launcher.Types;
|
using Phantom.Agent.Minecraft.Launcher.Types;
|
||||||
using Phantom.Agent.Minecraft.Properties;
|
using Phantom.Agent.Minecraft.Properties;
|
||||||
using Phantom.Agent.Minecraft.Server;
|
using Phantom.Agent.Minecraft.Server;
|
||||||
|
using Phantom.Agent.Rpc;
|
||||||
using Phantom.Agent.Services.Backups;
|
using Phantom.Agent.Services.Backups;
|
||||||
using Phantom.Agent.Services.Rpc;
|
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Instance;
|
using Phantom.Common.Data.Instance;
|
||||||
using Phantom.Common.Data.Minecraft;
|
using Phantom.Common.Data.Minecraft;
|
||||||
@@ -102,6 +102,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
|
|||||||
IServerLauncher launcher = configuration.MinecraftServerKind switch {
|
IServerLauncher launcher = configuration.MinecraftServerKind switch {
|
||||||
MinecraftServerKind.Vanilla => new VanillaLauncher(properties),
|
MinecraftServerKind.Vanilla => new VanillaLauncher(properties),
|
||||||
MinecraftServerKind.Fabric => new FabricLauncher(properties),
|
MinecraftServerKind.Fabric => new FabricLauncher(properties),
|
||||||
|
MinecraftServerKind.Forge => new ForgeLauncher(properties),
|
||||||
_ => InvalidLauncher.Instance
|
_ => InvalidLauncher.Instance
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using Phantom.Agent.Minecraft.Launcher;
|
using Phantom.Agent.Minecraft.Launcher;
|
||||||
|
using Phantom.Agent.Rpc;
|
||||||
using Phantom.Agent.Services.Backups;
|
using Phantom.Agent.Services.Backups;
|
||||||
using Phantom.Agent.Services.Rpc;
|
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Instances;
|
namespace Phantom.Agent.Services.Instances;
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
using Phantom.Agent.Services.Rpc;
|
using Phantom.Agent.Rpc;
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Agent;
|
using Phantom.Common.Data.Agent;
|
||||||
using Phantom.Common.Data.Instance;
|
using Phantom.Common.Data.Instance;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Messages.Agent.ToController;
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
|
using Phantom.Utils.Tasks;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Instances;
|
namespace Phantom.Agent.Services.Instances;
|
||||||
@@ -91,7 +92,7 @@ sealed class InstanceTicketManager {
|
|||||||
|
|
||||||
public void RefreshAgentStatus() {
|
public void RefreshAgentStatus() {
|
||||||
lock (this) {
|
lock (this) {
|
||||||
controllerConnection.TrySend(new ReportAgentStatusMessage(activeTicketGuids.Count, usedMemory));
|
controllerConnection.Send(new ReportAgentStatusMessage(activeTicketGuids.Count, usedMemory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using Phantom.Agent.Services.Rpc;
|
using Phantom.Agent.Rpc;
|
||||||
using Phantom.Common.Messages.Agent.ToController;
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Tasks;
|
using Phantom.Utils.Tasks;
|
||||||
@@ -63,7 +63,7 @@ sealed class InstanceLogSender : CancellableBackgroundTask {
|
|||||||
|
|
||||||
private void SendOutputToServer(ImmutableArray<string> lines) {
|
private void SendOutputToServer(ImmutableArray<string> lines) {
|
||||||
if (!lines.IsEmpty) {
|
if (!lines.IsEmpty) {
|
||||||
controllerConnection.TrySend(new InstanceOutputMessage(instanceGuid, lines));
|
controllerConnection.Send(new InstanceOutputMessage(instanceGuid, lines));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,140 +0,0 @@
|
|||||||
using Phantom.Agent.Minecraft.Instance;
|
|
||||||
using Phantom.Agent.Minecraft.Server;
|
|
||||||
using Phantom.Agent.Services.Rpc;
|
|
||||||
using Phantom.Common.Data.Instance;
|
|
||||||
using Phantom.Common.Messages.Agent.ToController;
|
|
||||||
using Phantom.Utils.Logging;
|
|
||||||
using Phantom.Utils.Tasks;
|
|
||||||
using Phantom.Utils.Threading;
|
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Instances.State;
|
|
||||||
|
|
||||||
sealed class InstancePlayerCountTracker : CancellableBackgroundTask {
|
|
||||||
private readonly ControllerConnection controllerConnection;
|
|
||||||
private readonly Guid instanceGuid;
|
|
||||||
private readonly ushort serverPort;
|
|
||||||
private readonly InstanceProcess process;
|
|
||||||
|
|
||||||
private readonly TaskCompletionSource firstDetection = AsyncTasks.CreateCompletionSource();
|
|
||||||
private readonly ManualResetEventSlim serverOutputEvent = new ();
|
|
||||||
|
|
||||||
private InstancePlayerCounts? playerCounts;
|
|
||||||
|
|
||||||
public InstancePlayerCounts? PlayerCounts {
|
|
||||||
get {
|
|
||||||
lock (this) {
|
|
||||||
return playerCounts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private set {
|
|
||||||
EventHandler<int?>? onlinePlayerCountChanged;
|
|
||||||
lock (this) {
|
|
||||||
if (playerCounts == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
playerCounts = value;
|
|
||||||
onlinePlayerCountChanged = OnlinePlayerCountChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
onlinePlayerCountChanged?.Invoke(this, value?.Online);
|
|
||||||
controllerConnection.TrySend(new ReportInstancePlayerCountsMessage(instanceGuid, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private event EventHandler<int?>? OnlinePlayerCountChanged;
|
|
||||||
|
|
||||||
private bool isDisposed = false;
|
|
||||||
|
|
||||||
public InstancePlayerCountTracker(InstanceContext context, InstanceProcess process, ushort serverPort) : base(PhantomLogger.Create<InstancePlayerCountTracker>(context.ShortName)) {
|
|
||||||
this.controllerConnection = context.Services.ControllerConnection;
|
|
||||||
this.instanceGuid = context.InstanceGuid;
|
|
||||||
this.process = process;
|
|
||||||
this.serverPort = serverPort;
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task RunTask() {
|
|
||||||
// Give the server time to start accepting connections.
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
|
|
||||||
|
|
||||||
serverOutputEvent.Set();
|
|
||||||
process.AddOutputListener(OnOutput, maxLinesToReadFromHistory: 0);
|
|
||||||
|
|
||||||
while (!CancellationToken.IsCancellationRequested) {
|
|
||||||
serverOutputEvent.Reset();
|
|
||||||
|
|
||||||
PlayerCounts = await TryGetPlayerCounts();
|
|
||||||
|
|
||||||
if (!firstDetection.Task.IsCompleted) {
|
|
||||||
firstDetection.SetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
|
|
||||||
await serverOutputEvent.WaitHandle.WaitOneAsync(CancellationToken);
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), CancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<InstancePlayerCounts?> TryGetPlayerCounts() {
|
|
||||||
try {
|
|
||||||
var result = await ServerStatusProtocol.GetPlayerCounts(serverPort, CancellationToken);
|
|
||||||
Logger.Debug("Detected {OnlinePlayerCount} / {MaximumPlayerCount} online player(s).", result.Online, result.Maximum);
|
|
||||||
return result;
|
|
||||||
} catch (ServerStatusProtocol.ProtocolException e) {
|
|
||||||
Logger.Error(e.Message);
|
|
||||||
return null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.Error(e, "Caught exception while checking online player count.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WaitForOnlinePlayers(CancellationToken cancellationToken) {
|
|
||||||
await firstDetection.Task.WaitAsync(cancellationToken);
|
|
||||||
|
|
||||||
var onlinePlayersDetected = AsyncTasks.CreateCompletionSource();
|
|
||||||
|
|
||||||
lock (this) {
|
|
||||||
if (playerCounts is { Online: > 0 }) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (playerCounts == null) {
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
OnlinePlayerCountChanged += OnOnlinePlayerCountChanged;
|
|
||||||
|
|
||||||
void OnOnlinePlayerCountChanged(object? sender, int? newPlayerCount) {
|
|
||||||
if (newPlayerCount == null) {
|
|
||||||
onlinePlayersDetected.TrySetException(new InvalidOperationException());
|
|
||||||
OnlinePlayerCountChanged -= OnOnlinePlayerCountChanged;
|
|
||||||
}
|
|
||||||
else if (newPlayerCount > 0) {
|
|
||||||
onlinePlayersDetected.TrySetResult();
|
|
||||||
OnlinePlayerCountChanged -= OnOnlinePlayerCountChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await onlinePlayersDetected.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnOutput(object? sender, string? line) {
|
|
||||||
lock (this) {
|
|
||||||
if (!isDisposed) {
|
|
||||||
serverOutputEvent.Set();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose() {
|
|
||||||
lock (this) {
|
|
||||||
isDisposed = true;
|
|
||||||
playerCounts = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
process.RemoveOutputListener(OnOutput);
|
|
||||||
serverOutputEvent.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -19,7 +19,6 @@ sealed class InstanceRunningState : IDisposable {
|
|||||||
private readonly CancellationToken cancellationToken;
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
private readonly InstanceLogSender logSender;
|
private readonly InstanceLogSender logSender;
|
||||||
private readonly InstancePlayerCountTracker playerCountTracker;
|
|
||||||
private readonly BackupScheduler backupScheduler;
|
private readonly BackupScheduler backupScheduler;
|
||||||
|
|
||||||
private bool isDisposed;
|
private bool isDisposed;
|
||||||
@@ -33,9 +32,8 @@ sealed class InstanceRunningState : IDisposable {
|
|||||||
this.cancellationToken = cancellationToken;
|
this.cancellationToken = cancellationToken;
|
||||||
|
|
||||||
this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.InstanceGuid, context.ShortName);
|
this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.InstanceGuid, context.ShortName);
|
||||||
this.playerCountTracker = new InstancePlayerCountTracker(context, process, configuration.ServerPort);
|
|
||||||
|
|
||||||
this.backupScheduler = new BackupScheduler(context, playerCountTracker);
|
this.backupScheduler = new BackupScheduler(context, process, configuration.ServerPort);
|
||||||
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
|
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,11 +93,6 @@ sealed class InstanceRunningState : IDisposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStopInitiated() {
|
|
||||||
backupScheduler.Stop();
|
|
||||||
playerCountTracker.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryDispose() {
|
private bool TryDispose() {
|
||||||
lock (this) {
|
lock (this) {
|
||||||
if (isDisposed) {
|
if (isDisposed) {
|
||||||
@@ -109,8 +102,8 @@ sealed class InstanceRunningState : IDisposable {
|
|||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnStopInitiated();
|
|
||||||
logSender.Stop();
|
logSender.Stop();
|
||||||
|
backupScheduler.Stop();
|
||||||
|
|
||||||
Process.Dispose();
|
Process.Dispose();
|
||||||
|
|
||||||
|
@@ -25,8 +25,6 @@ static class InstanceStopProcedure {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Too late to cancel the stop procedure now.
|
// Too late to cancel the stop procedure now.
|
||||||
runningState.OnStopInitiated();
|
|
||||||
|
|
||||||
if (!process.HasEnded) {
|
if (!process.HasEnded) {
|
||||||
context.Logger.Information("Session stopping now.");
|
context.Logger.Information("Session stopping now.");
|
||||||
await DoStop(context, process);
|
await DoStop(context, process);
|
||||||
@@ -87,7 +85,7 @@ static class InstanceStopProcedure {
|
|||||||
private static async Task WaitForSessionToEnd(InstanceContext context, InstanceProcess process) {
|
private static async Task WaitForSessionToEnd(InstanceContext context, InstanceProcess process) {
|
||||||
try {
|
try {
|
||||||
await process.WaitForExit(TimeSpan.FromSeconds(55));
|
await process.WaitForExit(TimeSpan.FromSeconds(55));
|
||||||
} catch (TimeoutException) {
|
} catch (OperationCanceledException) {
|
||||||
try {
|
try {
|
||||||
context.Logger.Warning("Waiting timed out, killing session...");
|
context.Logger.Warning("Waiting timed out, killing session...");
|
||||||
process.Kill();
|
process.Kill();
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.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.Minecraft\Phantom.Agent.Minecraft.csproj" />
|
||||||
|
<ProjectReference Include="..\Phantom.Agent.Rpc\Phantom.Agent.Rpc.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
using Phantom.Common.Messages.Agent;
|
|
||||||
using Phantom.Utils.Actor;
|
|
||||||
using Phantom.Utils.Rpc.Runtime;
|
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Rpc;
|
|
||||||
|
|
||||||
public sealed class ControllerConnection(RpcSendChannel<IMessageToController> sendChannel) {
|
|
||||||
public ValueTask Send<TMessage>(TMessage message, CancellationToken cancellationToken) where TMessage : IMessageToController {
|
|
||||||
return sendChannel.SendMessage(message, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle properly
|
|
||||||
public bool TrySend<TMessage>(TMessage message) where TMessage : IMessageToController {
|
|
||||||
return sendChannel.TrySendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken cancellationToken) where TMessage : IMessageToController, ICanReply<TReply> {
|
|
||||||
return sendChannel.SendMessage<TMessage, TReply>(message, waitForReplyTime, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +1,86 @@
|
|||||||
using Phantom.Agent.Services.Instances;
|
using Phantom.Agent.Services.Instances;
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
|
using Phantom.Common.Data.Instance;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Messages.Agent;
|
using Phantom.Common.Messages.Agent;
|
||||||
|
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
using Phantom.Common.Messages.Agent.ToAgent;
|
||||||
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
using Phantom.Utils.Logging;
|
||||||
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Rpc;
|
namespace Phantom.Agent.Services.Rpc;
|
||||||
|
|
||||||
public sealed class ControllerMessageHandlerActor : ReceiveActor<IMessageToAgent> {
|
public sealed class ControllerMessageHandlerActor : ReceiveActor<IMessageToAgent> {
|
||||||
public readonly record struct Init(AgentServices Agent);
|
private static ILogger Logger { get; } = PhantomLogger.Create<ControllerMessageHandlerActor>();
|
||||||
|
|
||||||
|
public readonly record struct Init(RpcConnectionToServer<IMessageToController> Connection, AgentServices Agent, CancellationTokenSource ShutdownTokenSource);
|
||||||
|
|
||||||
public static Props<IMessageToAgent> Factory(Init init) {
|
public static Props<IMessageToAgent> Factory(Init init) {
|
||||||
return Props<IMessageToAgent>.Create(() => new ControllerMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
return Props<IMessageToAgent>.Create(() => new ControllerMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly RpcConnectionToServer<IMessageToController> connection;
|
||||||
private readonly AgentServices agent;
|
private readonly AgentServices agent;
|
||||||
|
private readonly CancellationTokenSource shutdownTokenSource;
|
||||||
|
|
||||||
private ControllerMessageHandlerActor(Init init) {
|
private ControllerMessageHandlerActor(Init init) {
|
||||||
|
this.connection = init.Connection;
|
||||||
this.agent = init.Agent;
|
this.agent = init.Agent;
|
||||||
|
this.shutdownTokenSource = init.ShutdownTokenSource;
|
||||||
|
|
||||||
|
ReceiveAsync<RegisterAgentSuccessMessage>(HandleRegisterAgentSuccess);
|
||||||
|
Receive<RegisterAgentFailureMessage>(HandleRegisterAgentFailure);
|
||||||
ReceiveAndReplyLater<ConfigureInstanceMessage, Result<ConfigureInstanceResult, InstanceActionFailure>>(HandleConfigureInstance);
|
ReceiveAndReplyLater<ConfigureInstanceMessage, Result<ConfigureInstanceResult, InstanceActionFailure>>(HandleConfigureInstance);
|
||||||
ReceiveAndReplyLater<LaunchInstanceMessage, Result<LaunchInstanceResult, InstanceActionFailure>>(HandleLaunchInstance);
|
ReceiveAndReplyLater<LaunchInstanceMessage, Result<LaunchInstanceResult, InstanceActionFailure>>(HandleLaunchInstance);
|
||||||
ReceiveAndReplyLater<StopInstanceMessage, Result<StopInstanceResult, InstanceActionFailure>>(HandleStopInstance);
|
ReceiveAndReplyLater<StopInstanceMessage, Result<StopInstanceResult, InstanceActionFailure>>(HandleStopInstance);
|
||||||
ReceiveAndReplyLater<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, InstanceActionFailure>>(HandleSendCommandToInstance);
|
ReceiveAndReplyLater<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, InstanceActionFailure>>(HandleSendCommandToInstance);
|
||||||
|
Receive<ReplyMessage>(HandleReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleRegisterAgentSuccess(RegisterAgentSuccessMessage message) {
|
||||||
|
Logger.Information("Agent authentication successful.");
|
||||||
|
|
||||||
|
void ShutdownAfterConfigurationFailed(Guid instanceGuid, InstanceConfiguration configuration) {
|
||||||
|
Logger.Fatal("Unable to configure instance \"{Name}\" (GUID {Guid}), shutting down.", configuration.InstanceName, instanceGuid);
|
||||||
|
shutdownTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var configureInstanceMessage in message.InitialInstanceConfigurations) {
|
||||||
|
var result = await HandleConfigureInstance(configureInstanceMessage, alwaysReportStatus: true);
|
||||||
|
if (!result.Is(ConfigureInstanceResult.Success)) {
|
||||||
|
ShutdownAfterConfigurationFailed(configureInstanceMessage.InstanceGuid, configureInstanceMessage.Configuration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.SetIsReady();
|
||||||
|
|
||||||
|
await connection.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All));
|
||||||
|
agent.InstanceTicketManager.RefreshAgentStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRegisterAgentFailure(RegisterAgentFailureMessage message) {
|
||||||
|
string errorMessage = message.FailureKind switch {
|
||||||
|
RegisterAgentFailure.ConnectionAlreadyHasAnAgent => "This connection already has an associated agent.",
|
||||||
|
RegisterAgentFailure.InvalidToken => "Invalid token.",
|
||||||
|
_ => "Unknown error " + (byte) message.FailureKind + "."
|
||||||
|
};
|
||||||
|
|
||||||
|
Logger.Fatal("Agent authentication failed: {Error}", errorMessage);
|
||||||
|
|
||||||
|
PhantomLogger.Dispose();
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<Result<ConfigureInstanceResult, InstanceActionFailure>> HandleConfigureInstance(ConfigureInstanceMessage message, bool alwaysReportStatus) {
|
||||||
|
return agent.InstanceManager.Request(new InstanceManagerActor.ConfigureInstanceCommand(message.InstanceGuid, message.Configuration, message.LaunchProperties, message.LaunchNow, alwaysReportStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Result<ConfigureInstanceResult, InstanceActionFailure>> HandleConfigureInstance(ConfigureInstanceMessage message) {
|
private async Task<Result<ConfigureInstanceResult, InstanceActionFailure>> HandleConfigureInstance(ConfigureInstanceMessage message) {
|
||||||
return await agent.InstanceManager.Request(new InstanceManagerActor.ConfigureInstanceCommand(message.InstanceGuid, message.Configuration, message.LaunchProperties, message.LaunchNow, AlwaysReportStatus: false));
|
return await HandleConfigureInstance(message, alwaysReportStatus: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Result<LaunchInstanceResult, InstanceActionFailure>> HandleLaunchInstance(LaunchInstanceMessage message) {
|
private async Task<Result<LaunchInstanceResult, InstanceActionFailure>> HandleLaunchInstance(LaunchInstanceMessage message) {
|
||||||
@@ -40,4 +94,8 @@ public sealed class ControllerMessageHandlerActor : ReceiveActor<IMessageToAgent
|
|||||||
private async Task<Result<SendCommandToInstanceResult, InstanceActionFailure>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) {
|
private async Task<Result<SendCommandToInstanceResult, InstanceActionFailure>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) {
|
||||||
return await agent.InstanceManager.Request(new InstanceManagerActor.SendCommandToInstanceCommand(message.InstanceGuid, message.Command));
|
return await agent.InstanceManager.Request(new InstanceManagerActor.SendCommandToInstanceCommand(message.InstanceGuid, message.Command));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleReply(ReplyMessage message) {
|
||||||
|
connection.Receive(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
using System.Text;
|
using NetMQ;
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Utils.Cryptography;
|
using Phantom.Utils.Cryptography;
|
||||||
using Phantom.Utils.IO;
|
using Phantom.Utils.IO;
|
||||||
@@ -10,7 +10,7 @@ namespace Phantom.Agent;
|
|||||||
static class AgentKey {
|
static class AgentKey {
|
||||||
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(AgentKey));
|
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(AgentKey));
|
||||||
|
|
||||||
public static Task<ConnectionKey?> Load(string? agentKeyToken, string? agentKeyFilePath) {
|
public static Task<(NetMQCertificate, AuthToken)?> Load(string? agentKeyToken, string? agentKeyFilePath) {
|
||||||
if (agentKeyFilePath != null) {
|
if (agentKeyFilePath != null) {
|
||||||
return LoadFromFile(agentKeyFilePath);
|
return LoadFromFile(agentKeyFilePath);
|
||||||
}
|
}
|
||||||
@@ -22,19 +22,18 @@ static class AgentKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ConnectionKey?> LoadFromFile(string agentKeyFilePath) {
|
private static async Task<(NetMQCertificate, AuthToken)?> LoadFromFile(string agentKeyFilePath) {
|
||||||
if (!File.Exists(agentKeyFilePath)) {
|
if (!File.Exists(agentKeyFilePath)) {
|
||||||
Logger.Fatal("Missing agent key file: {AgentKeyFilePath}", agentKeyFilePath);
|
Logger.Fatal("Missing agent key file: {AgentKeyFilePath}", agentKeyFilePath);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.RequireMaximumFileSize(agentKeyFilePath, maximumBytes: 64);
|
Files.RequireMaximumFileSize(agentKeyFilePath, 64);
|
||||||
string[] lines = await File.ReadAllLinesAsync(agentKeyFilePath, Encoding.UTF8);
|
return LoadFromBytes(await File.ReadAllBytesAsync(agentKeyFilePath));
|
||||||
return LoadFromToken(lines[0]);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.Fatal("Error loading agent key from file: {AgentKeyFilePath}", agentKeyFilePath);
|
Logger.Fatal("Error loading agent key from file: {AgentKeyFilePath}", agentKeyFilePath);
|
||||||
Logger.Fatal("{}", e.Message);
|
Logger.Fatal(e.Message);
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
Logger.Fatal("File does not contain a valid agent key: {AgentKeyFilePath}", agentKeyFilePath);
|
Logger.Fatal("File does not contain a valid agent key: {AgentKeyFilePath}", agentKeyFilePath);
|
||||||
@@ -42,7 +41,7 @@ static class AgentKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConnectionKey? LoadFromToken(string agentKey) {
|
private static (NetMQCertificate, AuthToken)? LoadFromToken(string agentKey) {
|
||||||
try {
|
try {
|
||||||
return LoadFromBytes(TokenGenerator.DecodeBytes(agentKey));
|
return LoadFromBytes(TokenGenerator.DecodeBytes(agentKey));
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
@@ -51,9 +50,11 @@ static class AgentKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConnectionKey? LoadFromBytes(byte[] agentKey) {
|
private static (NetMQCertificate, AuthToken)? LoadFromBytes(byte[] agentKey) {
|
||||||
var connectionKey = ConnectionKey.FromBytes(agentKey);
|
var (publicKey, agentToken) = ConnectionCommonKey.FromBytes(agentKey);
|
||||||
|
var controllerCertificate = NetMQCertificate.FromPublicKey(publicKey);
|
||||||
|
|
||||||
Logger.Information("Loaded agent key.");
|
Logger.Information("Loaded agent key.");
|
||||||
return connectionKey;
|
return (controllerCertificate, agentToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using NetMQ;
|
||||||
using Phantom.Agent;
|
using Phantom.Agent;
|
||||||
|
using Phantom.Agent.Rpc;
|
||||||
using Phantom.Agent.Services;
|
using Phantom.Agent.Services;
|
||||||
using Phantom.Agent.Services.Rpc;
|
using Phantom.Agent.Services.Rpc;
|
||||||
using Phantom.Common.Data.Agent;
|
using Phantom.Common.Data.Agent;
|
||||||
@@ -7,9 +9,9 @@ using Phantom.Common.Messages.Agent;
|
|||||||
using Phantom.Common.Messages.Agent.ToController;
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Rpc.Runtime.Client;
|
using Phantom.Utils.Rpc;
|
||||||
|
using Phantom.Utils.Rpc.Sockets;
|
||||||
using Phantom.Utils.Runtime;
|
using Phantom.Utils.Runtime;
|
||||||
using Phantom.Utils.Threading;
|
|
||||||
|
|
||||||
const int ProtocolVersion = 1;
|
const int ProtocolVersion = 1;
|
||||||
|
|
||||||
@@ -46,55 +48,33 @@ try {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcClientConnectionParameters = new RpcClientConnectionParameters(
|
var (controllerCertificate, agentToken) = agentKey.Value;
|
||||||
Host: controllerHost,
|
var agentInfo = new AgentInfo(agentGuid.Value, agentName, ProtocolVersion, fullVersion, maxInstances, maxMemory, allowedServerPorts, allowedRconPorts);
|
||||||
Port: controllerPort,
|
|
||||||
DistinguishedName: "phantom-controller",
|
|
||||||
CertificateThumbprint: agentKey.Value.CertificateThumbprint,
|
|
||||||
AuthToken: agentKey.Value.AuthToken,
|
|
||||||
SendQueueCapacity: 500,
|
|
||||||
PingInterval: TimeSpan.FromSeconds(10)
|
|
||||||
);
|
|
||||||
|
|
||||||
using var rpcClient = await RpcClient<IMessageToController, IMessageToAgent>.Connect("Controller", rpcClientConnectionParameters, AgentMessageRegistries.Definitions, shutdownCancellationToken);
|
|
||||||
if (rpcClient == null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var controllerConnection = new ControllerConnection(rpcClient.SendChannel);
|
|
||||||
|
|
||||||
Task? rpcClientListener = null;
|
|
||||||
try {
|
|
||||||
PhantomLogger.Root.InformationHeading("Launching Phantom Panel agent...");
|
PhantomLogger.Root.InformationHeading("Launching Phantom Panel agent...");
|
||||||
|
|
||||||
var agentInfo = new AgentInfo(agentGuid.Value, agentName, ProtocolVersion, fullVersion, maxInstances, maxMemory, allowedServerPorts, allowedRconPorts);
|
var rpcConfiguration = new RpcConfiguration("Agent", controllerHost, controllerPort, controllerCertificate);
|
||||||
var agentServices = new AgentServices(agentInfo, folders, new AgentServiceConfiguration(maxConcurrentBackupCompressionTasks), controllerConnection);
|
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();
|
await agentServices.Initialize();
|
||||||
|
|
||||||
var rpcMessageHandlerInit = new ControllerMessageHandlerActor.Init(agentServices);
|
var rpcMessageHandlerInit = new ControllerMessageHandlerActor.Init(rpcSocket.Connection, agentServices, shutdownCancellationTokenSource);
|
||||||
var rpcMessageHandlerActor = agentServices.ActorSystem.ActorOf(ControllerMessageHandlerActor.Factory(rpcMessageHandlerInit), "ControllerMessageHandler");
|
var rpcMessageHandlerActor = agentServices.ActorSystem.ActorOf(ControllerMessageHandlerActor.Factory(rpcMessageHandlerInit), "ControllerMessageHandler");
|
||||||
|
|
||||||
rpcClientListener = rpcClient.Listen(rpcMessageHandlerActor);
|
var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1);
|
||||||
|
var rpcTask = RpcClientRuntime.Launch(rpcSocket, rpcMessageHandlerActor, rpcDisconnectSemaphore, shutdownCancellationToken);
|
||||||
if (await agentServices.Register(controllerConnection, shutdownCancellationToken)) {
|
|
||||||
PhantomLogger.Root.Information("Phantom Panel agent is ready.");
|
|
||||||
await shutdownCancellationToken.WaitHandle.WaitOneAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
await agentServices.Shutdown();
|
|
||||||
} finally {
|
|
||||||
try {
|
try {
|
||||||
await controllerConnection.Send(new UnregisterAgentMessage(), CancellationToken.None);
|
await rpcTask.WaitAsync(shutdownCancellationToken);
|
||||||
// TODO wait for acknowledgment
|
|
||||||
} catch (Exception e) {
|
|
||||||
PhantomLogger.Root.Warning(e, "Could not unregister agent after shutdown.");
|
|
||||||
} finally {
|
} finally {
|
||||||
await rpcClient.Shutdown();
|
shutdownCancellationTokenSource.Cancel();
|
||||||
|
await agentServices.Shutdown();
|
||||||
|
|
||||||
if (rpcClientListener != null) {
|
rpcDisconnectSemaphore.Release();
|
||||||
await rpcClientListener;
|
await rpcTask;
|
||||||
}
|
rpcDisconnectSemaphore.Dispose();
|
||||||
}
|
|
||||||
|
NetMQConfig.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -8,10 +8,9 @@ public sealed partial record Instance(
|
|||||||
[property: MemoryPackOrder(0)] Guid InstanceGuid,
|
[property: MemoryPackOrder(0)] Guid InstanceGuid,
|
||||||
[property: MemoryPackOrder(1)] InstanceConfiguration Configuration,
|
[property: MemoryPackOrder(1)] InstanceConfiguration Configuration,
|
||||||
[property: MemoryPackOrder(2)] IInstanceStatus Status,
|
[property: MemoryPackOrder(2)] IInstanceStatus Status,
|
||||||
[property: MemoryPackOrder(3)] InstancePlayerCounts? PlayerCounts,
|
[property: MemoryPackOrder(3)] bool LaunchAutomatically
|
||||||
[property: MemoryPackOrder(4)] bool LaunchAutomatically
|
|
||||||
) {
|
) {
|
||||||
public static Instance Offline(Guid instanceGuid, InstanceConfiguration configuration, bool launchAutomatically = false) {
|
public static Instance Offline(Guid instanceGuid, InstanceConfiguration configuration, bool launchAutomatically = false) {
|
||||||
return new Instance(instanceGuid, configuration, InstanceStatus.Offline, PlayerCounts: null, launchAutomatically);
|
return new Instance(instanceGuid, configuration, InstanceStatus.Offline, launchAutomatically);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
namespace Phantom.Common.Data.Web.Users;
|
||||||
|
|
||||||
@@ -7,18 +6,5 @@ namespace Phantom.Common.Data.Web.Users;
|
|||||||
public sealed partial record AuthenticatedUserInfo(
|
public sealed partial record AuthenticatedUserInfo(
|
||||||
[property: MemoryPackOrder(0)] Guid Guid,
|
[property: MemoryPackOrder(0)] Guid Guid,
|
||||||
[property: MemoryPackOrder(1)] string Name,
|
[property: MemoryPackOrder(1)] string Name,
|
||||||
[property: MemoryPackOrder(2)] PermissionSet Permissions,
|
[property: MemoryPackOrder(2)] PermissionSet Permissions
|
||||||
[property: MemoryPackOrder(3)] ImmutableHashSet<Guid> ManagedAgentGuids
|
);
|
||||||
) {
|
|
||||||
public bool CheckPermission(Permission permission) {
|
|
||||||
return Permissions.Check(permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasAccessToAgent(Guid agentGuid) {
|
|
||||||
return ManagedAgentGuids.Contains(agentGuid) || Permissions.Check(Permission.ManageAllAgents);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableHashSet<Guid> FilterAccessibleAgentGuids(ImmutableHashSet<Guid> agentGuids) {
|
|
||||||
return Permissions.Check(Permission.ManageAllAgents) ? agentGuids : agentGuids.Intersect(ManagedAgentGuids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -6,5 +6,5 @@ namespace Phantom.Common.Data.Web.Users;
|
|||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record LogInSuccess(
|
public sealed partial record LogInSuccess(
|
||||||
[property: MemoryPackOrder(0)] AuthenticatedUserInfo UserInfo,
|
[property: MemoryPackOrder(0)] AuthenticatedUserInfo UserInfo,
|
||||||
[property: MemoryPackOrder(1)] ImmutableArray<byte> AuthToken
|
[property: MemoryPackOrder(1)] ImmutableArray<byte> Token
|
||||||
);
|
);
|
||||||
|
@@ -14,9 +14,6 @@ public sealed record Permission(string Id, Permission? Parent) {
|
|||||||
return Register(id, this);
|
return Register(id, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string ManageAllAgentsPolicy = "Agents.ManageAll";
|
|
||||||
public static readonly Permission ManageAllAgents = Register(ManageAllAgentsPolicy);
|
|
||||||
|
|
||||||
public const string ViewInstancesPolicy = "Instances.View";
|
public const string ViewInstancesPolicy = "Instances.View";
|
||||||
public static readonly Permission ViewInstances = Register(ViewInstancesPolicy);
|
public static readonly Permission ViewInstances = Register(ViewInstancesPolicy);
|
||||||
|
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
namespace Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
public enum UserActionFailure {
|
|
||||||
NotAuthorized
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data.Replies;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
[MemoryPackable]
|
|
||||||
[MemoryPackUnion(0, typeof(OfUserActionFailure))]
|
|
||||||
[MemoryPackUnion(1, typeof(OfInstanceActionFailure))]
|
|
||||||
public abstract partial record UserInstanceActionFailure {
|
|
||||||
internal UserInstanceActionFailure() {}
|
|
||||||
|
|
||||||
public static implicit operator UserInstanceActionFailure(UserActionFailure failure) {
|
|
||||||
return new OfUserActionFailure(failure);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator UserInstanceActionFailure(InstanceActionFailure failure) {
|
|
||||||
return new OfInstanceActionFailure(failure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record OfUserActionFailure([property: MemoryPackOrder(0)] UserActionFailure Failure) : UserInstanceActionFailure;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record OfInstanceActionFailure([property: MemoryPackOrder(0)] InstanceActionFailure Failure) : UserInstanceActionFailure;
|
|
37
Common/Phantom.Common.Data/AuthToken.cs
Normal file
37
Common/Phantom.Common.Data/AuthToken.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using MemoryPack;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Data;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||||
|
public sealed partial class AuthToken {
|
||||||
|
internal const int Length = 12;
|
||||||
|
|
||||||
|
[MemoryPackOrder(0)]
|
||||||
|
[MemoryPackInclude]
|
||||||
|
private readonly byte[] bytes;
|
||||||
|
|
||||||
|
internal AuthToken(byte[]? bytes) {
|
||||||
|
ArgumentNullException.ThrowIfNull(bytes);
|
||||||
|
|
||||||
|
if (bytes.Length != Length) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bytes), "Invalid token length: " + bytes.Length + ". Token length must be exactly " + Length + " bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bytes = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FixedTimeEquals(AuthToken providedAuthToken) {
|
||||||
|
return CryptographicOperations.FixedTimeEquals(bytes, providedAuthToken.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WriteTo(Span<byte> span) {
|
||||||
|
bytes.CopyTo(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,21 +0,0 @@
|
|||||||
using Phantom.Utils.Rpc;
|
|
||||||
using Phantom.Utils.Rpc.Runtime.Tls;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data;
|
|
||||||
|
|
||||||
public readonly record struct ConnectionKey(RpcCertificateThumbprint CertificateThumbprint, AuthToken AuthToken) {
|
|
||||||
private const byte TokenLength = AuthToken.Length;
|
|
||||||
|
|
||||||
public byte[] ToBytes() {
|
|
||||||
Span<byte> result = stackalloc byte[TokenLength + CertificateThumbprint.Bytes.Length];
|
|
||||||
AuthToken.Bytes.CopyTo(result[..TokenLength]);
|
|
||||||
CertificateThumbprint.Bytes.CopyTo(result[TokenLength..]);
|
|
||||||
return result.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ConnectionKey FromBytes(ReadOnlySpan<byte> data) {
|
|
||||||
var authToken = new AuthToken([..data[..TokenLength]]);
|
|
||||||
var certificateThumbprint = RpcCertificateThumbprint.From(data[TokenLength..]);
|
|
||||||
return new ConnectionKey(certificateThumbprint, authToken);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Instance;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public readonly partial record struct InstancePlayerCounts(
|
|
||||||
[property: MemoryPackOrder(0)] int Online,
|
|
||||||
[property: MemoryPackOrder(1)] int Maximum
|
|
||||||
);
|
|
@@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
public enum MinecraftServerKind : ushort {
|
public enum MinecraftServerKind : ushort {
|
||||||
Vanilla = 1,
|
Vanilla = 1,
|
||||||
Fabric = 2
|
Fabric = 2,
|
||||||
|
Forge = 3
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Utils\Phantom.Utils\Phantom.Utils.csproj" />
|
<ProjectReference Include="..\..\Utils\Phantom.Utils\Phantom.Utils.csproj" />
|
||||||
<ProjectReference Include="..\..\Utils\Phantom.Utils.Rpc\Phantom.Utils.Rpc.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Phantom.Common.Data.Replies;
|
||||||
|
|
||||||
|
public enum RegisterAgentFailure : byte {
|
||||||
|
ConnectionAlreadyHasAnAgent,
|
||||||
|
InvalidToken
|
||||||
|
}
|
@@ -1,7 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
using Phantom.Utils.Monads;
|
|
||||||
using Phantom.Utils.Result;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data;
|
namespace Phantom.Common.Data;
|
||||||
|
|
||||||
@@ -25,9 +23,6 @@ public sealed partial class Result<TValue, TError> {
|
|||||||
[MemoryPackIgnore]
|
[MemoryPackIgnore]
|
||||||
public TError Error => !hasValue ? error! : throw new InvalidOperationException("Attempted to get error from a success result.");
|
public TError Error => !hasValue ? error! : throw new InvalidOperationException("Attempted to get error from a success result.");
|
||||||
|
|
||||||
[MemoryPackIgnore]
|
|
||||||
public Either<TValue, TError> AsEither => hasValue ? Either.Left(value!) : Either.Right(error!);
|
|
||||||
|
|
||||||
private Result(bool hasValue, TValue? value, TError? error) {
|
private Result(bool hasValue, TValue? value, TError? error) {
|
||||||
this.hasValue = hasValue;
|
this.hasValue = hasValue;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -38,18 +33,10 @@ public sealed partial class Result<TValue, TError> {
|
|||||||
return hasValue && EqualityComparer<TValue>.Default.Equals(value, expectedValue);
|
return hasValue && EqualityComparer<TValue>.Default.Equals(value, expectedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TOutput Into<TOutput>(Func<TValue, TOutput> valueConverter, Func<TError, TOutput> errorConverter) {
|
public TOutput Map<TOutput>(Func<TValue, TOutput> valueConverter, Func<TError, TOutput> errorConverter) {
|
||||||
return hasValue ? valueConverter(value!) : errorConverter(error!);
|
return hasValue ? valueConverter(value!) : errorConverter(error!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<TValue, TNewError> MapError<TNewError>(Func<TError, TNewError> errorConverter) {
|
|
||||||
return hasValue ? value! : errorConverter(error!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Utils.Result.Result Variant() {
|
|
||||||
return hasValue ? new Ok<TValue>(Value) : new Err<TError>(Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Result<TValue, TError>(TValue value) {
|
public static implicit operator Result<TValue, TError>(TValue value) {
|
||||||
return new Result<TValue, TError>(hasValue: true, value, default);
|
return new Result<TValue, TError>(hasValue: true, value, default);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Immutable;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
|
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
using Phantom.Common.Messages.Agent.ToAgent;
|
||||||
using Phantom.Common.Messages.Agent.ToController;
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
@@ -12,15 +12,18 @@ public static class AgentMessageRegistries {
|
|||||||
public static MessageRegistry<IMessageToAgent> ToAgent { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToAgent)));
|
public static MessageRegistry<IMessageToAgent> ToAgent { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToAgent)));
|
||||||
public static MessageRegistry<IMessageToController> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
|
public static MessageRegistry<IMessageToController> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
|
||||||
|
|
||||||
public static IMessageDefinitions<IMessageToController, IMessageToAgent> Definitions { get; } = new MessageDefinitions();
|
public static IMessageDefinitions<IMessageToAgent, IMessageToController, ReplyMessage> Definitions { get; } = new MessageDefinitions();
|
||||||
|
|
||||||
static AgentMessageRegistries() {
|
static AgentMessageRegistries() {
|
||||||
|
ToAgent.Add<RegisterAgentSuccessMessage>(0);
|
||||||
|
ToAgent.Add<RegisterAgentFailureMessage>(1);
|
||||||
ToAgent.Add<ConfigureInstanceMessage, Result<ConfigureInstanceResult, InstanceActionFailure>>(2);
|
ToAgent.Add<ConfigureInstanceMessage, Result<ConfigureInstanceResult, InstanceActionFailure>>(2);
|
||||||
ToAgent.Add<LaunchInstanceMessage, Result<LaunchInstanceResult, InstanceActionFailure>>(3);
|
ToAgent.Add<LaunchInstanceMessage, Result<LaunchInstanceResult, InstanceActionFailure>>(3);
|
||||||
ToAgent.Add<StopInstanceMessage, Result<StopInstanceResult, InstanceActionFailure>>(4);
|
ToAgent.Add<StopInstanceMessage, Result<StopInstanceResult, InstanceActionFailure>>(4);
|
||||||
ToAgent.Add<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, InstanceActionFailure>>(5);
|
ToAgent.Add<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, InstanceActionFailure>>(5);
|
||||||
|
ToAgent.Add<ReplyMessage>(127);
|
||||||
|
|
||||||
ToController.Add<RegisterAgentMessage, ImmutableArray<ConfigureInstanceMessage>>(0);
|
ToController.Add<RegisterAgentMessage>(0);
|
||||||
ToController.Add<UnregisterAgentMessage>(1);
|
ToController.Add<UnregisterAgentMessage>(1);
|
||||||
ToController.Add<AgentIsAliveMessage>(2);
|
ToController.Add<AgentIsAliveMessage>(2);
|
||||||
ToController.Add<AdvertiseJavaRuntimesMessage>(3);
|
ToController.Add<AdvertiseJavaRuntimesMessage>(3);
|
||||||
@@ -28,11 +31,15 @@ public static class AgentMessageRegistries {
|
|||||||
ToController.Add<InstanceOutputMessage>(5);
|
ToController.Add<InstanceOutputMessage>(5);
|
||||||
ToController.Add<ReportAgentStatusMessage>(6);
|
ToController.Add<ReportAgentStatusMessage>(6);
|
||||||
ToController.Add<ReportInstanceEventMessage>(7);
|
ToController.Add<ReportInstanceEventMessage>(7);
|
||||||
ToController.Add<ReportInstancePlayerCountsMessage>(8);
|
ToController.Add<ReplyMessage>(127);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToController, IMessageToAgent> {
|
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgent, IMessageToController, ReplyMessage> {
|
||||||
public MessageRegistry<IMessageToAgent> ToClient => ToAgent;
|
public MessageRegistry<IMessageToAgent> ToClient => ToAgent;
|
||||||
public MessageRegistry<IMessageToController> ToServer => ToController;
|
public MessageRegistry<IMessageToController> ToServer => ToController;
|
||||||
|
|
||||||
|
public ReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply) {
|
||||||
|
return new ReplyMessage(sequenceId, serializedReply);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Utils.Rpc.Message;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Agent.BiDirectional;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record ReplyMessage(
|
||||||
|
[property: MemoryPackOrder(0)] uint SequenceId,
|
||||||
|
[property: MemoryPackOrder(1)] byte[] SerializedReply
|
||||||
|
) : IMessageToController, IMessageToAgent, IReply;
|
@@ -1,10 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Agent.Handshake;
|
|
||||||
|
|
||||||
[MemoryPackable]
|
|
||||||
[MemoryPackUnion(tag: 0, typeof(InvalidAuthToken))]
|
|
||||||
public partial interface IAgentHandshakeResult;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record InvalidAuthToken : IAgentHandshakeResult;
|
|
@@ -0,0 +1,9 @@
|
|||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Common.Data.Replies;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Agent.ToAgent;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record RegisterAgentFailureMessage(
|
||||||
|
[property: MemoryPackOrder(0)] RegisterAgentFailure FailureKind
|
||||||
|
) : IMessageToAgent;
|
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using MemoryPack;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Agent.ToAgent;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record RegisterAgentSuccessMessage(
|
||||||
|
[property: MemoryPackOrder(0)] ImmutableArray<ConfigureInstanceMessage> InitialInstanceConfigurations
|
||||||
|
) : IMessageToAgent;
|
@@ -1,12 +1,11 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Agent;
|
using Phantom.Common.Data.Agent;
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
|
||||||
using Phantom.Utils.Actor;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Agent.ToController;
|
namespace Phantom.Common.Messages.Agent.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record RegisterAgentMessage(
|
public sealed partial record RegisterAgentMessage(
|
||||||
[property: MemoryPackOrder(0)] AgentInfo AgentInfo
|
[property: MemoryPackOrder(0)] AuthToken AuthToken,
|
||||||
) : IMessageToController, ICanReply<ImmutableArray<ConfigureInstanceMessage>>;
|
[property: MemoryPackOrder(1)] AgentInfo AgentInfo
|
||||||
|
) : IMessageToController;
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data.Instance;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Agent.ToController;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record ReportInstancePlayerCountsMessage(
|
|
||||||
[property: MemoryPackOrder(0)] Guid InstanceGuid,
|
|
||||||
[property: MemoryPackOrder(1)] InstancePlayerCounts? PlayerCounts
|
|
||||||
) : IMessageToController;
|
|
@@ -0,0 +1,10 @@
|
|||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Utils.Rpc.Message;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Web.BiDirectional;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record ReplyMessage(
|
||||||
|
[property: MemoryPackOrder(0)] uint SequenceId,
|
||||||
|
[property: MemoryPackOrder(1)] byte[] SerializedReply
|
||||||
|
) : IMessageToController, IMessageToWeb, IReply;
|
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
@@ -8,8 +7,8 @@ namespace Phantom.Common.Messages.Web.ToController;
|
|||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record ChangeUserRolesMessage(
|
public sealed partial record ChangeUserRolesMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] Guid SubjectUserGuid,
|
[property: MemoryPackOrder(1)] Guid SubjectUserGuid,
|
||||||
[property: MemoryPackOrder(2)] ImmutableHashSet<Guid> AddToRoleGuids,
|
[property: MemoryPackOrder(2)] ImmutableHashSet<Guid> AddToRoleGuids,
|
||||||
[property: MemoryPackOrder(3)] ImmutableHashSet<Guid> RemoveFromRoleGuids
|
[property: MemoryPackOrder(3)] ImmutableHashSet<Guid> RemoveFromRoleGuids
|
||||||
) : IMessageToController, ICanReply<Result<ChangeUserRolesResult, UserActionFailure>>;
|
) : IMessageToController, ICanReply<ChangeUserRolesResult>;
|
||||||
|
@@ -1,16 +1,15 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Instance;
|
using Phantom.Common.Data.Instance;
|
||||||
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record CreateOrUpdateInstanceMessage(
|
public sealed partial record CreateOrUpdateInstanceMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] Guid InstanceGuid,
|
[property: MemoryPackOrder(1)] Guid InstanceGuid,
|
||||||
[property: MemoryPackOrder(2)] InstanceConfiguration Configuration
|
[property: MemoryPackOrder(2)] InstanceConfiguration Configuration
|
||||||
) : IMessageToController, ICanReply<Result<CreateOrUpdateInstanceResult, UserInstanceActionFailure>>;
|
) : IMessageToController, ICanReply<Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>;
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
@@ -8,7 +6,7 @@ namespace Phantom.Common.Messages.Web.ToController;
|
|||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record CreateUserMessage(
|
public sealed partial record CreateUserMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] string Username,
|
[property: MemoryPackOrder(1)] string Username,
|
||||||
[property: MemoryPackOrder(2)] string Password
|
[property: MemoryPackOrder(2)] string Password
|
||||||
) : IMessageToController, ICanReply<Result<CreateUserResult, UserActionFailure>>;
|
) : IMessageToController, ICanReply<CreateUserResult>;
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
@@ -8,6 +6,6 @@ namespace Phantom.Common.Messages.Web.ToController;
|
|||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record DeleteUserMessage(
|
public sealed partial record DeleteUserMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] Guid SubjectUserGuid
|
[property: MemoryPackOrder(1)] Guid SubjectUserGuid
|
||||||
) : IMessageToController, ICanReply<Result<DeleteUserResult, UserActionFailure>>;
|
) : IMessageToController, ICanReply<DeleteUserResult>;
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.AuditLog;
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record GetAuditLogMessage(
|
public sealed partial record GetAuditLogMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] int Count
|
||||||
[property: MemoryPackOrder(1)] int Count
|
) : IMessageToController, ICanReply<ImmutableArray<AuditLogItem>>;
|
||||||
) : IMessageToController, ICanReply<Result<ImmutableArray<AuditLogItem>, UserActionFailure>>;
|
|
||||||
|
@@ -9,5 +9,5 @@ namespace Phantom.Common.Messages.Web.ToController;
|
|||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record GetAuthenticatedUser(
|
public sealed partial record GetAuthenticatedUser(
|
||||||
[property: MemoryPackOrder(0)] Guid UserGuid,
|
[property: MemoryPackOrder(0)] Guid UserGuid,
|
||||||
[property: MemoryPackOrder(1)] ImmutableArray<byte> AuthToken
|
[property: MemoryPackOrder(1)] ImmutableArray<byte> SessionToken
|
||||||
) : IMessageToController, ICanReply<Optional<AuthenticatedUserInfo>>;
|
) : IMessageToController, ICanReply<Optional<AuthenticatedUserInfo>>;
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.EventLog;
|
using Phantom.Common.Data.Web.EventLog;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record GetEventLogMessage(
|
public sealed partial record GetEventLogMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] int Count
|
||||||
[property: MemoryPackOrder(1)] int Count
|
) : IMessageToController, ICanReply<ImmutableArray<EventLogItem>>;
|
||||||
) : IMessageToController, ICanReply<Result<ImmutableArray<EventLogItem>, UserActionFailure>>;
|
|
||||||
|
@@ -1,15 +1,13 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record LaunchInstanceMessage(
|
public sealed partial record LaunchInstanceMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] Guid AgentGuid,
|
[property: MemoryPackOrder(1)] Guid AgentGuid,
|
||||||
[property: MemoryPackOrder(2)] Guid InstanceGuid
|
[property: MemoryPackOrder(2)] Guid InstanceGuid
|
||||||
) : IMessageToController, ICanReply<Result<LaunchInstanceResult, UserInstanceActionFailure>>;
|
) : IMessageToController, ICanReply<Result<LaunchInstanceResult, InstanceActionFailure>>;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
@@ -9,4 +8,4 @@ namespace Phantom.Common.Messages.Web.ToController;
|
|||||||
public sealed partial record LogInMessage(
|
public sealed partial record LogInMessage(
|
||||||
[property: MemoryPackOrder(0)] string Username,
|
[property: MemoryPackOrder(0)] string Username,
|
||||||
[property: MemoryPackOrder(1)] string Password
|
[property: MemoryPackOrder(1)] string Password
|
||||||
) : IMessageToController, ICanReply<Optional<LogInSuccess>>;
|
) : IMessageToController, ICanReply<LogInSuccess?>;
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Common.Data;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record RegisterWebMessage(
|
||||||
|
[property: MemoryPackOrder(0)] AuthToken AuthToken
|
||||||
|
) : IMessageToController;
|
@@ -1,16 +1,14 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record SendCommandToInstanceMessage(
|
public sealed partial record SendCommandToInstanceMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] Guid AgentGuid,
|
[property: MemoryPackOrder(1)] Guid AgentGuid,
|
||||||
[property: MemoryPackOrder(2)] Guid InstanceGuid,
|
[property: MemoryPackOrder(2)] Guid InstanceGuid,
|
||||||
[property: MemoryPackOrder(3)] string Command
|
[property: MemoryPackOrder(3)] string Command
|
||||||
) : IMessageToController, ICanReply<Result<SendCommandToInstanceResult, UserInstanceActionFailure>>;
|
) : IMessageToController, ICanReply<Result<SendCommandToInstanceResult, InstanceActionFailure>>;
|
||||||
|
@@ -1,17 +1,15 @@
|
|||||||
using System.Collections.Immutable;
|
using MemoryPack;
|
||||||
using MemoryPack;
|
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Minecraft;
|
using Phantom.Common.Data.Minecraft;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record StopInstanceMessage(
|
public sealed partial record StopInstanceMessage(
|
||||||
[property: MemoryPackOrder(0)] ImmutableArray<byte> AuthToken,
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
[property: MemoryPackOrder(1)] Guid AgentGuid,
|
[property: MemoryPackOrder(1)] Guid AgentGuid,
|
||||||
[property: MemoryPackOrder(2)] Guid InstanceGuid,
|
[property: MemoryPackOrder(2)] Guid InstanceGuid,
|
||||||
[property: MemoryPackOrder(3)] MinecraftStopStrategy StopStrategy
|
[property: MemoryPackOrder(3)] MinecraftStopStrategy StopStrategy
|
||||||
) : IMessageToController, ICanReply<Result<StopInstanceResult, UserInstanceActionFailure>>;
|
) : IMessageToController, ICanReply<Result<StopInstanceResult, InstanceActionFailure>>;
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToWeb;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record RefreshUserSessionMessage(
|
|
||||||
[property: MemoryPackOrder(0)] Guid UserGuid
|
|
||||||
) : IMessageToWeb;
|
|
@@ -7,6 +7,7 @@ using Phantom.Common.Data.Web.AuditLog;
|
|||||||
using Phantom.Common.Data.Web.EventLog;
|
using Phantom.Common.Data.Web.EventLog;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
|
using Phantom.Common.Messages.Web.BiDirectional;
|
||||||
using Phantom.Common.Messages.Web.ToController;
|
using Phantom.Common.Messages.Web.ToController;
|
||||||
using Phantom.Common.Messages.Web.ToWeb;
|
using Phantom.Common.Messages.Web.ToWeb;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
@@ -18,37 +19,44 @@ public static class WebMessageRegistries {
|
|||||||
public static MessageRegistry<IMessageToController> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
|
public static MessageRegistry<IMessageToController> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
|
||||||
public static MessageRegistry<IMessageToWeb> ToWeb { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToWeb)));
|
public static MessageRegistry<IMessageToWeb> ToWeb { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToWeb)));
|
||||||
|
|
||||||
public static IMessageDefinitions<IMessageToController, IMessageToWeb> Definitions { get; } = new MessageDefinitions();
|
public static IMessageDefinitions<IMessageToWeb, IMessageToController, ReplyMessage> Definitions { get; } = new MessageDefinitions();
|
||||||
|
|
||||||
static WebMessageRegistries() {
|
static WebMessageRegistries() {
|
||||||
|
ToController.Add<RegisterWebMessage>(0);
|
||||||
ToController.Add<UnregisterWebMessage>(1);
|
ToController.Add<UnregisterWebMessage>(1);
|
||||||
ToController.Add<LogInMessage, Optional<LogInSuccess>>(2);
|
ToController.Add<LogInMessage, LogInSuccess?>(2);
|
||||||
ToController.Add<LogOutMessage>(3);
|
ToController.Add<LogOutMessage>(3);
|
||||||
ToController.Add<GetAuthenticatedUser, Optional<AuthenticatedUserInfo>>(4);
|
ToController.Add<GetAuthenticatedUser, Optional<AuthenticatedUserInfo>>(4);
|
||||||
ToController.Add<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(5);
|
ToController.Add<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(5);
|
||||||
ToController.Add<CreateUserMessage, Result<CreateUserResult, UserActionFailure>>(6);
|
ToController.Add<CreateUserMessage, CreateUserResult>(6);
|
||||||
ToController.Add<DeleteUserMessage, Result<DeleteUserResult, UserActionFailure>>(7);
|
ToController.Add<DeleteUserMessage, DeleteUserResult>(7);
|
||||||
ToController.Add<GetUsersMessage, ImmutableArray<UserInfo>>(8);
|
ToController.Add<GetUsersMessage, ImmutableArray<UserInfo>>(8);
|
||||||
ToController.Add<GetRolesMessage, ImmutableArray<RoleInfo>>(9);
|
ToController.Add<GetRolesMessage, ImmutableArray<RoleInfo>>(9);
|
||||||
ToController.Add<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(10);
|
ToController.Add<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(10);
|
||||||
ToController.Add<ChangeUserRolesMessage, Result<ChangeUserRolesResult, UserActionFailure>>(11);
|
ToController.Add<ChangeUserRolesMessage, ChangeUserRolesResult>(11);
|
||||||
ToController.Add<CreateOrUpdateInstanceMessage, Result<CreateOrUpdateInstanceResult, UserInstanceActionFailure>>(12);
|
ToController.Add<CreateOrUpdateInstanceMessage, Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>(12);
|
||||||
ToController.Add<LaunchInstanceMessage, Result<LaunchInstanceResult, UserInstanceActionFailure>>(13);
|
ToController.Add<LaunchInstanceMessage, Result<LaunchInstanceResult, InstanceActionFailure>>(13);
|
||||||
ToController.Add<StopInstanceMessage, Result<StopInstanceResult, UserInstanceActionFailure>>(14);
|
ToController.Add<StopInstanceMessage, Result<StopInstanceResult, InstanceActionFailure>>(14);
|
||||||
ToController.Add<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, UserInstanceActionFailure>>(15);
|
ToController.Add<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, InstanceActionFailure>>(15);
|
||||||
ToController.Add<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(16);
|
ToController.Add<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(16);
|
||||||
ToController.Add<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(17);
|
ToController.Add<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(17);
|
||||||
ToController.Add<GetAuditLogMessage, Result<ImmutableArray<AuditLogItem>, UserActionFailure>>(18);
|
ToController.Add<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(18);
|
||||||
ToController.Add<GetEventLogMessage, Result<ImmutableArray<EventLogItem>, UserActionFailure>>(19);
|
ToController.Add<GetEventLogMessage, ImmutableArray<EventLogItem>>(19);
|
||||||
|
ToController.Add<ReplyMessage>(127);
|
||||||
|
|
||||||
|
ToWeb.Add<RegisterWebResultMessage>(0);
|
||||||
ToWeb.Add<RefreshAgentsMessage>(1);
|
ToWeb.Add<RefreshAgentsMessage>(1);
|
||||||
ToWeb.Add<RefreshInstancesMessage>(2);
|
ToWeb.Add<RefreshInstancesMessage>(2);
|
||||||
ToWeb.Add<InstanceOutputMessage>(3);
|
ToWeb.Add<InstanceOutputMessage>(3);
|
||||||
ToWeb.Add<RefreshUserSessionMessage>(4);
|
ToWeb.Add<ReplyMessage>(127);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToController, IMessageToWeb> {
|
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWeb, IMessageToController, ReplyMessage> {
|
||||||
public MessageRegistry<IMessageToWeb> ToClient => ToWeb;
|
public MessageRegistry<IMessageToWeb> ToClient => ToWeb;
|
||||||
public MessageRegistry<IMessageToController> ToServer => ToController;
|
public MessageRegistry<IMessageToController> ToServer => ToController;
|
||||||
|
|
||||||
|
public ReplyMessage CreateReplyMessage(uint sequenceId, byte[] serializedReply) {
|
||||||
|
return new ReplyMessage(sequenceId, serializedReply);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,353 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
using Phantom.Controller.Database;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Database.Postgres.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
|
||||||
[Migration("20240407211636_UserAgentAccess")]
|
|
||||||
partial class UserAgentAccess
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "8.0.0")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.AgentEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("AgentGuid")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("BuildVersion")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("MaxInstances")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<ushort>("MaxMemory")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("ProtocolVersion")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("AgentGuid");
|
|
||||||
|
|
||||||
b.ToTable("Agents", "agents");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.AuditLogEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<JsonDocument>("Data")
|
|
||||||
.HasColumnType("jsonb");
|
|
||||||
|
|
||||||
b.Property<string>("EventType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("SubjectId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("SubjectType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<Guid?>("UserGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<DateTime>("UtcTime")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("UserGuid");
|
|
||||||
|
|
||||||
b.ToTable("AuditLog", "system");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.EventLogEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("EventGuid")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<Guid?>("AgentGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<JsonDocument>("Data")
|
|
||||||
.HasColumnType("jsonb");
|
|
||||||
|
|
||||||
b.Property<string>("EventType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("SubjectId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("SubjectType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<DateTime>("UtcTime")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.HasKey("EventGuid");
|
|
||||||
|
|
||||||
b.ToTable("EventLog", "system");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.InstanceEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("InstanceGuid")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<Guid>("AgentGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("InstanceName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<Guid>("JavaRuntimeGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("JvmArguments")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<bool>("LaunchAutomatically")
|
|
||||||
.HasColumnType("boolean");
|
|
||||||
|
|
||||||
b.Property<ushort>("MemoryAllocation")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<string>("MinecraftServerKind")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("MinecraftVersion")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<int>("RconPort")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.Property<int>("ServerPort")
|
|
||||||
.HasColumnType("integer");
|
|
||||||
|
|
||||||
b.HasKey("InstanceGuid");
|
|
||||||
|
|
||||||
b.ToTable("Instances", "agents");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.PermissionEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Permissions", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.RoleEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("RoleGuid")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("RoleGuid");
|
|
||||||
|
|
||||||
b.ToTable("Roles", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.RolePermissionEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("RoleGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("PermissionId")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("RoleGuid", "PermissionId");
|
|
||||||
|
|
||||||
b.HasIndex("PermissionId");
|
|
||||||
|
|
||||||
b.ToTable("RolePermissions", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserAgentAccessEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("UserGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<Guid>("AgentGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.HasKey("UserGuid", "AgentGuid");
|
|
||||||
|
|
||||||
b.HasIndex("AgentGuid");
|
|
||||||
|
|
||||||
b.ToTable("UserAgentAccess", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("UserGuid")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("PasswordHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("UserGuid");
|
|
||||||
|
|
||||||
b.HasIndex("Name")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("Users", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserPermissionEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("UserGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<string>("PermissionId")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("UserGuid", "PermissionId");
|
|
||||||
|
|
||||||
b.HasIndex("PermissionId");
|
|
||||||
|
|
||||||
b.ToTable("UserPermissions", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserRoleEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("UserGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<Guid>("RoleGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.HasKey("UserGuid", "RoleGuid");
|
|
||||||
|
|
||||||
b.HasIndex("RoleGuid");
|
|
||||||
|
|
||||||
b.ToTable("UserRoles", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.AuditLogEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.UserEntity", "User")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserGuid")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.RolePermissionEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.PermissionEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("PermissionId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.RoleEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("RoleGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserAgentAccessEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.AgentEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AgentGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.UserEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserPermissionEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.PermissionEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("PermissionId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.UserEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserRoleEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.RoleEntity", "Role")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("RoleGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.UserEntity", "User")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Role");
|
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Database.Postgres.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class UserAgentAccess : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "UserAgentAccess",
|
|
||||||
schema: "identity",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
UserGuid = table.Column<Guid>(type: "uuid", nullable: false),
|
|
||||||
AgentGuid = table.Column<Guid>(type: "uuid", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_UserAgentAccess", x => new { x.UserGuid, x.AgentGuid });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_UserAgentAccess_Agents_AgentGuid",
|
|
||||||
column: x => x.AgentGuid,
|
|
||||||
principalSchema: "agents",
|
|
||||||
principalTable: "Agents",
|
|
||||||
principalColumn: "AgentGuid",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_UserAgentAccess_Users_UserGuid",
|
|
||||||
column: x => x.UserGuid,
|
|
||||||
principalSchema: "identity",
|
|
||||||
principalTable: "Users",
|
|
||||||
principalColumn: "UserGuid",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_UserAgentAccess_AgentGuid",
|
|
||||||
schema: "identity",
|
|
||||||
table: "UserAgentAccess",
|
|
||||||
column: "AgentGuid");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "UserAgentAccess",
|
|
||||||
schema: "identity");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -18,7 +18,7 @@ namespace Phantom.Controller.Database.Postgres.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "8.0.0")
|
.HasAnnotation("ProductVersion", "7.0.11")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -204,21 +204,6 @@ namespace Phantom.Controller.Database.Postgres.Migrations
|
|||||||
b.ToTable("RolePermissions", "identity");
|
b.ToTable("RolePermissions", "identity");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserAgentAccessEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("UserGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.Property<Guid>("AgentGuid")
|
|
||||||
.HasColumnType("uuid");
|
|
||||||
|
|
||||||
b.HasKey("UserGuid", "AgentGuid");
|
|
||||||
|
|
||||||
b.HasIndex("AgentGuid");
|
|
||||||
|
|
||||||
b.ToTable("UserAgentAccess", "identity");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserEntity", b =>
|
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("UserGuid")
|
b.Property<Guid>("UserGuid")
|
||||||
@@ -296,21 +281,6 @@ namespace Phantom.Controller.Database.Postgres.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserAgentAccessEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.AgentEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AgentGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.UserEntity", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserGuid")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserPermissionEntity", b =>
|
modelBuilder.Entity("Phantom.Controller.Database.Entities.UserPermissionEntity", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Phantom.Controller.Database.Entities.PermissionEntity", null)
|
b.HasOne("Phantom.Controller.Database.Entities.PermissionEntity", null)
|
||||||
|
@@ -20,7 +20,6 @@ public class ApplicationDbContext : DbContext {
|
|||||||
public DbSet<UserRoleEntity> UserRoles { get; init; } = null!;
|
public DbSet<UserRoleEntity> UserRoles { get; init; } = null!;
|
||||||
public DbSet<UserPermissionEntity> UserPermissions { get; init; } = null!;
|
public DbSet<UserPermissionEntity> UserPermissions { get; init; } = null!;
|
||||||
public DbSet<RolePermissionEntity> RolePermissions { get; init; } = null!;
|
public DbSet<RolePermissionEntity> RolePermissions { get; init; } = null!;
|
||||||
public DbSet<UserAgentAccessEntity> UserAgentAccess { get; init; } = null!;
|
|
||||||
|
|
||||||
public DbSet<AgentEntity> Agents { get; init; } = null!;
|
public DbSet<AgentEntity> Agents { get; init; } = null!;
|
||||||
public DbSet<InstanceEntity> Instances { get; init; } = null!;
|
public DbSet<InstanceEntity> Instances { get; init; } = null!;
|
||||||
@@ -63,12 +62,6 @@ public class ApplicationDbContext : DbContext {
|
|||||||
b.HasOne<RoleEntity>().WithMany().HasForeignKey(static e => e.RoleGuid).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
b.HasOne<RoleEntity>().WithMany().HasForeignKey(static e => e.RoleGuid).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||||
b.HasOne<PermissionEntity>().WithMany().HasForeignKey(static e => e.PermissionId).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
b.HasOne<PermissionEntity>().WithMany().HasForeignKey(static e => e.PermissionId).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<UserAgentAccessEntity>(static b => {
|
|
||||||
b.HasKey(static e => new { UserId = e.UserGuid, AgentId = e.AgentGuid });
|
|
||||||
b.HasOne<UserEntity>().WithMany().HasForeignKey(static e => e.UserGuid).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
|
||||||
b.HasOne<AgentEntity>().WithMany().HasForeignKey(static e => e.AgentGuid).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ConfigureConventions(ModelConfigurationBuilder builder) {
|
protected override void ConfigureConventions(ModelConfigurationBuilder builder) {
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Database.Entities;
|
|
||||||
|
|
||||||
[Table("UserAgentAccess", Schema = "identity")]
|
|
||||||
public sealed class UserAgentAccessEntity {
|
|
||||||
public Guid UserGuid { get; init; }
|
|
||||||
public Guid AgentGuid { get; init; }
|
|
||||||
|
|
||||||
public UserAgentAccessEntity(Guid userGuid, Guid agentGuid) {
|
|
||||||
UserGuid = userGuid;
|
|
||||||
AgentGuid = agentGuid;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -17,12 +17,11 @@ public sealed class EventLogRepository {
|
|||||||
db.Ctx.EventLog.Add(new EventLogEntity(eventGuid, utcTime, agentGuid, eventType, subjectId, extra));
|
db.Ctx.EventLog.Add(new EventLogEntity(eventGuid, utcTime, agentGuid, eventType, subjectId, extra));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ImmutableArray<EventLogItem>> GetMostRecentItems(ImmutableHashSet<Guid> agentGuids, int count, CancellationToken cancellationToken) {
|
public Task<ImmutableArray<EventLogItem>> GetMostRecentItems(int count, CancellationToken cancellationToken) {
|
||||||
return db.Ctx
|
return db.Ctx
|
||||||
.EventLog
|
.EventLog
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.OrderByDescending(static entity => entity.UtcTime)
|
.OrderByDescending(static entity => entity.UtcTime)
|
||||||
.Where(entity => entity.AgentGuid == null || agentGuids.Contains(entity.AgentGuid.Value))
|
|
||||||
.Take(count)
|
.Take(count)
|
||||||
.AsAsyncEnumerable()
|
.AsAsyncEnumerable()
|
||||||
.Select(static entity => new EventLogItem(entity.UtcTime, entity.AgentGuid, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data?.RootElement.ToString()))
|
.Select(static entity => new EventLogItem(entity.UtcTime, entity.AgentGuid, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data?.RootElement.ToString()))
|
||||||
|
@@ -1,35 +0,0 @@
|
|||||||
using System.Collections.Immutable;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Controller.Database.Entities;
|
|
||||||
using Phantom.Utils.Collections;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Database.Repositories;
|
|
||||||
|
|
||||||
public sealed class PermissionRepository {
|
|
||||||
private readonly ILazyDbContext db;
|
|
||||||
|
|
||||||
public PermissionRepository(ILazyDbContext db) {
|
|
||||||
this.db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<PermissionSet> GetAllUserPermissions(UserEntity user) {
|
|
||||||
var userPermissions = db.Ctx.UserPermissions
|
|
||||||
.Where(up => up.UserGuid == user.UserGuid)
|
|
||||||
.Select(static up => up.PermissionId);
|
|
||||||
|
|
||||||
var rolePermissions = db.Ctx.UserRoles
|
|
||||||
.Where(ur => ur.UserGuid == user.UserGuid)
|
|
||||||
.Join(db.Ctx.RolePermissions, static ur => ur.RoleGuid, static rp => rp.RoleGuid, static (ur, rp) => rp.PermissionId);
|
|
||||||
|
|
||||||
return new PermissionSet(await userPermissions.Union(rolePermissions).AsAsyncEnumerable().ToImmutableSetAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<ImmutableHashSet<Guid>> GetManagedAgentGuids(UserEntity user) {
|
|
||||||
return db.Ctx.UserAgentAccess
|
|
||||||
.Where(ua => ua.UserGuid == user.UserGuid)
|
|
||||||
.Select(static ua => ua.AgentGuid)
|
|
||||||
.AsAsyncEnumerable()
|
|
||||||
.ToImmutableSetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -21,7 +21,7 @@ using Phantom.Utils.Actor.Mailbox;
|
|||||||
using Phantom.Utils.Actor.Tasks;
|
using Phantom.Utils.Actor.Tasks;
|
||||||
using Phantom.Utils.Collections;
|
using Phantom.Utils.Collections;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Agents;
|
namespace Phantom.Controller.Services.Agents;
|
||||||
@@ -96,7 +96,6 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
Receive<UpdateJavaRuntimesCommand>(UpdateJavaRuntimes);
|
Receive<UpdateJavaRuntimesCommand>(UpdateJavaRuntimes);
|
||||||
ReceiveAndReplyLater<CreateOrUpdateInstanceCommand, Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>(CreateOrUpdateInstance);
|
ReceiveAndReplyLater<CreateOrUpdateInstanceCommand, Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>(CreateOrUpdateInstance);
|
||||||
Receive<UpdateInstanceStatusCommand>(UpdateInstanceStatus);
|
Receive<UpdateInstanceStatusCommand>(UpdateInstanceStatus);
|
||||||
Receive<UpdateInstancePlayerCountsCommand>(UpdateInstancePlayerCounts);
|
|
||||||
ReceiveAndReplyLater<LaunchInstanceCommand, Result<LaunchInstanceResult, InstanceActionFailure>>(LaunchInstance);
|
ReceiveAndReplyLater<LaunchInstanceCommand, Result<LaunchInstanceResult, InstanceActionFailure>>(LaunchInstance);
|
||||||
ReceiveAndReplyLater<StopInstanceCommand, Result<StopInstanceResult, InstanceActionFailure>>(StopInstance);
|
ReceiveAndReplyLater<StopInstanceCommand, Result<StopInstanceResult, InstanceActionFailure>>(StopInstance);
|
||||||
ReceiveAndReplyLater<SendCommandToInstanceCommand, Result<SendCommandToInstanceResult, InstanceActionFailure>>(SendMinecraftCommand);
|
ReceiveAndReplyLater<SendCommandToInstanceCommand, Result<SendCommandToInstanceResult, InstanceActionFailure>>(SendMinecraftCommand);
|
||||||
@@ -147,20 +146,20 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Result<TReply, InstanceActionFailure>> RequestInstance<TCommand, TReply>(Guid instanceGuid, TCommand command) where TCommand : InstanceActor.ICommand, ICanReply<Result<TReply, InstanceActionFailure>> {
|
private Task<Result<TReply, InstanceActionFailure>> RequestInstance<TCommand, TReply>(Guid instanceGuid, TCommand command) where TCommand : InstanceActor.ICommand, ICanReply<Result<TReply, InstanceActionFailure>> {
|
||||||
if (instanceActorByGuid.TryGetValue(instanceGuid, out var instance)) {
|
if (instanceActorByGuid.TryGetValue(instanceGuid, out var instance)) {
|
||||||
return await instance.Request(command, cancellationToken);
|
return instance.Request(command, cancellationToken);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Logger.Warning("Could not deliver command {CommandType} to instance {InstanceGuid}, instance not found.", command.GetType().Name, instanceGuid);
|
Logger.Warning("Could not deliver command {CommandType} to instance {InstanceGuid}, instance not found.", command.GetType().Name, instanceGuid);
|
||||||
return InstanceActionFailure.InstanceDoesNotExist;
|
return Task.FromResult<Result<TReply, InstanceActionFailure>>(InstanceActionFailure.InstanceDoesNotExist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ImmutableArray<ConfigureInstanceMessage>> PrepareInitialConfigurationMessages() {
|
private async Task<ImmutableArray<ConfigureInstanceMessage>> PrepareInitialConfigurationMessages() {
|
||||||
var configurationMessages = ImmutableArray.CreateBuilder<ConfigureInstanceMessage>();
|
var configurationMessages = ImmutableArray.CreateBuilder<ConfigureInstanceMessage>();
|
||||||
|
|
||||||
foreach (var (instanceGuid, instanceConfiguration, _, _, launchAutomatically) in instanceDataByGuid.Values.ToImmutableArray()) {
|
foreach (var (instanceGuid, instanceConfiguration, _, launchAutomatically) in instanceDataByGuid.Values.ToImmutableArray()) {
|
||||||
var serverExecutableInfo = await minecraftVersions.GetServerExecutableInfo(instanceConfiguration.MinecraftVersion, cancellationToken);
|
var serverExecutableInfo = await minecraftVersions.GetServerExecutableInfo(instanceConfiguration.MinecraftVersion, cancellationToken);
|
||||||
configurationMessages.Add(new ConfigureInstanceMessage(instanceGuid, instanceConfiguration, new InstanceLaunchProperties(serverExecutableInfo), launchAutomatically));
|
configurationMessages.Add(new ConfigureInstanceMessage(instanceGuid, instanceConfiguration, new InstanceLaunchProperties(serverExecutableInfo), launchAutomatically));
|
||||||
}
|
}
|
||||||
@@ -168,13 +167,13 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
return configurationMessages.ToImmutable();
|
return configurationMessages.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ICommand;
|
public interface ICommand {}
|
||||||
|
|
||||||
private sealed record InitializeCommand : ICommand;
|
private sealed record InitializeCommand : ICommand;
|
||||||
|
|
||||||
public sealed record RegisterCommand(AgentConfiguration Configuration, RpcServerToClientConnection<IMessageToController, IMessageToAgent> Connection) : ICommand, ICanReply<ImmutableArray<ConfigureInstanceMessage>>;
|
public sealed record RegisterCommand(AgentConfiguration Configuration, RpcConnectionToClient<IMessageToAgent> Connection) : ICommand, ICanReply<ImmutableArray<ConfigureInstanceMessage>>;
|
||||||
|
|
||||||
public sealed record UnregisterCommand(RpcServerToClientConnection<IMessageToController, IMessageToAgent> Connection) : ICommand;
|
public sealed record UnregisterCommand(RpcConnectionToClient<IMessageToAgent> Connection) : ICommand;
|
||||||
|
|
||||||
private sealed record RefreshConnectionStatusCommand : ICommand;
|
private sealed record RefreshConnectionStatusCommand : ICommand;
|
||||||
|
|
||||||
@@ -184,17 +183,15 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
|
|
||||||
public sealed record UpdateJavaRuntimesCommand(ImmutableArray<TaggedJavaRuntime> JavaRuntimes) : ICommand;
|
public sealed record UpdateJavaRuntimesCommand(ImmutableArray<TaggedJavaRuntime> JavaRuntimes) : ICommand;
|
||||||
|
|
||||||
public sealed record CreateOrUpdateInstanceCommand(Guid LoggedInUserGuid, Guid InstanceGuid, InstanceConfiguration Configuration) : ICommand, ICanReply<Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>;
|
public sealed record CreateOrUpdateInstanceCommand(Guid AuditLogUserGuid, Guid InstanceGuid, InstanceConfiguration Configuration) : ICommand, ICanReply<Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>;
|
||||||
|
|
||||||
public sealed record UpdateInstanceStatusCommand(Guid InstanceGuid, IInstanceStatus Status) : ICommand;
|
public sealed record UpdateInstanceStatusCommand(Guid InstanceGuid, IInstanceStatus Status) : ICommand;
|
||||||
|
|
||||||
public sealed record UpdateInstancePlayerCountsCommand(Guid InstanceGuid, InstancePlayerCounts? PlayerCounts) : ICommand;
|
public sealed record LaunchInstanceCommand(Guid InstanceGuid, Guid AuditLogUserGuid) : ICommand, ICanReply<Result<LaunchInstanceResult, InstanceActionFailure>>;
|
||||||
|
|
||||||
public sealed record LaunchInstanceCommand(Guid LoggedInUserGuid, Guid InstanceGuid) : ICommand, ICanReply<Result<LaunchInstanceResult, InstanceActionFailure>>;
|
public sealed record StopInstanceCommand(Guid InstanceGuid, Guid AuditLogUserGuid, MinecraftStopStrategy StopStrategy) : ICommand, ICanReply<Result<StopInstanceResult, InstanceActionFailure>>;
|
||||||
|
|
||||||
public sealed record StopInstanceCommand(Guid LoggedInUserGuid, Guid InstanceGuid, MinecraftStopStrategy StopStrategy) : ICommand, ICanReply<Result<StopInstanceResult, InstanceActionFailure>>;
|
public sealed record SendCommandToInstanceCommand(Guid InstanceGuid, Guid AuditLogUserGuid, string Command) : ICommand, ICanReply<Result<SendCommandToInstanceResult, InstanceActionFailure>>;
|
||||||
|
|
||||||
public sealed record SendCommandToInstanceCommand(Guid LoggedInUserGuid, Guid InstanceGuid, string Command) : ICommand, ICanReply<Result<SendCommandToInstanceResult, InstanceActionFailure>>;
|
|
||||||
|
|
||||||
public sealed record ReceiveInstanceDataCommand(Instance Instance) : ICommand, IJumpAhead;
|
public sealed record ReceiveInstanceDataCommand(Instance Instance) : ICommand, IJumpAhead;
|
||||||
|
|
||||||
@@ -311,7 +308,7 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
instanceActorRef = CreateNewInstance(Instance.Offline(command.InstanceGuid, instanceConfiguration));
|
instanceActorRef = CreateNewInstance(Instance.Offline(command.InstanceGuid, instanceConfiguration));
|
||||||
}
|
}
|
||||||
|
|
||||||
var configureInstanceCommand = new InstanceActor.ConfigureInstanceCommand(command.LoggedInUserGuid, command.InstanceGuid, instanceConfiguration, new InstanceLaunchProperties(serverExecutableInfo), isCreatingInstance);
|
var configureInstanceCommand = new InstanceActor.ConfigureInstanceCommand(command.AuditLogUserGuid, command.InstanceGuid, instanceConfiguration, new InstanceLaunchProperties(serverExecutableInfo), isCreatingInstance);
|
||||||
|
|
||||||
return instanceActorRef.Request(configureInstanceCommand, cancellationToken)
|
return instanceActorRef.Request(configureInstanceCommand, cancellationToken)
|
||||||
.ContinueOnActor(CreateOrUpdateInstance2, configureInstanceCommand);
|
.ContinueOnActor(CreateOrUpdateInstance2, configureInstanceCommand);
|
||||||
@@ -333,7 +330,7 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
else {
|
else {
|
||||||
string action = isCreating ? "adding" : "editing";
|
string action = isCreating ? "adding" : "editing";
|
||||||
string relation = isCreating ? "to agent" : "in agent";
|
string relation = isCreating ? "to agent" : "in agent";
|
||||||
string reason = result.Into(ConfigureInstanceResultExtensions.ToSentence, InstanceActionFailureExtensions.ToSentence);
|
string reason = result.Map(ConfigureInstanceResultExtensions.ToSentence, InstanceActionFailureExtensions.ToSentence);
|
||||||
|
|
||||||
Logger.Information("Failed " + action + " instance \"{InstanceName}\" (GUID {InstanceGuid}) " + relation + " \"{AgentName}\". {ErrorMessage}", instanceName, instanceGuid, configuration.AgentName, reason);
|
Logger.Information("Failed " + action + " instance \"{InstanceName}\" (GUID {InstanceGuid}) " + relation + " \"{AgentName}\". {ErrorMessage}", instanceName, instanceGuid, configuration.AgentName, reason);
|
||||||
|
|
||||||
@@ -345,20 +342,16 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
TellInstance(command.InstanceGuid, new InstanceActor.SetStatusCommand(command.Status));
|
TellInstance(command.InstanceGuid, new InstanceActor.SetStatusCommand(command.Status));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateInstancePlayerCounts(UpdateInstancePlayerCountsCommand command) {
|
|
||||||
TellInstance(command.InstanceGuid, new InstanceActor.SetPlayerCountsCommand(command.PlayerCounts));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<Result<LaunchInstanceResult, InstanceActionFailure>> LaunchInstance(LaunchInstanceCommand command) {
|
private Task<Result<LaunchInstanceResult, InstanceActionFailure>> LaunchInstance(LaunchInstanceCommand command) {
|
||||||
return RequestInstance<InstanceActor.LaunchInstanceCommand, LaunchInstanceResult>(command.InstanceGuid, new InstanceActor.LaunchInstanceCommand(command.LoggedInUserGuid));
|
return RequestInstance<InstanceActor.LaunchInstanceCommand, LaunchInstanceResult>(command.InstanceGuid, new InstanceActor.LaunchInstanceCommand(command.AuditLogUserGuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<StopInstanceResult, InstanceActionFailure>> StopInstance(StopInstanceCommand command) {
|
private Task<Result<StopInstanceResult, InstanceActionFailure>> StopInstance(StopInstanceCommand command) {
|
||||||
return RequestInstance<InstanceActor.StopInstanceCommand, StopInstanceResult>(command.InstanceGuid, new InstanceActor.StopInstanceCommand(command.LoggedInUserGuid, command.StopStrategy));
|
return RequestInstance<InstanceActor.StopInstanceCommand, StopInstanceResult>(command.InstanceGuid, new InstanceActor.StopInstanceCommand(command.AuditLogUserGuid, command.StopStrategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<SendCommandToInstanceResult, InstanceActionFailure>> SendMinecraftCommand(SendCommandToInstanceCommand command) {
|
private Task<Result<SendCommandToInstanceResult, InstanceActionFailure>> SendMinecraftCommand(SendCommandToInstanceCommand command) {
|
||||||
return RequestInstance<InstanceActor.SendCommandToInstanceCommand, SendCommandToInstanceResult>(command.InstanceGuid, new InstanceActor.SendCommandToInstanceCommand(command.LoggedInUserGuid, command.Command));
|
return RequestInstance<InstanceActor.SendCommandToInstanceCommand, SendCommandToInstanceResult>(command.InstanceGuid, new InstanceActor.SendCommandToInstanceCommand(command.AuditLogUserGuid, command.Command));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReceiveInstanceData(ReceiveInstanceDataCommand command) {
|
private void ReceiveInstanceData(ReceiveInstanceDataCommand command) {
|
||||||
|
@@ -1,29 +1,36 @@
|
|||||||
using Phantom.Common.Messages.Agent;
|
using Phantom.Common.Messages.Agent;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Agents;
|
namespace Phantom.Controller.Services.Agents;
|
||||||
|
|
||||||
sealed class AgentConnection(Guid agentGuid, string agentName) {
|
sealed class AgentConnection {
|
||||||
private static readonly ILogger Logger = PhantomLogger.Create<AgentConnection>();
|
private static readonly ILogger Logger = PhantomLogger.Create<AgentConnection>();
|
||||||
|
|
||||||
private string agentName = agentName;
|
private readonly Guid agentGuid;
|
||||||
private RpcServerToClientConnection<IMessageToController, IMessageToAgent>? connection;
|
private string agentName;
|
||||||
|
|
||||||
public void UpdateConnection(RpcServerToClientConnection<IMessageToController, IMessageToAgent> newConnection, string newAgentName) {
|
private RpcConnectionToClient<IMessageToAgent>? connection;
|
||||||
|
|
||||||
|
public AgentConnection(Guid agentGuid, string agentName) {
|
||||||
|
this.agentName = agentName;
|
||||||
|
this.agentGuid = agentGuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateConnection(RpcConnectionToClient<IMessageToAgent> newConnection, string newAgentName) {
|
||||||
lock (this) {
|
lock (this) {
|
||||||
connection?.CloseSession();
|
connection?.Close();
|
||||||
connection = newConnection;
|
connection = newConnection;
|
||||||
agentName = newAgentName;
|
agentName = newAgentName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CloseIfSame(RpcServerToClientConnection<IMessageToController, IMessageToAgent> expected) {
|
public bool CloseIfSame(RpcConnectionToClient<IMessageToAgent> expected) {
|
||||||
lock (this) {
|
lock (this) {
|
||||||
if (connection != null && connection.IsSame(expected)) {
|
if (connection != null && connection.IsSame(expected)) {
|
||||||
connection.CloseSession();
|
connection.Close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +45,7 @@ sealed class AgentConnection(Guid agentGuid, string agentName) {
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection.SendChannel.SendMessage(message).AsTask();
|
return connection.Send(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,10 +53,10 @@ sealed class AgentConnection(Guid agentGuid, string agentName) {
|
|||||||
lock (this) {
|
lock (this) {
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
LogAgentOffline();
|
LogAgentOffline();
|
||||||
return Task.FromResult<TReply?>(null);
|
return Task.FromResult<TReply?>(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection.SendChannel.SendMessage<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken)!;
|
return connection.Send<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,19 +1,16 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
using Phantom.Common.Data;
|
using Phantom.Common.Data;
|
||||||
using Phantom.Common.Data.Agent;
|
using Phantom.Common.Data.Agent;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Agent;
|
using Phantom.Common.Data.Web.Agent;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Common.Messages.Agent;
|
using Phantom.Common.Messages.Agent;
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
using Phantom.Common.Messages.Agent.ToAgent;
|
||||||
using Phantom.Controller.Database;
|
using Phantom.Controller.Database;
|
||||||
using Phantom.Controller.Minecraft;
|
using Phantom.Controller.Minecraft;
|
||||||
using Phantom.Controller.Services.Users.Sessions;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Agents;
|
namespace Phantom.Controller.Services.Agents;
|
||||||
@@ -22,20 +19,20 @@ sealed class AgentManager {
|
|||||||
private static readonly ILogger Logger = PhantomLogger.Create<AgentManager>();
|
private static readonly ILogger Logger = PhantomLogger.Create<AgentManager>();
|
||||||
|
|
||||||
private readonly IActorRefFactory actorSystem;
|
private readonly IActorRefFactory actorSystem;
|
||||||
|
private readonly AuthToken authToken;
|
||||||
private readonly ControllerState controllerState;
|
private readonly ControllerState controllerState;
|
||||||
private readonly MinecraftVersions minecraftVersions;
|
private readonly MinecraftVersions minecraftVersions;
|
||||||
private readonly UserLoginManager userLoginManager;
|
|
||||||
private readonly IDbContextProvider dbProvider;
|
private readonly IDbContextProvider dbProvider;
|
||||||
private readonly CancellationToken cancellationToken;
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Guid, ActorRef<AgentActor.ICommand>> agentsByGuid = new ();
|
private readonly ConcurrentDictionary<Guid, ActorRef<AgentActor.ICommand>> agentsByGuid = new ();
|
||||||
private readonly Func<Guid, AgentConfiguration, ActorRef<AgentActor.ICommand>> addAgentActorFactory;
|
private readonly Func<Guid, AgentConfiguration, ActorRef<AgentActor.ICommand>> addAgentActorFactory;
|
||||||
|
|
||||||
public AgentManager(IActorRefFactory actorSystem, ControllerState controllerState, MinecraftVersions minecraftVersions, UserLoginManager userLoginManager, IDbContextProvider dbProvider, CancellationToken cancellationToken) {
|
public AgentManager(IActorRefFactory actorSystem, AuthToken authToken, ControllerState controllerState, MinecraftVersions minecraftVersions, IDbContextProvider dbProvider, CancellationToken cancellationToken) {
|
||||||
this.actorSystem = actorSystem;
|
this.actorSystem = actorSystem;
|
||||||
|
this.authToken = authToken;
|
||||||
this.controllerState = controllerState;
|
this.controllerState = controllerState;
|
||||||
this.minecraftVersions = minecraftVersions;
|
this.minecraftVersions = minecraftVersions;
|
||||||
this.userLoginManager = userLoginManager;
|
|
||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
this.cancellationToken = cancellationToken;
|
this.cancellationToken = cancellationToken;
|
||||||
|
|
||||||
@@ -61,10 +58,18 @@ sealed class AgentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ImmutableArray<ConfigureInstanceMessage>> RegisterAgent(AgentInfo agentInfo, RpcServerToClientConnection<IMessageToController, IMessageToAgent> connection) {
|
public async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, RpcConnectionToClient<IMessageToAgent> connection) {
|
||||||
|
if (!this.authToken.FixedTimeEquals(authToken)) {
|
||||||
|
await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.InvalidToken));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var agentProperties = AgentConfiguration.From(agentInfo);
|
var agentProperties = AgentConfiguration.From(agentInfo);
|
||||||
var agentActor = agentsByGuid.GetOrAdd(agentInfo.AgentGuid, addAgentActorFactory, agentProperties);
|
var agentActor = agentsByGuid.GetOrAdd(agentInfo.AgentGuid, addAgentActorFactory, agentProperties);
|
||||||
return await agentActor.Request(new AgentActor.RegisterCommand(agentProperties, connection), cancellationToken);
|
var configureInstanceMessages = await agentActor.Request(new AgentActor.RegisterCommand(agentProperties, connection), cancellationToken);
|
||||||
|
await connection.Send(new RegisterAgentSuccessMessage(configureInstanceMessages));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TellAgent(Guid agentGuid, AgentActor.ICommand command) {
|
public bool TellAgent(Guid agentGuid, AgentActor.ICommand command) {
|
||||||
@@ -78,18 +83,7 @@ sealed class AgentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<TReply, UserInstanceActionFailure>> DoInstanceAction<TCommand, TReply>(Permission requiredPermission, ImmutableArray<byte> authToken, Guid agentGuid, Func<Guid, TCommand> commandFactoryFromLoggedInUserGuid) where TCommand : class, AgentActor.ICommand, ICanReply<Result<TReply, InstanceActionFailure>> {
|
public async Task<Result<TReply, InstanceActionFailure>> DoInstanceAction<TCommand, TReply>(Guid agentGuid, TCommand command) where TCommand : class, AgentActor.ICommand, ICanReply<Result<TReply, InstanceActionFailure>> {
|
||||||
var loggedInUser = userLoginManager.GetLoggedInUser(authToken);
|
return agentsByGuid.TryGetValue(agentGuid, out var agent) ? await agent.Request(command, cancellationToken) : InstanceActionFailure.AgentDoesNotExist;
|
||||||
if (!loggedInUser.HasAccessToAgent(agentGuid) || !loggedInUser.CheckPermission(requiredPermission)) {
|
|
||||||
return (UserInstanceActionFailure) UserActionFailure.NotAuthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!agentsByGuid.TryGetValue(agentGuid, out var agent)) {
|
|
||||||
return (UserInstanceActionFailure) InstanceActionFailure.AgentDoesNotExist;
|
|
||||||
}
|
|
||||||
|
|
||||||
var command = commandFactoryFromLoggedInUserGuid(loggedInUser.Guid!.Value);
|
|
||||||
var result = await agent.Request(command, cancellationToken);
|
|
||||||
return result.MapError(static error => (UserInstanceActionFailure) error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
|
using Phantom.Common.Data;
|
||||||
|
using Phantom.Common.Messages.Agent;
|
||||||
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
|
using Phantom.Common.Messages.Web;
|
||||||
|
using Phantom.Common.Messages.Web.ToController;
|
||||||
using Phantom.Controller.Database;
|
using Phantom.Controller.Database;
|
||||||
using Phantom.Controller.Minecraft;
|
using Phantom.Controller.Minecraft;
|
||||||
using Phantom.Controller.Services.Agents;
|
using Phantom.Controller.Services.Agents;
|
||||||
@@ -6,11 +11,10 @@ using Phantom.Controller.Services.Events;
|
|||||||
using Phantom.Controller.Services.Instances;
|
using Phantom.Controller.Services.Instances;
|
||||||
using Phantom.Controller.Services.Rpc;
|
using Phantom.Controller.Services.Rpc;
|
||||||
using Phantom.Controller.Services.Users;
|
using Phantom.Controller.Services.Users;
|
||||||
using Phantom.Controller.Services.Users.Sessions;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Rpc;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
using IRpcAgentRegistrar = Phantom.Utils.Rpc.Runtime.Server.IRpcServerClientRegistrar<Phantom.Common.Messages.Agent.IMessageToController, Phantom.Common.Messages.Agent.IMessageToAgent>;
|
using IMessageFromAgentToController = Phantom.Common.Messages.Agent.IMessageToController;
|
||||||
using IRpcWebRegistrar = Phantom.Utils.Rpc.Runtime.Server.IRpcServerClientRegistrar<Phantom.Common.Messages.Web.IMessageToController, Phantom.Common.Messages.Web.IMessageToWeb>;
|
using IMessageFromWebToController = Phantom.Common.Messages.Web.IMessageToController;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services;
|
namespace Phantom.Controller.Services;
|
||||||
|
|
||||||
@@ -20,26 +24,25 @@ public sealed class ControllerServices : IDisposable {
|
|||||||
private ControllerState ControllerState { get; }
|
private ControllerState ControllerState { get; }
|
||||||
private MinecraftVersions MinecraftVersions { get; }
|
private MinecraftVersions MinecraftVersions { get; }
|
||||||
|
|
||||||
private AuthenticatedUserCache AuthenticatedUserCache { get; }
|
|
||||||
private UserManager UserManager { get; }
|
|
||||||
private RoleManager RoleManager { get; }
|
|
||||||
private UserRoleManager UserRoleManager { get; }
|
|
||||||
private UserLoginManager UserLoginManager { get; }
|
|
||||||
private PermissionManager PermissionManager { get; }
|
|
||||||
|
|
||||||
private AgentManager AgentManager { get; }
|
private AgentManager AgentManager { get; }
|
||||||
private InstanceLogManager InstanceLogManager { get; }
|
private InstanceLogManager InstanceLogManager { get; }
|
||||||
|
|
||||||
private AuditLogManager AuditLogManager { get; }
|
|
||||||
private EventLogManager EventLogManager { get; }
|
private EventLogManager EventLogManager { get; }
|
||||||
|
|
||||||
public IRpcAgentRegistrar AgentRegistrar { get; }
|
private UserManager UserManager { get; }
|
||||||
public IRpcWebRegistrar WebRegistrar { get; }
|
private RoleManager RoleManager { get; }
|
||||||
|
private PermissionManager PermissionManager { get; }
|
||||||
|
|
||||||
|
private UserRoleManager UserRoleManager { get; }
|
||||||
|
private UserLoginManager UserLoginManager { get; }
|
||||||
|
private AuditLogManager AuditLogManager { get; }
|
||||||
|
|
||||||
|
public IRegistrationHandler<IMessageToAgent, IMessageFromAgentToController, RegisterAgentMessage> AgentRegistrationHandler { get; }
|
||||||
|
public IRegistrationHandler<IMessageToWeb, IMessageFromWebToController, RegisterWebMessage> WebRegistrationHandler { get; }
|
||||||
|
|
||||||
private readonly IDbContextProvider dbProvider;
|
private readonly IDbContextProvider dbProvider;
|
||||||
private readonly CancellationToken cancellationToken;
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
public ControllerServices(IDbContextProvider dbProvider, AuthToken agentAuthToken, CancellationToken shutdownCancellationToken) {
|
public ControllerServices(IDbContextProvider dbProvider, AuthToken agentAuthToken, AuthToken webAuthToken, CancellationToken shutdownCancellationToken) {
|
||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
this.cancellationToken = shutdownCancellationToken;
|
this.cancellationToken = shutdownCancellationToken;
|
||||||
|
|
||||||
@@ -48,21 +51,20 @@ public sealed class ControllerServices : IDisposable {
|
|||||||
this.ControllerState = new ControllerState();
|
this.ControllerState = new ControllerState();
|
||||||
this.MinecraftVersions = new MinecraftVersions();
|
this.MinecraftVersions = new MinecraftVersions();
|
||||||
|
|
||||||
this.AuthenticatedUserCache = new AuthenticatedUserCache();
|
this.AgentManager = new AgentManager(ActorSystem, agentAuthToken, ControllerState, MinecraftVersions, dbProvider, cancellationToken);
|
||||||
this.UserManager = new UserManager(AuthenticatedUserCache, ControllerState, dbProvider);
|
|
||||||
this.RoleManager = new RoleManager(dbProvider);
|
|
||||||
this.UserRoleManager = new UserRoleManager(AuthenticatedUserCache, ControllerState, dbProvider);
|
|
||||||
this.UserLoginManager = new UserLoginManager(AuthenticatedUserCache, UserManager, dbProvider);
|
|
||||||
this.PermissionManager = new PermissionManager(dbProvider);
|
|
||||||
|
|
||||||
this.AgentManager = new AgentManager(ActorSystem, ControllerState, MinecraftVersions, UserLoginManager, dbProvider, cancellationToken);
|
|
||||||
this.InstanceLogManager = new InstanceLogManager();
|
this.InstanceLogManager = new InstanceLogManager();
|
||||||
|
|
||||||
this.AuditLogManager = new AuditLogManager(dbProvider);
|
this.UserManager = new UserManager(dbProvider);
|
||||||
this.EventLogManager = new EventLogManager(ControllerState, ActorSystem, dbProvider, shutdownCancellationToken);
|
this.RoleManager = new RoleManager(dbProvider);
|
||||||
|
this.PermissionManager = new PermissionManager(dbProvider);
|
||||||
|
|
||||||
this.AgentRegistrar = new AgentClientRegistrar(ActorSystem, AgentManager, InstanceLogManager, EventLogManager);
|
this.UserRoleManager = new UserRoleManager(dbProvider);
|
||||||
this.WebRegistrar = new WebClientRegistrar(ActorSystem, ControllerState, InstanceLogManager, UserManager, RoleManager, UserRoleManager, UserLoginManager, AuditLogManager, AgentManager, MinecraftVersions, EventLogManager);
|
this.UserLoginManager = new UserLoginManager(UserManager, PermissionManager, dbProvider);
|
||||||
|
this.AuditLogManager = new AuditLogManager(dbProvider);
|
||||||
|
this.EventLogManager = new EventLogManager(ActorSystem, dbProvider, shutdownCancellationToken);
|
||||||
|
|
||||||
|
this.AgentRegistrationHandler = new AgentRegistrationHandler(AgentManager, InstanceLogManager, EventLogManager);
|
||||||
|
this.WebRegistrationHandler = new WebRegistrationHandler(webAuthToken, ControllerState, InstanceLogManager, UserManager, RoleManager, UserRoleManager, UserLoginManager, AuditLogManager, AgentManager, MinecraftVersions, EventLogManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize() {
|
public async Task Initialize() {
|
||||||
|
@@ -19,8 +19,6 @@ sealed class ControllerState {
|
|||||||
public ObservableState<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>.Receiver AgentJavaRuntimesByGuidReceiver => agentJavaRuntimesByGuid.ReceiverSide;
|
public ObservableState<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>.Receiver AgentJavaRuntimesByGuidReceiver => agentJavaRuntimesByGuid.ReceiverSide;
|
||||||
public ObservableState<ImmutableDictionary<Guid, Instance>>.Receiver InstancesByGuidReceiver => instancesByGuid.ReceiverSide;
|
public ObservableState<ImmutableDictionary<Guid, Instance>>.Receiver InstancesByGuidReceiver => instancesByGuid.ReceiverSide;
|
||||||
|
|
||||||
public event EventHandler<Guid>? UserUpdatedOrDeleted;
|
|
||||||
|
|
||||||
public void UpdateAgent(Agent agent) {
|
public void UpdateAgent(Agent agent) {
|
||||||
agentsByGuid.PublisherSide.Publish(static (agentsByGuid, agent) => agentsByGuid.SetItem(agent.AgentGuid, agent), agent);
|
agentsByGuid.PublisherSide.Publish(static (agentsByGuid, agent) => agentsByGuid.SetItem(agent.AgentGuid, agent), agent);
|
||||||
}
|
}
|
||||||
@@ -32,8 +30,4 @@ sealed class ControllerState {
|
|||||||
public void UpdateInstance(Instance instance) {
|
public void UpdateInstance(Instance instance) {
|
||||||
instancesByGuid.PublisherSide.Publish(static (instancesByGuid, instance) => instancesByGuid.SetItem(instance.InstanceGuid, instance), instance);
|
instancesByGuid.PublisherSide.Publish(static (instancesByGuid, instance) => instancesByGuid.SetItem(instance.InstanceGuid, instance), instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateOrDeleteUser(Guid userGuid) {
|
|
||||||
UserUpdatedOrDeleted?.Invoke(null, userGuid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,18 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.EventLog;
|
using Phantom.Common.Data.Web.EventLog;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Controller.Database;
|
using Phantom.Controller.Database;
|
||||||
using Phantom.Controller.Database.Repositories;
|
using Phantom.Controller.Database.Repositories;
|
||||||
using Phantom.Controller.Services.Users.Sessions;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Events;
|
namespace Phantom.Controller.Services.Events;
|
||||||
|
|
||||||
sealed partial class EventLogManager {
|
sealed partial class EventLogManager {
|
||||||
private readonly ControllerState controllerState;
|
|
||||||
private readonly ActorRef<EventLogDatabaseStorageActor.ICommand> databaseStorageActor;
|
private readonly ActorRef<EventLogDatabaseStorageActor.ICommand> databaseStorageActor;
|
||||||
private readonly IDbContextProvider dbProvider;
|
private readonly IDbContextProvider dbProvider;
|
||||||
private readonly CancellationToken cancellationToken;
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
public EventLogManager(ControllerState controllerState, IActorRefFactory actorSystem, IDbContextProvider dbProvider, CancellationToken cancellationToken) {
|
public EventLogManager(IActorRefFactory actorSystem, IDbContextProvider dbProvider, CancellationToken cancellationToken) {
|
||||||
this.controllerState = controllerState;
|
|
||||||
this.databaseStorageActor = actorSystem.ActorOf(EventLogDatabaseStorageActor.Factory(new EventLogDatabaseStorageActor.Init(dbProvider, cancellationToken)), "EventLogDatabaseStorage");
|
this.databaseStorageActor = actorSystem.ActorOf(EventLogDatabaseStorageActor.Factory(new EventLogDatabaseStorageActor.Init(dbProvider, cancellationToken)), "EventLogDatabaseStorage");
|
||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
this.cancellationToken = cancellationToken;
|
this.cancellationToken = cancellationToken;
|
||||||
@@ -27,14 +22,8 @@ sealed partial class EventLogManager {
|
|||||||
databaseStorageActor.Tell(new EventLogDatabaseStorageActor.StoreEventCommand(eventGuid, utcTime, agentGuid, eventType, subjectId, extra));
|
databaseStorageActor.Tell(new EventLogDatabaseStorageActor.StoreEventCommand(eventGuid, utcTime, agentGuid, eventType, subjectId, extra));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ImmutableArray<EventLogItem>, UserActionFailure>> GetMostRecentItems(LoggedInUser loggedInUser, int count) {
|
public async Task<ImmutableArray<EventLogItem>> GetMostRecentItems(int count) {
|
||||||
if (!loggedInUser.CheckPermission(Permission.ViewEvents)) {
|
|
||||||
return UserActionFailure.NotAuthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessibleAgentGuids = loggedInUser.FilterAccessibleAgentGuids(controllerState.AgentsByGuid.Keys.ToImmutableHashSet());
|
|
||||||
|
|
||||||
await using var db = dbProvider.Lazy();
|
await using var db = dbProvider.Lazy();
|
||||||
return await new EventLogRepository(db).GetMostRecentItems(accessibleAgentGuids, count, cancellationToken);
|
return await new EventLogRepository(db).GetMostRecentItems(count, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,6 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
|
|
||||||
private InstanceConfiguration configuration;
|
private InstanceConfiguration configuration;
|
||||||
private IInstanceStatus status;
|
private IInstanceStatus status;
|
||||||
private InstancePlayerCounts? playerCounts;
|
|
||||||
private bool launchAutomatically;
|
private bool launchAutomatically;
|
||||||
|
|
||||||
private readonly ActorRef<InstanceDatabaseStorageActor.ICommand> databaseStorageActor;
|
private readonly ActorRef<InstanceDatabaseStorageActor.ICommand> databaseStorageActor;
|
||||||
@@ -36,12 +35,11 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
this.agentConnection = init.AgentConnection;
|
this.agentConnection = init.AgentConnection;
|
||||||
this.cancellationToken = init.CancellationToken;
|
this.cancellationToken = init.CancellationToken;
|
||||||
|
|
||||||
(this.instanceGuid, this.configuration, this.status, this.playerCounts, this.launchAutomatically) = init.Instance;
|
(this.instanceGuid, this.configuration, this.status, this.launchAutomatically) = init.Instance;
|
||||||
|
|
||||||
this.databaseStorageActor = Context.ActorOf(InstanceDatabaseStorageActor.Factory(new InstanceDatabaseStorageActor.Init(instanceGuid, init.DbProvider, init.CancellationToken)), "DatabaseStorage");
|
this.databaseStorageActor = Context.ActorOf(InstanceDatabaseStorageActor.Factory(new InstanceDatabaseStorageActor.Init(instanceGuid, init.DbProvider, init.CancellationToken)), "DatabaseStorage");
|
||||||
|
|
||||||
Receive<SetStatusCommand>(SetStatus);
|
Receive<SetStatusCommand>(SetStatus);
|
||||||
Receive<SetPlayerCountsCommand>(SetPlayerCounts);
|
|
||||||
ReceiveAsyncAndReply<ConfigureInstanceCommand, Result<ConfigureInstanceResult, InstanceActionFailure>>(ConfigureInstance);
|
ReceiveAsyncAndReply<ConfigureInstanceCommand, Result<ConfigureInstanceResult, InstanceActionFailure>>(ConfigureInstance);
|
||||||
ReceiveAsyncAndReply<LaunchInstanceCommand, Result<LaunchInstanceResult, InstanceActionFailure>>(LaunchInstance);
|
ReceiveAsyncAndReply<LaunchInstanceCommand, Result<LaunchInstanceResult, InstanceActionFailure>>(LaunchInstance);
|
||||||
ReceiveAsyncAndReply<StopInstanceCommand, Result<StopInstanceResult, InstanceActionFailure>>(StopInstance);
|
ReceiveAsyncAndReply<StopInstanceCommand, Result<StopInstanceResult, InstanceActionFailure>>(StopInstance);
|
||||||
@@ -49,7 +47,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyInstanceUpdated() {
|
private void NotifyInstanceUpdated() {
|
||||||
agentActor.Tell(new AgentActor.ReceiveInstanceDataCommand(new Instance(instanceGuid, configuration, status, playerCounts, launchAutomatically)));
|
agentActor.Tell(new AgentActor.ReceiveInstanceDataCommand(new Instance(instanceGuid, configuration, status, launchAutomatically)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetLaunchAutomatically(bool newValue) {
|
private void SetLaunchAutomatically(bool newValue) {
|
||||||
@@ -68,8 +66,6 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
|
|
||||||
public sealed record SetStatusCommand(IInstanceStatus Status) : ICommand;
|
public sealed record SetStatusCommand(IInstanceStatus Status) : ICommand;
|
||||||
|
|
||||||
public sealed record SetPlayerCountsCommand(InstancePlayerCounts? PlayerCounts) : ICommand;
|
|
||||||
|
|
||||||
public sealed record ConfigureInstanceCommand(Guid AuditLogUserGuid, Guid InstanceGuid, InstanceConfiguration Configuration, InstanceLaunchProperties LaunchProperties, bool IsCreatingInstance) : ICommand, ICanReply<Result<ConfigureInstanceResult, InstanceActionFailure>>;
|
public sealed record ConfigureInstanceCommand(Guid AuditLogUserGuid, Guid InstanceGuid, InstanceConfiguration Configuration, InstanceLaunchProperties LaunchProperties, bool IsCreatingInstance) : ICommand, ICanReply<Result<ConfigureInstanceResult, InstanceActionFailure>>;
|
||||||
|
|
||||||
public sealed record LaunchInstanceCommand(Guid AuditLogUserGuid) : ICommand, ICanReply<Result<LaunchInstanceResult, InstanceActionFailure>>;
|
public sealed record LaunchInstanceCommand(Guid AuditLogUserGuid) : ICommand, ICanReply<Result<LaunchInstanceResult, InstanceActionFailure>>;
|
||||||
@@ -80,16 +76,6 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
|
|
||||||
private void SetStatus(SetStatusCommand command) {
|
private void SetStatus(SetStatusCommand command) {
|
||||||
status = command.Status;
|
status = command.Status;
|
||||||
|
|
||||||
if (!status.IsRunning() && status != InstanceStatus.Offline /* Guard against temporary disconnects */) {
|
|
||||||
playerCounts = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
NotifyInstanceUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPlayerCounts(SetPlayerCountsCommand command) {
|
|
||||||
playerCounts = command.PlayerCounts;
|
|
||||||
NotifyInstanceUpdated();
|
NotifyInstanceUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
using Akka.Actor;
|
|
||||||
using Phantom.Common.Messages.Agent;
|
|
||||||
using Phantom.Controller.Services.Agents;
|
|
||||||
using Phantom.Controller.Services.Events;
|
|
||||||
using Phantom.Controller.Services.Instances;
|
|
||||||
using Phantom.Utils.Actor;
|
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Rpc;
|
|
||||||
|
|
||||||
sealed class AgentClientRegistrar(
|
|
||||||
IActorRefFactory actorSystem,
|
|
||||||
AgentManager agentManager,
|
|
||||||
InstanceLogManager instanceLogManager,
|
|
||||||
EventLogManager eventLogManager
|
|
||||||
) : IRpcServerClientRegistrar<IMessageToController, IMessageToAgent> {
|
|
||||||
public ActorRef<IMessageToController> Register(Guid sessionId, RpcServerToClientConnection<IMessageToController, IMessageToAgent> connection) {
|
|
||||||
var init = new AgentMessageHandlerActor.Init(connection, agentManager, instanceLogManager, eventLogManager);
|
|
||||||
return actorSystem.ActorOf(AgentMessageHandlerActor.Factory(init), "AgentClient-" + sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,85 +1,88 @@
|
|||||||
using System.Collections.Immutable;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Messages.Agent;
|
using Phantom.Common.Messages.Agent;
|
||||||
|
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
using Phantom.Common.Messages.Agent.ToAgent;
|
||||||
using Phantom.Common.Messages.Agent.ToController;
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
using Phantom.Controller.Services.Agents;
|
using Phantom.Controller.Services.Agents;
|
||||||
using Phantom.Controller.Services.Events;
|
using Phantom.Controller.Services.Events;
|
||||||
using Phantom.Controller.Services.Instances;
|
using Phantom.Controller.Services.Instances;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Rpc;
|
namespace Phantom.Controller.Services.Rpc;
|
||||||
|
|
||||||
sealed class AgentMessageHandlerActor : ReceiveActor<IMessageToController> {
|
sealed class AgentMessageHandlerActor : ReceiveActor<IMessageToController> {
|
||||||
public readonly record struct Init(RpcServerToClientConnection<IMessageToController, IMessageToAgent> Connection, AgentManager AgentManager, InstanceLogManager InstanceLogManager, EventLogManager EventLogManager);
|
public readonly record struct Init(Guid AgentGuid, RpcConnectionToClient<IMessageToAgent> Connection, AgentRegistrationHandler AgentRegistrationHandler, AgentManager AgentManager, InstanceLogManager InstanceLogManager, EventLogManager EventLogManager);
|
||||||
|
|
||||||
public static Props<IMessageToController> Factory(Init init) {
|
public static Props<IMessageToController> Factory(Init init) {
|
||||||
return Props<IMessageToController>.Create(() => new AgentMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
return Props<IMessageToController>.Create(() => new AgentMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly RpcServerToClientConnection<IMessageToController, IMessageToAgent> connection;
|
private readonly Guid agentGuid;
|
||||||
|
private readonly RpcConnectionToClient<IMessageToAgent> connection;
|
||||||
|
private readonly AgentRegistrationHandler agentRegistrationHandler;
|
||||||
private readonly AgentManager agentManager;
|
private readonly AgentManager agentManager;
|
||||||
private readonly InstanceLogManager instanceLogManager;
|
private readonly InstanceLogManager instanceLogManager;
|
||||||
private readonly EventLogManager eventLogManager;
|
private readonly EventLogManager eventLogManager;
|
||||||
|
|
||||||
private Guid? agentGuid;
|
|
||||||
|
|
||||||
private AgentMessageHandlerActor(Init init) {
|
private AgentMessageHandlerActor(Init init) {
|
||||||
|
this.agentGuid = init.AgentGuid;
|
||||||
this.connection = init.Connection;
|
this.connection = init.Connection;
|
||||||
|
this.agentRegistrationHandler = init.AgentRegistrationHandler;
|
||||||
this.agentManager = init.AgentManager;
|
this.agentManager = init.AgentManager;
|
||||||
this.instanceLogManager = init.InstanceLogManager;
|
this.instanceLogManager = init.InstanceLogManager;
|
||||||
this.eventLogManager = init.EventLogManager;
|
this.eventLogManager = init.EventLogManager;
|
||||||
|
|
||||||
ReceiveAsyncAndReply<RegisterAgentMessage, ImmutableArray<ConfigureInstanceMessage>>(HandleRegisterAgent);
|
ReceiveAsync<RegisterAgentMessage>(HandleRegisterAgent);
|
||||||
Receive<UnregisterAgentMessage>(HandleUnregisterAgent);
|
Receive<UnregisterAgentMessage>(HandleUnregisterAgent);
|
||||||
Receive<AgentIsAliveMessage>(HandleAgentIsAlive);
|
Receive<AgentIsAliveMessage>(HandleAgentIsAlive);
|
||||||
Receive<AdvertiseJavaRuntimesMessage>(HandleAdvertiseJavaRuntimes);
|
Receive<AdvertiseJavaRuntimesMessage>(HandleAdvertiseJavaRuntimes);
|
||||||
Receive<ReportAgentStatusMessage>(HandleReportAgentStatus);
|
Receive<ReportAgentStatusMessage>(HandleReportAgentStatus);
|
||||||
Receive<ReportInstanceStatusMessage>(HandleReportInstanceStatus);
|
Receive<ReportInstanceStatusMessage>(HandleReportInstanceStatus);
|
||||||
Receive<ReportInstancePlayerCountsMessage>(HandleReportInstancePlayerCounts);
|
|
||||||
Receive<ReportInstanceEventMessage>(HandleReportInstanceEvent);
|
Receive<ReportInstanceEventMessage>(HandleReportInstanceEvent);
|
||||||
Receive<InstanceOutputMessage>(HandleInstanceOutput);
|
Receive<InstanceOutputMessage>(HandleInstanceOutput);
|
||||||
|
Receive<ReplyMessage>(HandleReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guid RequireAgentGuid() {
|
private async Task HandleRegisterAgent(RegisterAgentMessage message) {
|
||||||
return agentGuid ?? throw new InvalidOperationException("Agent has not registered yet.");
|
if (agentGuid != message.AgentInfo.AgentGuid) {
|
||||||
|
await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.ConnectionAlreadyHasAnAgent));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await agentRegistrationHandler.TryRegisterImpl(connection, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<ImmutableArray<ConfigureInstanceMessage>> HandleRegisterAgent(RegisterAgentMessage message) {
|
|
||||||
agentGuid = message.AgentInfo.AgentGuid;
|
|
||||||
return agentManager.RegisterAgent(message.AgentInfo, connection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleUnregisterAgent(UnregisterAgentMessage message) {
|
private void HandleUnregisterAgent(UnregisterAgentMessage message) {
|
||||||
agentManager.TellAgent(RequireAgentGuid(), new AgentActor.UnregisterCommand(connection));
|
agentManager.TellAgent(agentGuid, new AgentActor.UnregisterCommand(connection));
|
||||||
connection.CloseSession();
|
connection.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleAgentIsAlive(AgentIsAliveMessage message) {
|
private void HandleAgentIsAlive(AgentIsAliveMessage message) {
|
||||||
agentManager.TellAgent(RequireAgentGuid(), new AgentActor.NotifyIsAliveCommand());
|
agentManager.TellAgent(agentGuid, new AgentActor.NotifyIsAliveCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleAdvertiseJavaRuntimes(AdvertiseJavaRuntimesMessage message) {
|
private void HandleAdvertiseJavaRuntimes(AdvertiseJavaRuntimesMessage message) {
|
||||||
agentManager.TellAgent(RequireAgentGuid(), new AgentActor.UpdateJavaRuntimesCommand(message.Runtimes));
|
agentManager.TellAgent(agentGuid, new AgentActor.UpdateJavaRuntimesCommand(message.Runtimes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleReportAgentStatus(ReportAgentStatusMessage message) {
|
private void HandleReportAgentStatus(ReportAgentStatusMessage message) {
|
||||||
agentManager.TellAgent(RequireAgentGuid(), new AgentActor.UpdateStatsCommand(message.RunningInstanceCount, message.RunningInstanceMemory));
|
agentManager.TellAgent(agentGuid, new AgentActor.UpdateStatsCommand(message.RunningInstanceCount, message.RunningInstanceMemory));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleReportInstanceStatus(ReportInstanceStatusMessage message) {
|
private void HandleReportInstanceStatus(ReportInstanceStatusMessage message) {
|
||||||
agentManager.TellAgent(RequireAgentGuid(), new AgentActor.UpdateInstanceStatusCommand(message.InstanceGuid, message.InstanceStatus));
|
agentManager.TellAgent(agentGuid, new AgentActor.UpdateInstanceStatusCommand(message.InstanceGuid, message.InstanceStatus));
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleReportInstancePlayerCounts(ReportInstancePlayerCountsMessage message) {
|
|
||||||
agentManager.TellAgent(RequireAgentGuid(), new AgentActor.UpdateInstancePlayerCountsCommand(message.InstanceGuid, message.PlayerCounts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleReportInstanceEvent(ReportInstanceEventMessage message) {
|
private void HandleReportInstanceEvent(ReportInstanceEventMessage message) {
|
||||||
message.Event.Accept(eventLogManager.CreateInstanceEventVisitor(message.EventGuid, message.UtcTime, RequireAgentGuid(), message.InstanceGuid));
|
message.Event.Accept(eventLogManager.CreateInstanceEventVisitor(message.EventGuid, message.UtcTime, agentGuid, message.InstanceGuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleInstanceOutput(InstanceOutputMessage message) {
|
private void HandleInstanceOutput(InstanceOutputMessage message) {
|
||||||
instanceLogManager.ReceiveLines(message.InstanceGuid, message.Lines);
|
instanceLogManager.ReceiveLines(message.InstanceGuid, message.Lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleReply(ReplyMessage message) {
|
||||||
|
connection.Receive(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
using Phantom.Common.Messages.Agent;
|
||||||
|
using Phantom.Common.Messages.Agent.ToController;
|
||||||
|
using Phantom.Controller.Services.Agents;
|
||||||
|
using Phantom.Controller.Services.Events;
|
||||||
|
using Phantom.Controller.Services.Instances;
|
||||||
|
using Phantom.Utils.Actor;
|
||||||
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
|
||||||
|
namespace Phantom.Controller.Services.Rpc;
|
||||||
|
|
||||||
|
sealed class AgentRegistrationHandler : IRegistrationHandler<IMessageToAgent, IMessageToController, RegisterAgentMessage> {
|
||||||
|
private readonly AgentManager agentManager;
|
||||||
|
private readonly InstanceLogManager instanceLogManager;
|
||||||
|
private readonly EventLogManager eventLogManager;
|
||||||
|
|
||||||
|
public AgentRegistrationHandler(AgentManager agentManager, InstanceLogManager instanceLogManager, EventLogManager eventLogManager) {
|
||||||
|
this.agentManager = agentManager;
|
||||||
|
this.instanceLogManager = instanceLogManager;
|
||||||
|
this.eventLogManager = eventLogManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<Props<IMessageToController>?> IRegistrationHandler<IMessageToAgent, IMessageToController, RegisterAgentMessage>.TryRegister(RpcConnectionToClient<IMessageToAgent> connection, RegisterAgentMessage message) {
|
||||||
|
return await TryRegisterImpl(connection, message) ? CreateMessageHandlerActorProps(message.AgentInfo.AgentGuid, connection) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> TryRegisterImpl(RpcConnectionToClient<IMessageToAgent> connection, RegisterAgentMessage message) {
|
||||||
|
return agentManager.RegisterAgent(message.AuthToken, message.AgentInfo, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Props<IMessageToController> CreateMessageHandlerActorProps(Guid agentGuid, RpcConnectionToClient<IMessageToAgent> connection) {
|
||||||
|
var init = new AgentMessageHandlerActor.Init(agentGuid, connection, this, agentManager, instanceLogManager, eventLogManager);
|
||||||
|
return AgentMessageHandlerActor.Factory(init);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,31 +0,0 @@
|
|||||||
using Akka.Actor;
|
|
||||||
using Phantom.Common.Messages.Web;
|
|
||||||
using Phantom.Controller.Minecraft;
|
|
||||||
using Phantom.Controller.Services.Agents;
|
|
||||||
using Phantom.Controller.Services.Events;
|
|
||||||
using Phantom.Controller.Services.Instances;
|
|
||||||
using Phantom.Controller.Services.Users;
|
|
||||||
using Phantom.Controller.Services.Users.Sessions;
|
|
||||||
using Phantom.Utils.Actor;
|
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Rpc;
|
|
||||||
|
|
||||||
sealed class WebClientRegistrar(
|
|
||||||
IActorRefFactory actorSystem,
|
|
||||||
ControllerState controllerState,
|
|
||||||
InstanceLogManager instanceLogManager,
|
|
||||||
UserManager userManager,
|
|
||||||
RoleManager roleManager,
|
|
||||||
UserRoleManager userRoleManager,
|
|
||||||
UserLoginManager userLoginManager,
|
|
||||||
AuditLogManager auditLogManager,
|
|
||||||
AgentManager agentManager,
|
|
||||||
MinecraftVersions minecraftVersions,
|
|
||||||
EventLogManager eventLogManager
|
|
||||||
) : IRpcServerClientRegistrar<IMessageToController, IMessageToWeb> {
|
|
||||||
public ActorRef<IMessageToController> Register(Guid sessionId, RpcServerToClientConnection<IMessageToController, IMessageToWeb> connection) {
|
|
||||||
var init = new WebMessageHandlerActor.Init(connection, this, controllerState, instanceLogManager, userManager, roleManager, userRoleManager, userLoginManager, auditLogManager, agentManager, minecraftVersions, eventLogManager);
|
|
||||||
return actorSystem.ActorOf(WebMessageHandlerActor.Factory(init), "WebClient-" + sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -10,13 +10,13 @@ using Phantom.Utils.Rpc.Runtime;
|
|||||||
namespace Phantom.Controller.Services.Rpc;
|
namespace Phantom.Controller.Services.Rpc;
|
||||||
|
|
||||||
sealed class WebMessageDataUpdateSenderActor : ReceiveActor<WebMessageDataUpdateSenderActor.ICommand> {
|
sealed class WebMessageDataUpdateSenderActor : ReceiveActor<WebMessageDataUpdateSenderActor.ICommand> {
|
||||||
public readonly record struct Init(RpcSendChannel<IMessageToWeb> Connection, ControllerState ControllerState, InstanceLogManager InstanceLogManager);
|
public readonly record struct Init(RpcConnectionToClient<IMessageToWeb> Connection, ControllerState ControllerState, InstanceLogManager InstanceLogManager);
|
||||||
|
|
||||||
public static Props<ICommand> Factory(Init init) {
|
public static Props<ICommand> Factory(Init init) {
|
||||||
return Props<ICommand>.Create(() => new WebMessageDataUpdateSenderActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
return Props<ICommand>.Create(() => new WebMessageDataUpdateSenderActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly RpcSendChannel<IMessageToWeb> connection;
|
private readonly RpcConnectionToClient<IMessageToWeb> connection;
|
||||||
private readonly ControllerState controllerState;
|
private readonly ControllerState controllerState;
|
||||||
private readonly InstanceLogManager instanceLogManager;
|
private readonly InstanceLogManager instanceLogManager;
|
||||||
private readonly ActorRef<ICommand> selfCached;
|
private readonly ActorRef<ICommand> selfCached;
|
||||||
@@ -30,31 +30,22 @@ sealed class WebMessageDataUpdateSenderActor : ReceiveActor<WebMessageDataUpdate
|
|||||||
ReceiveAsync<RefreshAgentsCommand>(RefreshAgents);
|
ReceiveAsync<RefreshAgentsCommand>(RefreshAgents);
|
||||||
ReceiveAsync<RefreshInstancesCommand>(RefreshInstances);
|
ReceiveAsync<RefreshInstancesCommand>(RefreshInstances);
|
||||||
ReceiveAsync<ReceiveInstanceLogsCommand>(ReceiveInstanceLogs);
|
ReceiveAsync<ReceiveInstanceLogsCommand>(ReceiveInstanceLogs);
|
||||||
ReceiveAsync<RefreshUserSessionCommand>(RefreshUserSession);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PreStart() {
|
protected override void PreStart() {
|
||||||
controllerState.AgentsByGuidReceiver.Register(SelfTyped, static state => new RefreshAgentsCommand(state));
|
controllerState.AgentsByGuidReceiver.Register(SelfTyped, static state => new RefreshAgentsCommand(state));
|
||||||
controllerState.InstancesByGuidReceiver.Register(SelfTyped, static state => new RefreshInstancesCommand(state));
|
controllerState.InstancesByGuidReceiver.Register(SelfTyped, static state => new RefreshInstancesCommand(state));
|
||||||
|
|
||||||
controllerState.UserUpdatedOrDeleted += OnUserUpdatedOrDeleted;
|
|
||||||
|
|
||||||
instanceLogManager.LogsReceived += OnInstanceLogsReceived;
|
instanceLogManager.LogsReceived += OnInstanceLogsReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PostStop() {
|
protected override void PostStop() {
|
||||||
instanceLogManager.LogsReceived -= OnInstanceLogsReceived;
|
instanceLogManager.LogsReceived -= OnInstanceLogsReceived;
|
||||||
|
|
||||||
controllerState.UserUpdatedOrDeleted -= OnUserUpdatedOrDeleted;
|
|
||||||
|
|
||||||
controllerState.AgentsByGuidReceiver.Unregister(SelfTyped);
|
controllerState.AgentsByGuidReceiver.Unregister(SelfTyped);
|
||||||
controllerState.InstancesByGuidReceiver.Unregister(SelfTyped);
|
controllerState.InstancesByGuidReceiver.Unregister(SelfTyped);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserUpdatedOrDeleted(object? sender, Guid userGuid) {
|
|
||||||
selfCached.Tell(new RefreshUserSessionCommand(userGuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInstanceLogsReceived(object? sender, InstanceLogManager.Event e) {
|
private void OnInstanceLogsReceived(object? sender, InstanceLogManager.Event e) {
|
||||||
selfCached.Tell(new ReceiveInstanceLogsCommand(e.InstanceGuid, e.Lines));
|
selfCached.Tell(new ReceiveInstanceLogsCommand(e.InstanceGuid, e.Lines));
|
||||||
}
|
}
|
||||||
@@ -67,21 +58,15 @@ sealed class WebMessageDataUpdateSenderActor : ReceiveActor<WebMessageDataUpdate
|
|||||||
|
|
||||||
private sealed record ReceiveInstanceLogsCommand(Guid InstanceGuid, ImmutableArray<string> Lines) : ICommand;
|
private sealed record ReceiveInstanceLogsCommand(Guid InstanceGuid, ImmutableArray<string> Lines) : ICommand;
|
||||||
|
|
||||||
private sealed record RefreshUserSessionCommand(Guid UserGuid) : ICommand;
|
|
||||||
|
|
||||||
private Task RefreshAgents(RefreshAgentsCommand command) {
|
private Task RefreshAgents(RefreshAgentsCommand command) {
|
||||||
return connection.SendMessage(new RefreshAgentsMessage(command.Agents.Values.ToImmutableArray())).AsTask();
|
return connection.Send(new RefreshAgentsMessage(command.Agents.Values.ToImmutableArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task RefreshInstances(RefreshInstancesCommand command) {
|
private Task RefreshInstances(RefreshInstancesCommand command) {
|
||||||
return connection.SendMessage(new RefreshInstancesMessage(command.Instances.Values.ToImmutableArray())).AsTask();
|
return connection.Send(new RefreshInstancesMessage(command.Instances.Values.ToImmutableArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ReceiveInstanceLogs(ReceiveInstanceLogsCommand command) {
|
private Task ReceiveInstanceLogs(ReceiveInstanceLogsCommand command) {
|
||||||
return connection.SendMessage(new InstanceOutputMessage(command.InstanceGuid, command.Lines)).AsTask();
|
return connection.Send(new InstanceOutputMessage(command.InstanceGuid, command.Lines));
|
||||||
}
|
|
||||||
|
|
||||||
private Task RefreshUserSession(RefreshUserSessionCommand command) {
|
|
||||||
return connection.SendMessage(new RefreshUserSessionMessage(command.UserGuid)).AsTask();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ using Phantom.Common.Data.Web.AuditLog;
|
|||||||
using Phantom.Common.Data.Web.EventLog;
|
using Phantom.Common.Data.Web.EventLog;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
|
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||||
using Phantom.Common.Messages.Web;
|
using Phantom.Common.Messages.Web;
|
||||||
using Phantom.Common.Messages.Web.ToController;
|
using Phantom.Common.Messages.Web.ToController;
|
||||||
using Phantom.Controller.Minecraft;
|
using Phantom.Controller.Minecraft;
|
||||||
@@ -14,16 +15,15 @@ using Phantom.Controller.Services.Agents;
|
|||||||
using Phantom.Controller.Services.Events;
|
using Phantom.Controller.Services.Events;
|
||||||
using Phantom.Controller.Services.Instances;
|
using Phantom.Controller.Services.Instances;
|
||||||
using Phantom.Controller.Services.Users;
|
using Phantom.Controller.Services.Users;
|
||||||
using Phantom.Controller.Services.Users.Sessions;
|
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Rpc.Runtime.Server;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Rpc;
|
namespace Phantom.Controller.Services.Rpc;
|
||||||
|
|
||||||
sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
||||||
public readonly record struct Init(
|
public readonly record struct Init(
|
||||||
RpcServerToClientConnection<IMessageToController, IMessageToWeb> Connection,
|
RpcConnectionToClient<IMessageToWeb> Connection,
|
||||||
WebClientRegistrar WebClientRegistrar,
|
WebRegistrationHandler WebRegistrationHandler,
|
||||||
ControllerState ControllerState,
|
ControllerState ControllerState,
|
||||||
InstanceLogManager InstanceLogManager,
|
InstanceLogManager InstanceLogManager,
|
||||||
UserManager UserManager,
|
UserManager UserManager,
|
||||||
@@ -40,7 +40,8 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
|||||||
return Props<IMessageToController>.Create(() => new WebMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
return Props<IMessageToController>.Create(() => new WebMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly RpcServerToClientConnection<IMessageToController, IMessageToWeb> connection;
|
private readonly RpcConnectionToClient<IMessageToWeb> connection;
|
||||||
|
private readonly WebRegistrationHandler webRegistrationHandler;
|
||||||
private readonly ControllerState controllerState;
|
private readonly ControllerState controllerState;
|
||||||
private readonly UserManager userManager;
|
private readonly UserManager userManager;
|
||||||
private readonly RoleManager roleManager;
|
private readonly RoleManager roleManager;
|
||||||
@@ -53,6 +54,7 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
|||||||
|
|
||||||
private WebMessageHandlerActor(Init init) {
|
private WebMessageHandlerActor(Init init) {
|
||||||
this.connection = init.Connection;
|
this.connection = init.Connection;
|
||||||
|
this.webRegistrationHandler = init.WebRegistrationHandler;
|
||||||
this.controllerState = init.ControllerState;
|
this.controllerState = init.ControllerState;
|
||||||
this.userManager = init.UserManager;
|
this.userManager = init.UserManager;
|
||||||
this.roleManager = init.RoleManager;
|
this.roleManager = init.RoleManager;
|
||||||
@@ -63,35 +65,41 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
|||||||
this.minecraftVersions = init.MinecraftVersions;
|
this.minecraftVersions = init.MinecraftVersions;
|
||||||
this.eventLogManager = init.EventLogManager;
|
this.eventLogManager = init.EventLogManager;
|
||||||
|
|
||||||
var senderActorInit = new WebMessageDataUpdateSenderActor.Init(connection.SendChannel, controllerState, init.InstanceLogManager);
|
var senderActorInit = new WebMessageDataUpdateSenderActor.Init(connection, controllerState, init.InstanceLogManager);
|
||||||
Context.ActorOf(WebMessageDataUpdateSenderActor.Factory(senderActorInit), "DataUpdateSender");
|
Context.ActorOf(WebMessageDataUpdateSenderActor.Factory(senderActorInit), "DataUpdateSender");
|
||||||
|
|
||||||
ReceiveAsync<UnregisterWebMessage>(HandleUnregisterWeb);
|
ReceiveAsync<RegisterWebMessage>(HandleRegisterWeb);
|
||||||
ReceiveAndReplyLater<LogInMessage, Optional<LogInSuccess>>(HandleLogIn);
|
Receive<UnregisterWebMessage>(HandleUnregisterWeb);
|
||||||
|
ReceiveAndReplyLater<LogInMessage, LogInSuccess?>(HandleLogIn);
|
||||||
Receive<LogOutMessage>(HandleLogOut);
|
Receive<LogOutMessage>(HandleLogOut);
|
||||||
ReceiveAndReply<GetAuthenticatedUser, Optional<AuthenticatedUserInfo>>(GetAuthenticatedUser);
|
ReceiveAndReply<GetAuthenticatedUser, Optional<AuthenticatedUserInfo>>(GetAuthenticatedUser);
|
||||||
ReceiveAndReplyLater<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(HandleCreateOrUpdateAdministratorUser);
|
ReceiveAndReplyLater<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(HandleCreateOrUpdateAdministratorUser);
|
||||||
ReceiveAndReplyLater<CreateUserMessage, Result<CreateUserResult, UserActionFailure>>(HandleCreateUser);
|
ReceiveAndReplyLater<CreateUserMessage, CreateUserResult>(HandleCreateUser);
|
||||||
ReceiveAndReplyLater<GetUsersMessage, ImmutableArray<UserInfo>>(HandleGetUsers);
|
ReceiveAndReplyLater<GetUsersMessage, ImmutableArray<UserInfo>>(HandleGetUsers);
|
||||||
ReceiveAndReplyLater<GetRolesMessage, ImmutableArray<RoleInfo>>(HandleGetRoles);
|
ReceiveAndReplyLater<GetRolesMessage, ImmutableArray<RoleInfo>>(HandleGetRoles);
|
||||||
ReceiveAndReplyLater<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(HandleGetUserRoles);
|
ReceiveAndReplyLater<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(HandleGetUserRoles);
|
||||||
ReceiveAndReplyLater<ChangeUserRolesMessage, Result<ChangeUserRolesResult, UserActionFailure>>(HandleChangeUserRoles);
|
ReceiveAndReplyLater<ChangeUserRolesMessage, ChangeUserRolesResult>(HandleChangeUserRoles);
|
||||||
ReceiveAndReplyLater<DeleteUserMessage, Result<DeleteUserResult, UserActionFailure>>(HandleDeleteUser);
|
ReceiveAndReplyLater<DeleteUserMessage, DeleteUserResult>(HandleDeleteUser);
|
||||||
ReceiveAndReplyLater<CreateOrUpdateInstanceMessage, Result<CreateOrUpdateInstanceResult, UserInstanceActionFailure>>(HandleCreateOrUpdateInstance);
|
ReceiveAndReplyLater<CreateOrUpdateInstanceMessage, Result<CreateOrUpdateInstanceResult, InstanceActionFailure>>(HandleCreateOrUpdateInstance);
|
||||||
ReceiveAndReplyLater<LaunchInstanceMessage, Result<LaunchInstanceResult, UserInstanceActionFailure>>(HandleLaunchInstance);
|
ReceiveAndReplyLater<LaunchInstanceMessage, Result<LaunchInstanceResult, InstanceActionFailure>>(HandleLaunchInstance);
|
||||||
ReceiveAndReplyLater<StopInstanceMessage, Result<StopInstanceResult, UserInstanceActionFailure>>(HandleStopInstance);
|
ReceiveAndReplyLater<StopInstanceMessage, Result<StopInstanceResult, InstanceActionFailure>>(HandleStopInstance);
|
||||||
ReceiveAndReplyLater<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, UserInstanceActionFailure>>(HandleSendCommandToInstance);
|
ReceiveAndReplyLater<SendCommandToInstanceMessage, Result<SendCommandToInstanceResult, InstanceActionFailure>>(HandleSendCommandToInstance);
|
||||||
ReceiveAndReplyLater<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(HandleGetMinecraftVersions);
|
ReceiveAndReplyLater<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(HandleGetMinecraftVersions);
|
||||||
ReceiveAndReply<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(HandleGetAgentJavaRuntimes);
|
ReceiveAndReply<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(HandleGetAgentJavaRuntimes);
|
||||||
ReceiveAndReplyLater<GetAuditLogMessage, Result<ImmutableArray<AuditLogItem>, UserActionFailure>>(HandleGetAuditLog);
|
ReceiveAndReplyLater<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(HandleGetAuditLog);
|
||||||
ReceiveAndReplyLater<GetEventLogMessage, Result<ImmutableArray<EventLogItem>, UserActionFailure>>(HandleGetEventLog);
|
ReceiveAndReplyLater<GetEventLogMessage, ImmutableArray<EventLogItem>>(HandleGetEventLog);
|
||||||
|
Receive<ReplyMessage>(HandleReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleUnregisterWeb(UnregisterWebMessage message) {
|
private async Task HandleRegisterWeb(RegisterWebMessage message) {
|
||||||
return connection.CloseSession();
|
await webRegistrationHandler.TryRegisterImpl(connection, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Optional<LogInSuccess>> HandleLogIn(LogInMessage message) {
|
private void HandleUnregisterWeb(UnregisterWebMessage message) {
|
||||||
|
connection.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<LogInSuccess?> HandleLogIn(LogInMessage message) {
|
||||||
return userLoginManager.LogIn(message.Username, message.Password);
|
return userLoginManager.LogIn(message.Username, message.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,15 +108,15 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<AuthenticatedUserInfo> GetAuthenticatedUser(GetAuthenticatedUser message) {
|
private Optional<AuthenticatedUserInfo> GetAuthenticatedUser(GetAuthenticatedUser message) {
|
||||||
return userLoginManager.GetAuthenticatedUser(message.UserGuid, message.AuthToken);
|
return userLoginManager.GetAuthenticatedUser(message.UserGuid, message.SessionToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message) {
|
private Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message) {
|
||||||
return userManager.CreateOrUpdateAdministrator(message.Username, message.Password);
|
return userManager.CreateOrUpdateAdministrator(message.Username, message.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<CreateUserResult, UserActionFailure>> HandleCreateUser(CreateUserMessage message) {
|
private Task<CreateUserResult> HandleCreateUser(CreateUserMessage message) {
|
||||||
return userManager.Create(userLoginManager.GetLoggedInUser(message.AuthToken), message.Username, message.Password);
|
return userManager.Create(message.LoggedInUserGuid, message.Username, message.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message) {
|
private Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message) {
|
||||||
@@ -123,48 +131,28 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
|||||||
return userRoleManager.GetUserRoles(message.UserGuids);
|
return userRoleManager.GetUserRoles(message.UserGuids);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<ChangeUserRolesResult, UserActionFailure>> HandleChangeUserRoles(ChangeUserRolesMessage message) {
|
private Task<ChangeUserRolesResult> HandleChangeUserRoles(ChangeUserRolesMessage message) {
|
||||||
return userRoleManager.ChangeUserRoles(userLoginManager.GetLoggedInUser(message.AuthToken), message.SubjectUserGuid, message.AddToRoleGuids, message.RemoveFromRoleGuids);
|
return userRoleManager.ChangeUserRoles(message.LoggedInUserGuid, message.SubjectUserGuid, message.AddToRoleGuids, message.RemoveFromRoleGuids);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<DeleteUserResult, UserActionFailure>> HandleDeleteUser(DeleteUserMessage message) {
|
private Task<DeleteUserResult> HandleDeleteUser(DeleteUserMessage message) {
|
||||||
return userManager.DeleteByGuid(userLoginManager.GetLoggedInUser(message.AuthToken), message.SubjectUserGuid);
|
return userManager.DeleteByGuid(message.LoggedInUserGuid, message.SubjectUserGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<CreateOrUpdateInstanceResult, UserInstanceActionFailure>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message) {
|
private Task<Result<CreateOrUpdateInstanceResult, InstanceActionFailure>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message) {
|
||||||
return agentManager.DoInstanceAction<AgentActor.CreateOrUpdateInstanceCommand, CreateOrUpdateInstanceResult>(
|
return agentManager.DoInstanceAction<AgentActor.CreateOrUpdateInstanceCommand, CreateOrUpdateInstanceResult>(message.Configuration.AgentGuid, new AgentActor.CreateOrUpdateInstanceCommand(message.LoggedInUserGuid, message.InstanceGuid, message.Configuration));
|
||||||
Permission.CreateInstances,
|
|
||||||
message.AuthToken,
|
|
||||||
message.Configuration.AgentGuid,
|
|
||||||
loggedInUserGuid => new AgentActor.CreateOrUpdateInstanceCommand(loggedInUserGuid, message.InstanceGuid, message.Configuration)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<LaunchInstanceResult, UserInstanceActionFailure>> HandleLaunchInstance(LaunchInstanceMessage message) {
|
private Task<Result<LaunchInstanceResult, InstanceActionFailure>> HandleLaunchInstance(LaunchInstanceMessage message) {
|
||||||
return agentManager.DoInstanceAction<AgentActor.LaunchInstanceCommand, LaunchInstanceResult>(
|
return agentManager.DoInstanceAction<AgentActor.LaunchInstanceCommand, LaunchInstanceResult>(message.AgentGuid, new AgentActor.LaunchInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid));
|
||||||
Permission.ControlInstances,
|
|
||||||
message.AuthToken,
|
|
||||||
message.AgentGuid,
|
|
||||||
loggedInUserGuid => new AgentActor.LaunchInstanceCommand(loggedInUserGuid, message.InstanceGuid)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<StopInstanceResult, UserInstanceActionFailure>> HandleStopInstance(StopInstanceMessage message) {
|
private Task<Result<StopInstanceResult, InstanceActionFailure>> HandleStopInstance(StopInstanceMessage message) {
|
||||||
return agentManager.DoInstanceAction<AgentActor.StopInstanceCommand, StopInstanceResult>(
|
return agentManager.DoInstanceAction<AgentActor.StopInstanceCommand, StopInstanceResult>(message.AgentGuid, new AgentActor.StopInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid, message.StopStrategy));
|
||||||
Permission.ControlInstances,
|
|
||||||
message.AuthToken,
|
|
||||||
message.AgentGuid,
|
|
||||||
loggedInUserGuid => new AgentActor.StopInstanceCommand(loggedInUserGuid, message.InstanceGuid, message.StopStrategy)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<SendCommandToInstanceResult, UserInstanceActionFailure>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) {
|
private Task<Result<SendCommandToInstanceResult, InstanceActionFailure>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) {
|
||||||
return agentManager.DoInstanceAction<AgentActor.SendCommandToInstanceCommand, SendCommandToInstanceResult>(
|
return agentManager.DoInstanceAction<AgentActor.SendCommandToInstanceCommand, SendCommandToInstanceResult>(message.AgentGuid, new AgentActor.SendCommandToInstanceCommand(message.InstanceGuid, message.LoggedInUserGuid, message.Command));
|
||||||
Permission.ControlInstances,
|
|
||||||
message.AuthToken,
|
|
||||||
message.AgentGuid,
|
|
||||||
loggedInUserGuid => new AgentActor.SendCommandToInstanceCommand(loggedInUserGuid, message.InstanceGuid, message.Command)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message) {
|
private Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message) {
|
||||||
@@ -175,11 +163,15 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
|
|||||||
return controllerState.AgentJavaRuntimesByGuid;
|
return controllerState.AgentJavaRuntimesByGuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<ImmutableArray<AuditLogItem>, UserActionFailure>> HandleGetAuditLog(GetAuditLogMessage message) {
|
private Task<ImmutableArray<AuditLogItem>> HandleGetAuditLog(GetAuditLogMessage message) {
|
||||||
return auditLogManager.GetMostRecentItems(userLoginManager.GetLoggedInUser(message.AuthToken), message.Count);
|
return auditLogManager.GetMostRecentItems(message.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<Result<ImmutableArray<EventLogItem>, UserActionFailure>> HandleGetEventLog(GetEventLogMessage message) {
|
private Task<ImmutableArray<EventLogItem>> HandleGetEventLog(GetEventLogMessage message) {
|
||||||
return eventLogManager.GetMostRecentItems(userLoginManager.GetLoggedInUser(message.AuthToken), message.Count);
|
return eventLogManager.GetMostRecentItems(message.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleReply(ReplyMessage message) {
|
||||||
|
connection.Receive(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
using Phantom.Common.Data;
|
||||||
|
using Phantom.Common.Messages.Web;
|
||||||
|
using Phantom.Common.Messages.Web.ToController;
|
||||||
|
using Phantom.Common.Messages.Web.ToWeb;
|
||||||
|
using Phantom.Controller.Minecraft;
|
||||||
|
using Phantom.Controller.Services.Agents;
|
||||||
|
using Phantom.Controller.Services.Events;
|
||||||
|
using Phantom.Controller.Services.Instances;
|
||||||
|
using Phantom.Controller.Services.Users;
|
||||||
|
using Phantom.Utils.Actor;
|
||||||
|
using Phantom.Utils.Logging;
|
||||||
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Phantom.Controller.Services.Rpc;
|
||||||
|
|
||||||
|
sealed class WebRegistrationHandler : IRegistrationHandler<IMessageToWeb, IMessageToController, RegisterWebMessage> {
|
||||||
|
private static readonly ILogger Logger = PhantomLogger.Create<WebRegistrationHandler>();
|
||||||
|
|
||||||
|
private readonly AuthToken webAuthToken;
|
||||||
|
private readonly ControllerState controllerState;
|
||||||
|
private readonly InstanceLogManager instanceLogManager;
|
||||||
|
private readonly UserManager userManager;
|
||||||
|
private readonly RoleManager roleManager;
|
||||||
|
private readonly UserRoleManager userRoleManager;
|
||||||
|
private readonly UserLoginManager userLoginManager;
|
||||||
|
private readonly AuditLogManager auditLogManager;
|
||||||
|
private readonly AgentManager agentManager;
|
||||||
|
private readonly MinecraftVersions minecraftVersions;
|
||||||
|
private readonly EventLogManager eventLogManager;
|
||||||
|
|
||||||
|
public WebRegistrationHandler(AuthToken webAuthToken, ControllerState controllerState, InstanceLogManager instanceLogManager, UserManager userManager, RoleManager roleManager, UserRoleManager userRoleManager, UserLoginManager userLoginManager, AuditLogManager auditLogManager, AgentManager agentManager, MinecraftVersions minecraftVersions, EventLogManager eventLogManager) {
|
||||||
|
this.webAuthToken = webAuthToken;
|
||||||
|
this.controllerState = controllerState;
|
||||||
|
this.userManager = userManager;
|
||||||
|
this.roleManager = roleManager;
|
||||||
|
this.userRoleManager = userRoleManager;
|
||||||
|
this.userLoginManager = userLoginManager;
|
||||||
|
this.auditLogManager = auditLogManager;
|
||||||
|
this.agentManager = agentManager;
|
||||||
|
this.minecraftVersions = minecraftVersions;
|
||||||
|
this.eventLogManager = eventLogManager;
|
||||||
|
this.instanceLogManager = instanceLogManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<Props<IMessageToController>?> IRegistrationHandler<IMessageToWeb, IMessageToController, RegisterWebMessage>.TryRegister(RpcConnectionToClient<IMessageToWeb> connection, RegisterWebMessage message) {
|
||||||
|
return await TryRegisterImpl(connection, message) ? CreateMessageHandlerActorProps(connection) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TryRegisterImpl(RpcConnectionToClient<IMessageToWeb> connection, RegisterWebMessage message) {
|
||||||
|
if (webAuthToken.FixedTimeEquals(message.AuthToken)) {
|
||||||
|
Logger.Information("Web authorized successfully.");
|
||||||
|
await connection.Send(new RegisterWebResultMessage(true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger.Warning("Web failed to authorize, invalid token.");
|
||||||
|
await connection.Send(new RegisterWebResultMessage(false));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Props<IMessageToController> CreateMessageHandlerActorProps(RpcConnectionToClient<IMessageToWeb> connection) {
|
||||||
|
var init = new WebMessageHandlerActor.Init(connection, this, controllerState, instanceLogManager, userManager, roleManager, userRoleManager, userLoginManager, auditLogManager, agentManager, minecraftVersions, eventLogManager);
|
||||||
|
return WebMessageHandlerActor.Factory(init);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,7 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Phantom.Common.Data;
|
|
||||||
using Phantom.Common.Data.Web.AuditLog;
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
using Phantom.Controller.Database;
|
using Phantom.Controller.Database;
|
||||||
using Phantom.Controller.Database.Repositories;
|
using Phantom.Controller.Database.Repositories;
|
||||||
using Phantom.Controller.Services.Users.Sessions;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Users;
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
@@ -15,11 +12,7 @@ sealed class AuditLogManager {
|
|||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ImmutableArray<AuditLogItem>, UserActionFailure>> GetMostRecentItems(LoggedInUser loggedInUser, int count) {
|
public async Task<ImmutableArray<AuditLogItem>> GetMostRecentItems(int count) {
|
||||||
if (!loggedInUser.CheckPermission(Permission.ViewAudit)) {
|
|
||||||
return UserActionFailure.NotAuthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var db = dbProvider.Lazy();
|
await using var db = dbProvider.Lazy();
|
||||||
return await new AuditLogRepository(db).GetMostRecentItems(count, CancellationToken.None);
|
return await new AuditLogRepository(db).GetMostRecentItems(count, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
@@ -36,6 +36,34 @@ sealed class PermissionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PermissionSet> FetchPermissionsForAllUsers(Guid userId) {
|
||||||
|
await using var ctx = dbProvider.Eager();
|
||||||
|
|
||||||
|
var userPermissions = ctx.UserPermissions
|
||||||
|
.Where(up => up.UserGuid == userId)
|
||||||
|
.Select(static up => up.PermissionId);
|
||||||
|
|
||||||
|
var rolePermissions = ctx.UserRoles
|
||||||
|
.Where(ur => ur.UserGuid == userId)
|
||||||
|
.Join(ctx.RolePermissions, static ur => ur.RoleGuid, static rp => rp.RoleGuid, static (ur, rp) => rp.PermissionId);
|
||||||
|
|
||||||
|
return new PermissionSet(await userPermissions.Union(rolePermissions).AsAsyncEnumerable().ToImmutableSetAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PermissionSet> FetchPermissionsForUserId(Guid userId) {
|
||||||
|
await using var ctx = dbProvider.Eager();
|
||||||
|
|
||||||
|
var userPermissions = ctx.UserPermissions
|
||||||
|
.Where(up => up.UserGuid == userId)
|
||||||
|
.Select(static up => up.PermissionId);
|
||||||
|
|
||||||
|
var rolePermissions = ctx.UserRoles
|
||||||
|
.Where(ur => ur.UserGuid == userId)
|
||||||
|
.Join(ctx.RolePermissions, static ur => ur.RoleGuid, static rp => rp.RoleGuid, static (ur, rp) => rp.PermissionId);
|
||||||
|
|
||||||
|
return new PermissionSet(await userPermissions.Union(rolePermissions).AsAsyncEnumerable().ToImmutableSetAsync());
|
||||||
|
}
|
||||||
|
|
||||||
public static ImmutableArray<string> GetMissingPermissionsOrdered(IEnumerable<Permission> allPermissions, ImmutableHashSet<string> existingPermissionIds) {
|
public static ImmutableArray<string> GetMissingPermissionsOrdered(IEnumerable<Permission> allPermissions, ImmutableHashSet<string> existingPermissionIds) {
|
||||||
return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray();
|
return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user