diff --git a/SS14.Watchdog/Components/ServerManagement/IServerInstance.cs b/SS14.Watchdog/Components/ServerManagement/IServerInstance.cs
index e55f08b..0a8db7e 100644
--- a/SS14.Watchdog/Components/ServerManagement/IServerInstance.cs
+++ b/SS14.Watchdog/Components/ServerManagement/IServerInstance.cs
@@ -61,5 +61,30 @@ public interface IServerInstance
Task ForceShutdownServerAsync(CancellationToken cancel = default);
Task DoRestartCommandAsync(CancellationToken cancel = default);
+
+ ///
+ /// Instruct that the server instance should be stopped gracefully.
+ /// It will not be restarted automatically after shutdown.
+ ///
+ ///
+ /// The server will be asked to gracefully shut down via the /update end point.
+ ///
+ Task DoStopCommandAsync(ServerInstanceStopCommand stopCommand, CancellationToken cancel = default);
+ }
+
+ ///
+ /// Information about a stop command sent to a server instance.
+ ///
+ ///
+ public sealed class ServerInstanceStopCommand
+ {
+ public ServerInstanceStopReason StopReason;
+ }
+
+ ///
+ public enum ServerInstanceStopReason
+ {
+ Unknown,
+ Maintenance,
}
-}
\ No newline at end of file
+}
diff --git a/SS14.Watchdog/Components/ServerManagement/ServerInstance.Actor.cs b/SS14.Watchdog/Components/ServerManagement/ServerInstance.Actor.cs
index 29ef151..b6a2a4b 100644
--- a/SS14.Watchdog/Components/ServerManagement/ServerInstance.Actor.cs
+++ b/SS14.Watchdog/Components/ServerManagement/ServerInstance.Actor.cs
@@ -40,6 +40,8 @@ public sealed partial class ServerInstance
private int _serverTimeoutNumber;
private int _startNumber;
+ // Server got an explicit stop command, will not be automatically restarted.
+ private bool _stopped;
public async Task StartAsync(string baseServerAddress, CancellationToken cancel)
{
@@ -134,6 +136,9 @@ private async Task RunCommand(Command command, CancellationToken cancel)
case CommandRestart:
await RunCommandRestart(cancel);
break;
+ case CommandStop stop:
+ await RunCommandStop(stop.StopCommand, cancel);
+ break;
case CommandServerPing ping:
await RunCommandServerPing(ping, cancel);
break;
@@ -150,6 +155,12 @@ private async Task RunCommand(Command command, CancellationToken cancel)
private async Task RunCommandRestart(CancellationToken cancel)
{
+ if (_stopped)
+ {
+ _logger.LogDebug("Clearing stopped flag due to manual server restart");
+ _stopped = false;
+ }
+
if (_runningServer == null)
{
_loadFailCount = 0;
@@ -161,6 +172,18 @@ private async Task RunCommandRestart(CancellationToken cancel)
await ForceShutdownServerAsync(cancel);
}
+ private async Task RunCommandStop(ServerInstanceStopCommand stopCommand, CancellationToken cancel)
+ {
+ // TODO: use stopCommand to indicate more extensive error message to error.
+
+ _stopped = true;
+ if (IsRunning)
+ {
+ _logger.LogTrace("Server is running, sending fake update notification to make it stop");
+ await SendUpdateNotificationAsync(cancel);
+ }
+ }
+
private async Task RunCommandUpdateAvailable(CommandUpdateAvailable command, CancellationToken cancel)
{
_updateOnRestart = command.UpdateAvailable;
@@ -171,6 +194,10 @@ private async Task RunCommandUpdateAvailable(CommandUpdateAvailable command, Can
_logger.LogTrace("Server is running, sending update notification.");
await SendUpdateNotificationAsync(cancel);
}
+ else if (_stopped)
+ {
+ _logger.LogInformation("Not restarting server for update as it was manually stopped.");
+ }
else if (_startupFailUpdateWait)
{
_startupFailUpdateWait = false;
@@ -247,8 +274,16 @@ private async Task RunCommandServerExit(CommandServerExit exit, CancellationToke
_loadFailCount = 0;
}
- _logger.LogInformation("{Key}: Restarting server after exit...", Key);
- await StartServer(cancel);
+ if (!_stopped)
+ {
+ _logger.LogInformation("{Key}: Restarting server after exit...", Key);
+ await StartServer(cancel);
+ }
+ else
+ {
+ _logger.LogInformation("{Key}: Not restarting server as it was manually stopped.", Key);
+ _notificationManager.SendNotification($"Server `{Key}` has exited after manual stop request.");
+ }
}
private async Task RunCommandStart(CancellationToken cancel)
@@ -421,6 +456,11 @@ private sealed record CommandStart : Command;
///
private sealed record CommandRestart : Command;
+ ///
+ /// Command to stop the server gracefully, without restarting it afterwards.
+ ///
+ private sealed record CommandStop(ServerInstanceStopCommand StopCommand) : Command;
+
///
/// The server has failed to ping back in time, grab the axe!
///
diff --git a/SS14.Watchdog/Components/ServerManagement/ServerInstance.cs b/SS14.Watchdog/Components/ServerManagement/ServerInstance.cs
index 52d3de0..6bf8af3 100644
--- a/SS14.Watchdog/Components/ServerManagement/ServerInstance.cs
+++ b/SS14.Watchdog/Components/ServerManagement/ServerInstance.cs
@@ -353,6 +353,11 @@ public async Task DoRestartCommandAsync(CancellationToken cancel = default)
await _commandQueue.Writer.WriteAsync(new CommandRestart(), cancel);
}
+ public async Task DoStopCommandAsync(ServerInstanceStopCommand stopCommand, CancellationToken cancel = default)
+ {
+ await _commandQueue.Writer.WriteAsync(new CommandStop(stopCommand), cancel);
+ }
+
public async Task ForceShutdownServerAsync(CancellationToken cancel = default)
{
var proc = _runningServer;
diff --git a/SS14.Watchdog/Controllers/InstanceController.cs b/SS14.Watchdog/Controllers/InstanceController.cs
index 85aa940..3a2356b 100644
--- a/SS14.Watchdog/Controllers/InstanceController.cs
+++ b/SS14.Watchdog/Controllers/InstanceController.cs
@@ -30,6 +30,18 @@ public async Task Restart([FromHeader(Name = "Authorization")] st
return Ok();
}
+ [HttpPost("stop")]
+ public async Task Stop([FromHeader(Name = "Authorization")] string authorization, string key)
+ {
+ if (!TryAuthorize(authorization, key, out var failure, out var instance))
+ {
+ return failure;
+ }
+
+ await instance.DoStopCommandAsync(new ServerInstanceStopCommand());
+ return Ok();
+ }
+
[HttpPost("update")]
public IActionResult Update([FromHeader(Name = "Authorization")] string authorization, string key)
{
@@ -79,4 +91,4 @@ public bool TryAuthorize(string authorization,
return true;
}
}
-}
\ No newline at end of file
+}