mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 15:07:03 +01:00
More stuff
This commit is contained in:
@@ -40,6 +40,11 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
public string Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Game folder the results were compiled into
|
||||
/// </summary>
|
||||
public Guid? OutputGuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exit code of DM. If <see langword="null"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -51,12 +49,22 @@ namespace Tgstation.Server.Host.Components
|
||||
/// The <see cref="IInstanceShutdownMethod"/> for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
readonly IInstanceShutdownMethod instanceShutdownMethod;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
/// <summary>
|
||||
/// The <see cref="IDreamDaemonExecutor"/> for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
readonly IDreamDaemonExecutor dreamDaemonExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// Used for write control to class variables
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim semaphore;
|
||||
|
||||
readonly bool autoStart;
|
||||
|
||||
readonly SemaphoreSlim semaphore;
|
||||
|
||||
DreamDaemonLaunchParameters currentLaunchParameters;
|
||||
|
||||
CancellationTokenSource watchdogCancellationTokenSource;
|
||||
@@ -78,6 +86,7 @@ namespace Tgstation.Server.Host.Components
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (watchdogCancellationTokenSource != null)
|
||||
@@ -97,22 +106,7 @@ namespace Tgstation.Server.Host.Components
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
static string SecurityWord(DreamDaemonSecurity securityLevel)
|
||||
{
|
||||
switch (securityLevel)
|
||||
{
|
||||
case DreamDaemonSecurity.Safe:
|
||||
return "safe";
|
||||
case DreamDaemonSecurity.Trusted:
|
||||
return "trusted";
|
||||
case DreamDaemonSecurity.Ultrasafe:
|
||||
return "ultrasafe";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async Task Watchdog(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, CancellationToken cancellationToken)
|
||||
{
|
||||
async Task RunOnce(string executablePath)
|
||||
@@ -121,53 +115,28 @@ namespace Tgstation.Server.Host.Components
|
||||
throw new InvalidOperationException("No byond version installed!");
|
||||
|
||||
await byond.ClearCache(cancellationToken).ConfigureAwait(false);
|
||||
using (var proc = new Process())
|
||||
|
||||
string dmb = null; //TODO
|
||||
var accessToken = cryptographySuite.GetSecureString();
|
||||
var usePrimaryPort = true;
|
||||
|
||||
var ddTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, onSuccessfulStartup, executablePath, dmb, accessToken, usePrimaryPort, cancellationToken);
|
||||
|
||||
await onSuccessfulStartup.Task.ConfigureAwait(false);
|
||||
|
||||
interop.SetRun(usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, accessToken);
|
||||
|
||||
int ddExitCode;
|
||||
try
|
||||
{
|
||||
var accessToken = cryptographySuite.GetSecureString();
|
||||
proc.StartInfo.FileName = executablePath;
|
||||
proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {5}-close -verbose -params \"server_service={3}&server_service_version={4}&{6}={7}\" -{2} -public", DMB, launchParameters.PrimaryPort, SecurityWord(launchParameters.SecurityLevel), accessToken, Application.Version, launchParameters.AllowWebClient ? "-webclient " : String.Empty);
|
||||
|
||||
proc.EnableRaisingEvents = true;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
proc.Exited += (a, b) => tcs.SetResult(null);
|
||||
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
|
||||
await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
|
||||
if (onSuccessfulStartup != null)
|
||||
{
|
||||
onSuccessfulStartup.SetResult(null);
|
||||
onSuccessfulStartup = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (cancellationToken.Register(() => tcs.SetCanceled()))
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!instanceShutdownMethod.GracefulShutdown)
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
onSuccessfulStartup?.SetException(e);
|
||||
throw;
|
||||
}
|
||||
await eventConsumer.HandleEvent(proc.ExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false);
|
||||
ddExitCode = await ddTask.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
interop.SetRun(null, null);
|
||||
}
|
||||
|
||||
await eventConsumer.HandleEvent(ddExitCode != 0 ? EventType.DDCrash : EventType.DDExit, null, cancellationToken).ConfigureAwait(false);
|
||||
};
|
||||
|
||||
do
|
||||
@@ -176,6 +145,7 @@ namespace Tgstation.Server.Host.Components
|
||||
} while (!cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CancelGracefulActions()
|
||||
{
|
||||
lock (this)
|
||||
@@ -185,6 +155,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
Task launchTask;
|
||||
@@ -199,6 +170,7 @@ namespace Tgstation.Server.Host.Components
|
||||
await launchTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Launch(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -220,7 +192,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Restart(bool graceful, CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -246,10 +218,13 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => autoStart ? Launch(currentLaunchParameters, cancellationToken) : Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Terminate(false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Terminate(bool graceful, CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Core;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamDaemonExecutor : IDreamDaemonExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Change a given <paramref name="securityLevel"/> into the appropriate DreamDaemon command line word
|
||||
/// </summary>
|
||||
/// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to change</param>
|
||||
/// <returns>A <see cref="string"/> representation of the command line parameter</returns>
|
||||
static string SecurityWord(DreamDaemonSecurity securityLevel)
|
||||
{
|
||||
switch (securityLevel)
|
||||
{
|
||||
case DreamDaemonSecurity.Safe:
|
||||
return "safe";
|
||||
case DreamDaemonSecurity.Trusted:
|
||||
return "trusted";
|
||||
case DreamDaemonSecurity.Ultrasafe:
|
||||
return "ultrasafe";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture, "Bad DreamDaemon security level: {0}", securityLevel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceShutdownMethod"/> for the <see cref="DreamDaemonExecutor"/>
|
||||
/// </summary>
|
||||
readonly IInstanceShutdownMethod instanceShutdownMethod;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DreamDaemonExecutor"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DreamDaemonExecutor"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceShutdownMethod">The value of <see cref="instanceShutdownMethod"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
public DreamDaemonExecutor(IInstanceShutdownMethod instanceShutdownMethod, IIOManager ioManager)
|
||||
{
|
||||
this.instanceShutdownMethod = instanceShutdownMethod ?? throw new ArgumentNullException(nameof(instanceShutdownMethod));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var proc = new Process())
|
||||
{
|
||||
proc.StartInfo.FileName = dreamDaemonPath;
|
||||
proc.StartInfo.WorkingDirectory = ioManager.GetDirectoryName(dmbPath);
|
||||
proc.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}={5}&{6}={7}&{8}={9}&{10}={11}&{12}={13}\"", dmbPath, usePrimaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel),
|
||||
DreamDaemonParameters.AccessToken, accessToken,
|
||||
DreamDaemonParameters.HostVersion, Application.Version,
|
||||
DreamDaemonParameters.IsSecondaryServer, !usePrimaryPort,
|
||||
DreamDaemonParameters.PrimaryPort, launchParameters.PrimaryPort,
|
||||
DreamDaemonParameters.SecondaryPort, launchParameters.SecondaryPort);
|
||||
|
||||
proc.EnableRaisingEvents = true;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
proc.Exited += (a, b) => tcs.SetResult(null);
|
||||
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
|
||||
await Task.Factory.StartNew(() => proc.WaitForInputIdle(), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
|
||||
if (onSuccessfulStartup != null)
|
||||
{
|
||||
onSuccessfulStartup.SetResult(null);
|
||||
onSuccessfulStartup = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (cancellationToken.Register(() => tcs.SetCanceled()))
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!instanceShutdownMethod.GracefulShutdown)
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
return proc.ExitCode;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
onSuccessfulStartup.SetException(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
static class DreamDaemonParameters
|
||||
{
|
||||
public const string AccessToken = "tgs_key";
|
||||
public const string HostVersion = "tgs_ver";
|
||||
public const string PrimaryPort = "tgs_port1";
|
||||
public const string SecondaryPort = "tgs_port2";
|
||||
public const string IsSecondaryServer = "tgs_second";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For running a DreamDaemon instance
|
||||
/// </summary>
|
||||
interface IDreamDaemonExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Run a dream daemon instance
|
||||
/// </summary>
|
||||
/// <param name="launchParameters">The <see cref="DreamDaemonLaunchParameters"/></param>
|
||||
/// <param name="onSuccessfulStartup">The <see cref="TaskCompletionSource{TResult}"/> that is completed when dream daemon starts without crashing</param>
|
||||
/// <param name="dreamDaemonPath">The path to the dreamdaemon executable</param>
|
||||
/// <param name="dmbPath">The path to the .dmb to run, the working directory will be derived from this</param>
|
||||
/// <param name="accessToken">The access token to be used for communication</param>
|
||||
/// <param name="usePrimaryPort">If the server should open on <see cref="DreamDaemonLaunchParameters.PrimaryPort"/> or <see cref="DreamDaemonLaunchParameters.SecondaryPort"/> of <paramref name="launchParameters"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> representing the lifetime of the process and resulting in the exit code</returns>
|
||||
Task<int> RunDreamDaemon(DreamDaemonLaunchParameters launchParameters, TaskCompletionSource<object> onSuccessfulStartup, string dreamDaemonPath, string dmbPath, string accessToken, bool usePrimaryPort, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
sealed class ServerControlEventArgs : EventArgs
|
||||
{
|
||||
public bool PrimeReboot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If <see langword="true"/> the event will result in a new run of the world
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user