diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml
index 82252adb17..4439daea55 100644
--- a/.github/workflows/ci-pipeline.yml
+++ b/.github/workflows/ci-pipeline.yml
@@ -468,11 +468,11 @@ jobs:
sleep 10
set +e
test_output=$(dotnet test -c ${{ matrix.configuration }} --no-build --filter TestCategory=RequiresDatabase --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --collect:"XPlat Code Coverage" --settings ../../build/ci.runsettings --results-directory ../../TestResults)
- succeeded=$?
+ retval=$?
set -e
cd ../..
echo $test_output > ./test_output.txt
- if [[ $retVal -ne 0 ]]; then
+ if [[ $retval -ne 0 ]]; then
echo "succeeded=NO" >> $GITHUB_OUTPUT
else
echo "succeeded=YES" >> $GITHUB_OUTPUT
diff --git a/build/Dockerfile b/build/Dockerfile
index 5c7c7bd05a..a65df7defe 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -24,7 +24,7 @@ WORKDIR /repo/build
COPY build/Common.props Common.props
COPY build/NugetCommon.props NugetCommon.props
COPY build/Version.props Version.props
-COPY build/ControlPanelVersion.props ControlPanelVersion.props
+COPY build/WebpanelVersion.props WebpanelVersion.props
COPY build/SrcCommon.props SrcCommon.props
WORKDIR /repo/src/Tgstation.Server.Host
diff --git a/build/TestCommon.props b/build/TestCommon.props
index 5562510f72..234e06735a 100644
--- a/build/TestCommon.props
+++ b/build/TestCommon.props
@@ -13,7 +13,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/build/Version.props b/build/Version.props
index 0f6cb5b904..3478f0fc6f 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -1,16 +1,16 @@
-
+
- 5.17.3
+ 5.18.0
4.7.1
- 9.13.0
+ 9.14.0
7.0.0
- 12.0.1
- 14.1.0
- 6.6.2
- 5.6.2
+ 12.2.0
+ 14.2.0
+ 6.7.0
+ 5.7.0
1.4.0
1.2.1
1.0.2
diff --git a/build/ControlPanelVersion.props b/build/WebpanelVersion.props
similarity index 80%
rename from build/ControlPanelVersion.props
rename to build/WebpanelVersion.props
index 02d75e7142..3e3a8aa08c 100644
--- a/build/ControlPanelVersion.props
+++ b/build/WebpanelVersion.props
@@ -1,6 +1,6 @@
- 4.26.2
+ 5.1.0
diff --git a/docs/Features.dox b/docs/Features.dox
index 725b26d947..64eca5a3f7 100644
--- a/docs/Features.dox
+++ b/docs/Features.dox
@@ -87,4 +87,5 @@ tgstation-server is a BYOND server managment suite. It includes all the followin
- Functions with all 3 DreamDaemon security levels
- Provides notifications of TGS side events
- Allows specifying the .dmb's minimum required security level
+ - Broadcast messages to clients
*/
diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm
index 0cc106ec9c..b0e97e05e9 100644
--- a/src/DMAPI/tgs.dm
+++ b/src/DMAPI/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "6.6.2"
+#define TGS_DMAPI_VERSION "6.7.0"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
diff --git a/src/DMAPI/tgs/v5/__interop_version.dm b/src/DMAPI/tgs/v5/__interop_version.dm
index 1b52b31d6a..83420d130a 100644
--- a/src/DMAPI/tgs/v5/__interop_version.dm
+++ b/src/DMAPI/tgs/v5/__interop_version.dm
@@ -1 +1 @@
-"5.6.2"
+"5.7.0"
diff --git a/src/DMAPI/tgs/v5/_defines.dm b/src/DMAPI/tgs/v5/_defines.dm
index bdcd4e4dd5..48969c0c7d 100644
--- a/src/DMAPI/tgs/v5/_defines.dm
+++ b/src/DMAPI/tgs/v5/_defines.dm
@@ -80,6 +80,7 @@
#define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8
#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9
#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10
+#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11
#define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand"
@@ -89,6 +90,7 @@
#define DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME "newInstanceName"
#define DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE "chatUpdate"
#define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion"
+#define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage"
diff --git a/src/DMAPI/tgs/v5/topic.dm b/src/DMAPI/tgs/v5/topic.dm
index d7d4712138..2ef0c70a97 100644
--- a/src/DMAPI/tgs/v5/topic.dm
+++ b/src/DMAPI/tgs/v5/topic.dm
@@ -94,7 +94,7 @@
if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
- return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
+ return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
@@ -141,7 +141,7 @@
if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
- return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
+ return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]")
server_port = new_port
return TopicResponse()
@@ -157,7 +157,7 @@
var/error_message = null
if (new_port != null)
if (!isnum(new_port) || !(new_port > 0))
- error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]"
+ error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]"
else
server_port = new_port
@@ -165,7 +165,7 @@
if (!istext(new_version_string))
if(error_message != null)
error_message += ", "
- error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]"
+ error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]"
else
var/datum/tgs_version/new_version = new(new_version_string)
if (event_handler)
@@ -267,4 +267,16 @@
return chunk_to_send
+ if(DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST)
+ var/message = topic_parameters[DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]
+ if (!istext(message))
+ return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]")
+
+ TGS_WORLD_ANNOUNCE(message)
+ return TopicResponse()
+
return TopicResponse("Unknown command: [command]")
+
+/datum/tgs_api/v5/proc/WorldBroadcast(message)
+ set waitfor = FALSE
+ TGS_WORLD_ANNOUNCE(message)
diff --git a/src/DMAPI/tgs/v5/undefs.dm b/src/DMAPI/tgs/v5/undefs.dm
index f163adaaaf..fd1ed7e4cf 100644
--- a/src/DMAPI/tgs/v5/undefs.dm
+++ b/src/DMAPI/tgs/v5/undefs.dm
@@ -78,6 +78,9 @@
#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
#undef DMAPI5_TOPIC_COMMAND_HEALTHCHECK
#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
+#undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK
+#undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK
+#undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST
#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
@@ -87,6 +90,7 @@
#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
+#undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index ae2dd3341c..69f4d8dbed 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -629,6 +629,10 @@ namespace Tgstation.Server.Api.Models
[Description("The deployment took longer than the configured timeout!")]
DeploymentTimeout,
- // This comment is here to remind you that there is one more unused error code above and you should use it first
+ ///
+ /// Sending a broadcast message failed.
+ ///
+ [Description("Could not send broadcast to the DMAPI. This can happen either due to there being an insufficient DMAPI version, a communication failure, or the server being offline.")]
+ BroadcastFailure,
}
}
diff --git a/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs b/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs
index 0398d69935..0e4c51e993 100644
--- a/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs
+++ b/src/Tgstation.Server.Api/Models/Request/DreamDaemonRequest.cs
@@ -7,5 +7,9 @@ namespace Tgstation.Server.Api.Models.Request
///
public sealed class DreamDaemonRequest : DreamDaemonApiBase
{
+ ///
+ /// A to send to the running server's DMAPI for broadcasting. How this is displayed is up to how the DMAPI is integrated in the codebase. Requires interop version >=5.7.0.
+ ///
+ public string? BroadcastMessage { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs
index b13002bf9c..378783bf1e 100644
--- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs
+++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs
@@ -112,5 +112,10 @@ namespace Tgstation.Server.Api.Rights
/// User can change .
///
SetMapThreads = 1 << 19,
+
+ ///
+ /// User can use .
+ ///
+ BroadcastMessage = 1 << 20,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs
index 29be2c9a10..286c605d07 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs
@@ -62,5 +62,10 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
/// Receive additional data for a previous response.
///
ReceiveChunk,
+
+ ///
+ /// Sending a broadcast message.
+ ///
+ Broadcast,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
index 3da7e1c34c..fff5453b0d 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
@@ -42,6 +42,11 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
public string NewInstanceName { get; }
+ ///
+ /// The message to broadcast for requests.
+ ///
+ public string BroadcastMessage { get; }
+
///
/// The for requests.
///
@@ -68,6 +73,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
or TopicCommandType.ChangeRebootState
or TopicCommandType.InstanceRenamed
or TopicCommandType.ChatChannelsUpdate
+ or TopicCommandType.Broadcast
or TopicCommandType.ServerRestarted => true,
TopicCommandType.ChatCommand
or TopicCommandType.HealthCheck
@@ -76,6 +82,26 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
_ => throw new InvalidOperationException($"Invalid value for {nameof(CommandType)}: {CommandType}"),
};
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The created .
+ public static TopicParameters CreateInstanceRenamedTopicParameters(string newInstanceName)
+ => new (
+ newInstanceName ?? throw new ArgumentNullException(nameof(newInstanceName)),
+ TopicCommandType.InstanceRenamed);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The created .
+ public static TopicParameters CreateBroadcastParameters(string broadcastMessage)
+ => new (
+ broadcastMessage ?? throw new ArgumentNullException(nameof(broadcastMessage)),
+ TopicCommandType.Broadcast);
+
///
/// Initializes a new instance of the class.
///
@@ -116,16 +142,6 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
NewRebootState = newRebootState;
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The value of .
- public TopicParameters(string newInstanceName)
- : this(TopicCommandType.InstanceRenamed)
- {
- NewInstanceName = newInstanceName ?? throw new ArgumentNullException(nameof(newInstanceName));
- }
-
///
/// Initializes a new instance of the class.
///
@@ -175,5 +191,28 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
{
CommandType = commandType;
}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The parameter for the property designated by .
+ /// The value of .
+ TopicParameters(string stringCommand, TopicCommandType stringCommandType)
+ : this(stringCommandType)
+ {
+#pragma warning disable IDE0010 // Add missing cases
+ switch (stringCommandType)
+ {
+ case TopicCommandType.InstanceRenamed:
+ NewInstanceName = stringCommand;
+ break;
+ case TopicCommandType.Broadcast:
+ BroadcastMessage = stringCommand;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid string TopicCommandType: {stringCommandType}");
+ }
+#pragma warning restore IDE0010 // Add missing cases
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
index bf0d22fac7..93e05417a3 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
@@ -583,7 +583,9 @@ namespace Tgstation.Server.Host.Components.Session
public async ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
{
ReattachInformation.RuntimeInformation.InstanceName = newInstanceName;
- await SendCommand(new TopicParameters(newInstanceName), cancellationToken);
+ await SendCommand(
+ TopicParameters.CreateInstanceRenamedTopicParameters(newInstanceName),
+ cancellationToken);
}
///
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs
index 360d3fcfc3..2d8d0f5d24 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs
@@ -88,5 +88,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// The for the operation.
/// A representing the running operation.
ValueTask CreateDump(CancellationToken cancellationToken);
+
+ ///
+ /// Send a broadcast to the DMAPI.
+ ///
+ /// The message to broadcast.
+ /// The for the operation.
+ /// A resulting in if the broadcast succeeded., otherwise.
+ ValueTask Broadcast(string message, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index 025f3f27a8..a77496bae2 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -457,6 +457,43 @@ namespace Tgstation.Server.Host.Components.Watchdog
await session.CreateDump(dumpFileName, cancellationToken);
}
+ ///
+ public async ValueTask Broadcast(string message, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(message);
+
+ var activeServer = GetActiveController();
+ if (activeServer == null)
+ {
+ Logger.LogInformation("Attempted broadcast failed, no active server!");
+ return false;
+ }
+
+ if (!activeServer.DMApiAvailable)
+ {
+ Logger.LogInformation("Attempted broadcast failed, no DMAPI!");
+ return false;
+ }
+
+ var minimumRequiredVersion = new Version(5, 7, 0);
+ if (activeServer.DMApiVersion < minimumRequiredVersion)
+ {
+ Logger.LogInformation(
+ "Attempted broadcast failed, insufficient interop version: {interopVersion}. Requires {minimumRequiredVersion}!",
+ activeServer.DMApiVersion,
+ minimumRequiredVersion);
+ return false;
+ }
+
+ Logger.LogInformation("Broadcasting: {message}", message);
+
+ var response = await activeServer.SendCommand(
+ TopicParameters.CreateBroadcastParameters(message),
+ cancellationToken);
+
+ return response != null && response.ErrorMessage == null;
+ }
+
///
async ValueTask IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
{
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index 097e0377d3..8e57d022c5 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -148,7 +148,8 @@ namespace Tgstation.Server.Host.Controllers
| DreamDaemonRights.SetVisibility
| DreamDaemonRights.SetProfiler
| DreamDaemonRights.SetLogOutput
- | DreamDaemonRights.SetMapThreads)]
+ | DreamDaemonRights.SetMapThreads
+ | DreamDaemonRights.BroadcastMessage)]
[ProducesResponseType(typeof(DreamDaemonResponse), 200)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
#pragma warning disable CA1502 // TODO: Decomplexify
@@ -208,36 +209,41 @@ namespace Tgstation.Server.Host.Controllers
return false;
}
+ if (CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
+ || CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
+ || CheckModified(x => x.Port, DreamDaemonRights.SetPort)
+ || CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
+ || CheckModified(x => x.Visibility, DreamDaemonRights.SetVisibility)
+ || (model.SoftRestart.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftRestart))
+ || (model.SoftShutdown.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftShutdown))
+ || (!String.IsNullOrWhiteSpace(model.BroadcastMessage) && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.BroadcastMessage))
+ || CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)
+ || CheckModified(x => x.HealthCheckSeconds, DreamDaemonRights.SetHealthCheckInterval)
+ || CheckModified(x => x.DumpOnHealthCheckRestart, DreamDaemonRights.CreateDump)
+ || CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout)
+ || CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
+ || CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
+ || CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput)
+ || CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads))
+ return Forbid();
+
return await WithComponentInstance(
async instance =>
{
var watchdog = instance.Watchdog;
- var rebootState = watchdog.RebootState;
- var oldSoftRestart = rebootState == RebootState.Restart;
- var oldSoftShutdown = rebootState == RebootState.Shutdown;
-
- if (CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
- || CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
- || CheckModified(x => x.Port, DreamDaemonRights.SetPort)
- || CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
- || CheckModified(x => x.Visibility, DreamDaemonRights.SetVisibility)
- || (model.SoftRestart.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftRestart))
- || (model.SoftShutdown.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftShutdown))
- || CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)
- || CheckModified(x => x.HealthCheckSeconds, DreamDaemonRights.SetHealthCheckInterval)
- || CheckModified(x => x.DumpOnHealthCheckRestart, DreamDaemonRights.CreateDump)
- || CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout)
- || CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
- || CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
- || CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput)
- || CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads))
- return Forbid();
+ if (!String.IsNullOrWhiteSpace(model.BroadcastMessage)
+ && !await watchdog.Broadcast(model.BroadcastMessage, cancellationToken))
+ return Conflict(new ErrorMessageResponse(ErrorCode.BroadcastFailure));
await DatabaseContext.Save(cancellationToken);
// run this second because current may be modified by it
+ // slight race condition with request cancellation, but I CANNOT be assed right now
await watchdog.ChangeSettings(current, cancellationToken);
+ var rebootState = watchdog.RebootState;
+ var oldSoftRestart = rebootState == RebootState.Restart;
+ var oldSoftShutdown = rebootState == RebootState.Shutdown;
if (!oldSoftRestart && model.SoftRestart == true && watchdog.Status == WatchdogStatus.Online)
await watchdog.Restart(true, cancellationToken);
else if (!oldSoftShutdown && model.SoftShutdown == true)
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index a1cdabec96..41effa0094 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -558,7 +558,7 @@ namespace Tgstation.Server.Host.Core
logger.LogTrace("Configuration version: {configVersion}", GeneralConfiguration.CurrentConfigVersion);
logger.LogTrace("DMAPI Interop version: {interopVersion}", DMApiConstants.InteropVersion);
if (controlPanelConfiguration.Enable)
- logger.LogTrace("Web control panel version: {webCPVersion}", MasterVersionsAttribute.Instance.RawControlPanelVersion);
+ logger.LogTrace("Webpanel version: {webCPVersion}", MasterVersionsAttribute.Instance.RawWebpanelVersion);
logger.LogDebug("Starting hosting on port {httpApiPort}...", serverPortProvider.HttpApiPort);
}
diff --git a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs
index c9bfed3eca..d6461713bb 100644
--- a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs
+++ b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Host.Properties
///
/// The of the control panel version built.
///
- public string RawControlPanelVersion { get; }
+ public string RawWebpanelVersion { get; }
///
/// The of the control panel version built.
@@ -46,19 +46,19 @@ namespace Tgstation.Server.Host.Properties
///
/// The value of .
/// The value of .
- /// The value of .
+ /// The value of .
/// The value of .
/// The value of .
public MasterVersionsAttribute(
string rawConfigurationVersion,
string rawInteropVersion,
- string rawControlPanelVersion,
+ string rawWebpanelVersion,
string rawHostWatchdogVersion,
string rawMariaDBRedistVersion)
{
RawConfigurationVersion = rawConfigurationVersion ?? throw new ArgumentNullException(nameof(rawConfigurationVersion));
RawInteropVersion = rawInteropVersion ?? throw new ArgumentNullException(nameof(rawInteropVersion));
- RawControlPanelVersion = rawControlPanelVersion ?? throw new ArgumentNullException(nameof(rawControlPanelVersion));
+ RawWebpanelVersion = rawWebpanelVersion ?? throw new ArgumentNullException(nameof(rawWebpanelVersion));
RawHostWatchdogVersion = rawHostWatchdogVersion ?? throw new ArgumentNullException(nameof(rawHostWatchdogVersion));
RawMariaDBRedistVersion = rawMariaDBRedistVersion ?? throw new ArgumentNullException(nameof(rawMariaDBRedistVersion));
}
diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
index 40e00b73a7..bcca6637c3 100644
--- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
+++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
@@ -22,17 +22,17 @@
$(DefineConstants);NO_WEBPANEL
-
+
-
+
-
+
@@ -47,7 +47,7 @@
<_Parameter1>$(TgsConfigVersion)
<_Parameter2>$(TgsInteropVersion)
- <_Parameter3>$(TgsControlPanelVersion)
+ <_Parameter3>$(TgsWebpanelVersion)
<_Parameter4>$(TgsHostWatchdogVersion)
<_Parameter5>$(TgsMariaDBRedistVersion)
@@ -107,7 +107,7 @@
-
+
diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm
index c2e274f4d4..57b23805bc 100644
--- a/tests/DMAPI/LongRunning/Test.dm
+++ b/tests/DMAPI/LongRunning/Test.dm
@@ -174,6 +174,10 @@ var/run_bridge_test
if(tactics8)
return received_health_check ? "received health check" : "did not receive health check"
+ var/tactics_broadcast = data["tgs_integration_test_tactics_broadcast"]
+ if(tactics_broadcast)
+ return last_tgs_broadcast || "!!NULL!!"
+
var/legalize_nuclear_bombs = data["shadow_wizard_money_gang"]
if(legalize_nuclear_bombs)
text2file("I expect this to remain here for a while", "kajigger.txt")
diff --git a/tests/DMAPI/test_prelude.dm b/tests/DMAPI/test_prelude.dm
index efb84d46ae..6cb666e372 100644
--- a/tests/DMAPI/test_prelude.dm
+++ b/tests/DMAPI/test_prelude.dm
@@ -3,7 +3,8 @@
#define TGS_READ_GLOBAL(Name) global.##Name
#define TGS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value
#define TGS_PROTECT_DATUM(Path)
-#define TGS_WORLD_ANNOUNCE(message) world << ##message
+var/last_tgs_broadcast
+#define TGS_WORLD_ANNOUNCE(message) if(TRUE) { var/__tgs_announce_message_local = ##message; world << __tgs_announce_message_local; last_tgs_broadcast = __tgs_announce_message_local; }
#define TGS_WARNING_LOG(message) world.log << "Warn: [##message]"
#define TGS_NOTIFY_ADMINS(event)
#define TGS_CLIENT_COUNT 0
diff --git a/tests/Tgstation.Server.Host.Tests.Signals/Tgstation.Server.Host.Tests.Signals.csproj b/tests/Tgstation.Server.Host.Tests.Signals/Tgstation.Server.Host.Tests.Signals.csproj
index d515505048..7755d96945 100644
--- a/tests/Tgstation.Server.Host.Tests.Signals/Tgstation.Server.Host.Tests.Signals.csproj
+++ b/tests/Tgstation.Server.Host.Tests.Signals/Tgstation.Server.Host.Tests.Signals.csproj
@@ -1,16 +1,11 @@
-
+
$(TgsFrameworkVersion)
Exe
-
-
-
-
-
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
index 0a67dc8c02..b352bd28d6 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
@@ -211,12 +211,46 @@ namespace Tgstation.Server.Tests.Live.Instance
await RunTest(false);
}
+ async ValueTask BroadcastTest(CancellationToken cancellationToken)
+ {
+ var topicRequestResult = await topicClient.SendTopic(
+ IPAddress.Loopback,
+ $"tgs_integration_test_tactics_broadcast=1",
+ ddPort,
+ cancellationToken);
+
+ Assert.IsNotNull(topicRequestResult);
+ Assert.AreEqual("!!NULL!!", topicRequestResult.StringData);
+
+ const string TestBroadcastMessage = "TGS: THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM!";
+ await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
+ {
+ BroadcastMessage = TestBroadcastMessage,
+ }, cancellationToken);
+
+ topicRequestResult = await topicClient.SendTopic(
+ IPAddress.Loopback,
+ $"tgs_integration_test_tactics_broadcast=1",
+ ddPort,
+ cancellationToken);
+
+ Assert.IsNotNull(topicRequestResult);
+ Assert.AreEqual(TestBroadcastMessage, topicRequestResult.StringData);
+ }
+
async Task InteropTestsForLongRunningDme(CancellationToken cancellationToken)
{
await RegressionTest1686(cancellationToken);
+ await ApiAssert.ThrowsException(() => instanceClient.DreamDaemon.Update(new DreamDaemonRequest
+ {
+ BroadcastMessage = "ksjfdksjf",
+ }, cancellationToken), ErrorCode.BroadcastFailure);
+
await StartAndLeaveRunning(cancellationToken);
+ await BroadcastTest(cancellationToken);
+
await RegressionTest1550(cancellationToken);
var deleteJobTask = TestDeleteByondInstallErrorCasesAndQueing(cancellationToken);
diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs
index 41c48a1196..a87b47b2b1 100644
--- a/tests/Tgstation.Server.Tests/TestVersions.cs
+++ b/tests/Tgstation.Server.Tests/TestVersions.cs
@@ -284,11 +284,11 @@ namespace Tgstation.Server.Tests
[TestMethod]
public void TestControlPanelVersion()
{
- var doc = XDocument.Load("../../../../../build/ControlPanelVersion.props");
+ var doc = XDocument.Load("../../../../../build/WebpanelVersion.props");
var project = doc.Root;
var controlPanelXmlNamespace = project.GetDefaultNamespace();
var controlPanelVersionsPropertyGroup = project.Elements().First(x => x.Name == controlPanelXmlNamespace + "PropertyGroup");
- var versionString = controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsControlPanelVersion").Value;
+ var versionString = controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsWebpanelVersion").Value;
Assert.IsNotNull(versionString);
Assert.IsTrue(Version.TryParse(versionString, out var expected));
diff --git a/tgstation-server.sln b/tgstation-server.sln
index 36d9027e9d..ce57c2ea30 100644
--- a/tgstation-server.sln
+++ b/tgstation-server.sln
@@ -26,7 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6
build\BuildDox.ps1 = build\BuildDox.ps1
build\ci.runsettings = build\ci.runsettings
build\Common.props = build\Common.props
- build\ControlPanelVersion.props = build\ControlPanelVersion.props
build\Dockerfile = build\Dockerfile
build\GenerateMigrations.sh = build\GenerateMigrations.sh
build\logo.svg = build\logo.svg
@@ -39,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6
build\tgstation-server.service = build\tgstation-server.service
build\uac_elevation_manifest.xml = build\uac_elevation_manifest.xml
build\Version.props = build\Version.props
+ build\WebpanelVersion.props = build\WebpanelVersion.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Api", "src\Tgstation.Server.Api\Tgstation.Server.Api.csproj", "{8B4A208D-A48A-4A5D-8B94-E2661138865D}"
diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs
index 21bdbc1bfa..79b594e505 100644
--- a/tools/Tgstation.Server.ReleaseNotes/Program.cs
+++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs
@@ -223,7 +223,7 @@ namespace Tgstation.Server.ReleaseNotes
string prefix;
const string PropsPath = "build/Version.props";
- const string ControlPanelPropsPath = "build/ControlPanelVersion.props";
+ const string ControlPanelPropsPath = "build/WebpanelVersion.props";
var doc = XDocument.Load(PropsPath);
var project = doc.Root;
@@ -246,7 +246,7 @@ namespace Tgstation.Server.ReleaseNotes
var configVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsConfigVersion").Value);
var dmApiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsDmapiVersion").Value);
var interopVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsInteropVersion").Value);
- var webControlVersion = Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsControlPanelVersion").Value);
+ var webControlVersion = Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsWebpanelVersion").Value);
var hostWatchdogVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsHostWatchdogVersion").Value);
if (webControlVersion.Major == 0)
@@ -608,14 +608,14 @@ namespace Tgstation.Server.ReleaseNotes
dict.Add(Component.NugetApi, Parse("TgsApiLibraryVersion"));
dict.Add(Component.NugetClient, Parse("TgsClientVersion"));
- var webVersion = Parse("TgsControlPanelVersion");
+ var webVersion = Parse("TgsWebpanelVersion");
if (webVersion != null)
{
dict.Add(Component.WebControlPanel, webVersion);
}
else
{
- var controlPanelVersionBytes = await RLR(() => client.Repository.Content.GetRawContentByRef(RepoOwner, RepoName, "build/ControlPanelVersion.props", mergeCommit));
+ var controlPanelVersionBytes = await RLR(() => client.Repository.Content.GetRawContentByRef(RepoOwner, RepoName, "build/WebpanelVersion.props", mergeCommit));
using (var ms = new MemoryStream(controlPanelVersionBytes))
doc = XDocument.Load(ms);
@@ -623,7 +623,7 @@ namespace Tgstation.Server.ReleaseNotes
project = doc.Root;
var controlPanelXmlNamespace = project.GetDefaultNamespace();
var controlPanelVersionsPropertyGroup = project.Elements().First(x => x.Name == controlPanelXmlNamespace + "PropertyGroup");
- dict.Add(Component.WebControlPanel, Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsControlPanelVersion").Value));
+ dict.Add(Component.WebControlPanel, Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsWebpanelVersion").Value));
}
}
diff --git a/tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj b/tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj
index 35312949ce..4c87963970 100644
--- a/tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj
+++ b/tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj
@@ -8,7 +8,7 @@
-
+