diff --git a/README.md b/README.md
index 2efb724ec3..085f8c05db 100644
--- a/README.md
+++ b/README.md
@@ -127,7 +127,7 @@ sudo dpkg --add-architecture i386 \
&& sudo systemctl start tgstation-server
```
-The service will execute as the newly created user: `tgstation-server`.
+The service will execute as the newly created user: `tgstation-server`. You should, ideally, store your instances somewhere under `/home/tgstation-server`.
##### Manual Setup
diff --git a/build/TestCommon.props b/build/TestCommon.props
index 9811a480b8..af63625813 100644
--- a/build/TestCommon.props
+++ b/build/TestCommon.props
@@ -13,14 +13,14 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
-
+
diff --git a/build/Version.props b/build/Version.props
index b0347f6394..c11adcf272 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,21 +3,21 @@
- 6.2.0
+ 6.3.0
5.1.0
- 10.1.0
+ 10.2.0
7.0.0
- 13.1.0
- 15.1.0
- 7.0.2
- 5.8.0
+ 13.2.0
+ 15.2.0
+ 7.1.0
+ 5.9.0
1.4.1
1.2.1
2.0.0
netstandard2.0
8
- https://download.visualstudio.microsoft.com/download/pr/016c6447-764a-4210-a260-bf7a2880d5c0/a5746437a3862d7803284ae8c2290200/dotnet-hosting-8.0.1-win.exe
+ https://download.visualstudio.microsoft.com/download/pr/98ff0a08-a283-428f-8e54-19841d97154c/8c7d5f9600eadf264f04c82c813b7aab/dotnet-hosting-8.0.2-win.exe
10.11.6
1.22.21
diff --git a/build/WebpanelVersion.props b/build/WebpanelVersion.props
index abaf23b16d..4fed7127de 100644
--- a/build/WebpanelVersion.props
+++ b/build/WebpanelVersion.props
@@ -1,6 +1,6 @@
- 5.5.0
+ 5.5.1
diff --git a/build/package/deb/debian/postinst b/build/package/deb/debian/postinst
index 2b98bd204c..28f147a87e 100755
--- a/build/package/deb/debian/postinst
+++ b/build/package/deb/debian/postinst
@@ -1,7 +1,7 @@
#!/bin/sh -e
if [ -z "$2" ]; then
- adduser --system tgstation-server
+ adduser --system --home /home/tgstation-server tgstation-server
mkdir -m 754 -p /var/log/tgstation-server
chown -R tgstation-server /etc/tgstation-server
chown -R tgstation-server /opt/tgstation-server/lib
diff --git a/build/package/winget/.config/dotnet-tools.json b/build/package/winget/.config/dotnet-tools.json
index 14178b0a98..b94eed2541 100644
--- a/build/package/winget/.config/dotnet-tools.json
+++ b/build/package/winget/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"wix": {
- "version": "4.0.2",
+ "version": "4.0.4",
"commands": [
"wix"
]
diff --git a/build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/Tgstation.Server.Host.Service.Wix.Bundle.wixproj b/build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/Tgstation.Server.Host.Service.Wix.Bundle.wixproj
index 827b40f156..af8ce69036 100644
--- a/build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/Tgstation.Server.Host.Service.Wix.Bundle.wixproj
+++ b/build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/Tgstation.Server.Host.Service.Wix.Bundle.wixproj
@@ -1,4 +1,4 @@
-
+
ProductVersion=$(TgsCoreVersion);NetMajorVersion=$(TgsNetMajorVersion);DotnetRedistUrl=$(TgsDotnetRedistUrl);MariaDBRedistUrl=https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v$(TgsCoreVersion)/mariadb-$(TgsMariaDBRedistVersion)-winx64.msi
@@ -24,8 +24,8 @@
-
-
+
+
diff --git a/build/package/winget/Tgstation.Server.Host.Service.Wix.Extensions/Tgstation.Server.Host.Service.Wix.Extensions.csproj b/build/package/winget/Tgstation.Server.Host.Service.Wix.Extensions/Tgstation.Server.Host.Service.Wix.Extensions.csproj
index c7edd3c403..85cbe7e02f 100644
--- a/build/package/winget/Tgstation.Server.Host.Service.Wix.Extensions/Tgstation.Server.Host.Service.Wix.Extensions.csproj
+++ b/build/package/winget/Tgstation.Server.Host.Service.Wix.Extensions/Tgstation.Server.Host.Service.Wix.Extensions.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/build/package/winget/Tgstation.Server.Host.Service.Wix/Tgstation.Server.Host.Service.Wix.wixproj b/build/package/winget/Tgstation.Server.Host.Service.Wix/Tgstation.Server.Host.Service.Wix.wixproj
index 14838b0531..d8ada09e94 100644
--- a/build/package/winget/Tgstation.Server.Host.Service.Wix/Tgstation.Server.Host.Service.Wix.wixproj
+++ b/build/package/winget/Tgstation.Server.Host.Service.Wix/Tgstation.Server.Host.Service.Wix.wixproj
@@ -1,4 +1,4 @@
-
+
ProductVersion=$(TgsCoreVersion)
@@ -25,8 +25,8 @@
-
-
+
+
diff --git a/build/package/winget/manifest/Tgstation.Server.installer.yaml b/build/package/winget/manifest/Tgstation.Server.installer.yaml
index 5d0ed20324..e19c13c9fb 100644
--- a/build/package/winget/manifest/Tgstation.Server.installer.yaml
+++ b/build/package/winget/manifest/Tgstation.Server.installer.yaml
@@ -24,4 +24,4 @@ Installers:
Publisher: /tg/station 13
ReleaseDate: 2023-06-24 # Do not change. Set before publish by push_manifest.ps1
ManifestType: installer
-ManifestVersion: 1.5.0
+ManifestVersion: 1.6.0
diff --git a/build/package/winget/manifest/Tgstation.Server.locale.en-US.yaml b/build/package/winget/manifest/Tgstation.Server.locale.en-US.yaml
index 54556c5d92..a08b33f691 100644
--- a/build/package/winget/manifest/Tgstation.Server.locale.en-US.yaml
+++ b/build/package/winget/manifest/Tgstation.Server.locale.en-US.yaml
@@ -18,4 +18,4 @@ Documentations:
ReleaseNotesUrl: https://github.com/tgstation/tgstation-server/releases/tag/tgstation-server-v0.22.475
PurchaseUrl: https://github.com/sponsors/Cyberboss
ManifestType: defaultLocale
-ManifestVersion: 1.5.0
+ManifestVersion: 1.6.0
diff --git a/build/package/winget/manifest/Tgstation.Server.yaml b/build/package/winget/manifest/Tgstation.Server.yaml
index 09972f148d..2c28c483b4 100644
--- a/build/package/winget/manifest/Tgstation.Server.yaml
+++ b/build/package/winget/manifest/Tgstation.Server.yaml
@@ -5,4 +5,4 @@ PackageIdentifier: Tgstation.Server
PackageVersion: 0.22.475 # Do not change. Set before publish by push_manifest.ps1
DefaultLocale: en-US
ManifestType: version
-ManifestVersion: 1.5.0
+ManifestVersion: 1.6.0
diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm
index fdfec5e8ca..dc49d2c6f0 100644
--- a/src/DMAPI/tgs.dm
+++ b/src/DMAPI/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "7.0.2"
+#define TGS_DMAPI_VERSION "7.1.0"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -50,6 +50,13 @@
#endif
+#ifndef TGS_FILE2TEXT_NATIVE
+#ifdef file2text
+#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You can fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses
+#endif
+#define TGS_FILE2TEXT_NATIVE file2text
+#endif
+
// EVENT CODES
/// Before a reboot mode change, extras parameters are the current and new reboot mode enums.
@@ -489,6 +496,16 @@
/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsChatChannelInfo()
return
+
+/**
+ * Trigger an event in TGS. Requires TGS version >= 6.3.0. Returns [TRUE] if the event was triggered successfully, [FALSE] otherwise. This function may sleep!
+ *
+ * event_name - The name of the event to trigger
+ * parameters - Optional list of string parameters to pass as arguments to the event script. The first parameter passed to a script will always be the running game's directory followed by these parameters.
+ * wait_for_completion - If set, this function will not return until the event has run to completion.
+ */
+/world/proc/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE)
+ return
/*
The MIT License
diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm
index 8be96f2740..15622228e9 100644
--- a/src/DMAPI/tgs/core/core.dm
+++ b/src/DMAPI/tgs/core/core.dm
@@ -166,3 +166,11 @@
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.Visibility()
+
+/world/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE)
+ var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
+ if(api)
+ if(!istype(parameters, /list))
+ parameters = list()
+
+ return api.TriggerEvent(event_name, parameters, wait_for_completion)
diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm
index 07ce3b6845..898516f124 100644
--- a/src/DMAPI/tgs/core/datum.dm
+++ b/src/DMAPI/tgs/core/datum.dm
@@ -17,7 +17,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866
del(world)
world.sleep_offline = FALSE // just in case, this is BYOND after all...
- sleep(1)
+ sleep(world.tick_lag)
TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]")
/datum/tgs_api/latest
@@ -69,3 +69,6 @@ TGS_PROTECT_DATUM(/datum/tgs_api)
/datum/tgs_api/proc/Visibility()
return TGS_UNIMPLEMENTED
+
+/datum/tgs_api/proc/TriggerEvent(event_name, list/parameters, wait_for_completion)
+ return FALSE
diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm
index 945e2e4117..7c87922750 100644
--- a/src/DMAPI/tgs/v4/api.dm
+++ b/src/DMAPI/tgs/v4/api.dm
@@ -181,7 +181,7 @@
var/json = json_encode(data)
while(requesting_new_port && !override_requesting_new_port)
- sleep(1)
+ sleep(world.tick_lag)
//we need some port open at this point to facilitate return communication
if(!world.port)
@@ -209,7 +209,7 @@
requesting_new_port = FALSE
while(export_lock)
- sleep(1)
+ sleep(world.tick_lag)
export_lock = TRUE
last_interop_response = null
@@ -217,7 +217,7 @@
text2file(json, server_commands_json_path)
for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I)
- sleep(1)
+ sleep(world.tick_lag)
if(!last_interop_response)
TGS_ERROR_LOG("Failed to get export result for: [json]")
diff --git a/src/DMAPI/tgs/v5/__interop_version.dm b/src/DMAPI/tgs/v5/__interop_version.dm
index 616263098f..f4806f7adb 100644
--- a/src/DMAPI/tgs/v5/__interop_version.dm
+++ b/src/DMAPI/tgs/v5/__interop_version.dm
@@ -1 +1 @@
-"5.8.0"
+"5.9.0"
diff --git a/src/DMAPI/tgs/v5/_defines.dm b/src/DMAPI/tgs/v5/_defines.dm
index 1c7d67d20c..92c7a8388a 100644
--- a/src/DMAPI/tgs/v5/_defines.dm
+++ b/src/DMAPI/tgs/v5/_defines.dm
@@ -14,6 +14,7 @@
#define DMAPI5_BRIDGE_COMMAND_KILL 4
#define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5
#define DMAPI5_BRIDGE_COMMAND_CHUNK 6
+#define DMAPI5_BRIDGE_COMMAND_EVENT 7
#define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands"
@@ -34,6 +35,7 @@
#define DMAPI5_BRIDGE_PARAMETER_VERSION "version"
#define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage"
#define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel"
+#define DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION "eventInvocation"
#define DMAPI5_BRIDGE_RESPONSE_NEW_PORT "newPort"
#define DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION "runtimeInformation"
@@ -81,6 +83,7 @@
#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9
#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10
#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11
+#define DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT 12
#define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand"
@@ -116,3 +119,9 @@
#define DMAPI5_CUSTOM_CHAT_COMMAND_NAME "name"
#define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText"
#define DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY "adminOnly"
+
+#define DMAPI5_EVENT_ID "eventId"
+
+#define DMAPI5_EVENT_INVOCATION_NAME "eventName"
+#define DMAPI5_EVENT_INVOCATION_PARAMETERS "parameters"
+#define DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION "notifyCompletion"
diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm
index a5c064a8ea..9b64931f8f 100644
--- a/src/DMAPI/tgs/v5/api.dm
+++ b/src/DMAPI/tgs/v5/api.dm
@@ -27,6 +27,8 @@
var/chunked_requests = 0
var/list/chunked_topics = list()
+ var/list/pending_events = list()
+
var/detached = FALSE
/datum/tgs_api/v5/New()
@@ -125,7 +127,7 @@
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep")
logged = TRUE
- sleep(1)
+ sleep(world.tick_lag)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed")
@@ -249,6 +251,40 @@
WaitForReattach(TRUE)
return chat_channels.Copy()
+/datum/tgs_api/v5/TriggerEvent(event_name, list/parameters, wait_for_completion)
+ RequireInitialBridgeResponse()
+ WaitForReattach(TRUE)
+
+ if(interop_version.minor < 9)
+ TGS_WARNING_LOG("Interop version too low for custom events!")
+ return FALSE
+
+ var/str_parameters = list()
+ for(var/i in parameters)
+ str_parameters += "[i]"
+
+ var/list/response = Bridge(DMAPI5_BRIDGE_COMMAND_EVENT, list(DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION = list(DMAPI5_EVENT_INVOCATION_NAME = event_name, DMAPI5_EVENT_INVOCATION_PARAMETERS = str_parameters, DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION = wait_for_completion)))
+ if(!response)
+ return FALSE
+
+ var/event_id = response[DMAPI5_EVENT_ID]
+ if(!event_id)
+ return FALSE
+
+ TGS_DEBUG_LOG("Created event ID: [event_id]")
+ if(!wait_for_completion)
+ return TRUE
+
+ TGS_DEBUG_LOG("Waiting for completion of event ID: [event_id]")
+
+ while(!pending_events[event_id])
+ sleep(world.tick_lag)
+
+ TGS_DEBUG_LOG("Completed wait on event ID: [event_id]")
+ pending_events -= event_id
+
+ return TRUE
+
/datum/tgs_api/v5/proc/DecodeChannels(chat_update_json)
TGS_DEBUG_LOG("DecodeChannels()")
var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS]
diff --git a/src/DMAPI/tgs/v5/bridge.dm b/src/DMAPI/tgs/v5/bridge.dm
index a0ab359876..0c5e701a32 100644
--- a/src/DMAPI/tgs/v5/bridge.dm
+++ b/src/DMAPI/tgs/v5/bridge.dm
@@ -65,7 +65,7 @@
if(detached)
// Wait up to one minute
for(var/i in 1 to 600)
- sleep(1)
+ sleep(world.tick_lag)
if(!detached && (!require_channels || length(chat_channels)))
break
@@ -77,8 +77,11 @@
/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request)
WaitForReattach(FALSE)
+ TGS_DEBUG_LOG("Bridge request start")
// This is an infinite sleep until we get a response
var/export_response = world.Export(bridge_request)
+ TGS_DEBUG_LOG("Bridge request complete")
+
if(!export_response)
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
return
@@ -88,7 +91,7 @@
TGS_ERROR_LOG("Failed bridge request, missing content!")
return
- var/response_json = file2text(content)
+ var/response_json = TGS_FILE2TEXT_NATIVE(content)
if(!response_json)
TGS_ERROR_LOG("Failed bridge request, failed to load content!")
return
diff --git a/src/DMAPI/tgs/v5/topic.dm b/src/DMAPI/tgs/v5/topic.dm
index 05e6c4e1b2..e66edc2720 100644
--- a/src/DMAPI/tgs/v5/topic.dm
+++ b/src/DMAPI/tgs/v5/topic.dm
@@ -176,6 +176,9 @@
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort()
+
+ pending_events.Cut()
+
return reattach_response
if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK)
@@ -276,6 +279,15 @@
TGS_WORLD_ANNOUNCE(message)
return TopicResponse()
+ if(DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT)
+ var/event_id = topic_parameters[DMAPI5_EVENT_ID]
+ if (!istext(event_id))
+ return TopicResponse("Invalid or missing [DMAPI5_EVENT_ID]")
+
+ TGS_DEBUG_LOG("Completing event ID [event_id]...")
+ pending_events[event_id] = TRUE
+ return TopicResponse()
+
return TopicResponse("Unknown command: [command]")
/datum/tgs_api/v5/proc/WorldBroadcast(message)
diff --git a/src/DMAPI/tgs/v5/undefs.dm b/src/DMAPI/tgs/v5/undefs.dm
index d531d4b7b9..237207fdfd 100644
--- a/src/DMAPI/tgs/v5/undefs.dm
+++ b/src/DMAPI/tgs/v5/undefs.dm
@@ -14,6 +14,7 @@
#undef DMAPI5_BRIDGE_COMMAND_KILL
#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
#undef DMAPI5_BRIDGE_COMMAND_CHUNK
+#undef DMAPI5_BRIDGE_COMMAND_EVENT
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
@@ -34,6 +35,7 @@
#undef DMAPI5_BRIDGE_PARAMETER_VERSION
#undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE
#undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL
+#undef DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION
#undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT
#undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION
@@ -81,6 +83,7 @@
#undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK
#undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK
#undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST
+#undef DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT
#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
@@ -116,3 +119,9 @@
#undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME
#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY
+
+#undef DMAPI5_EVENT_ID
+
+#undef DMAPI5_EVENT_INVOCATION_NAME
+#undef DMAPI5_EVENT_INVOCATION_PARAMETERS
+#undef DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION
diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index 28f39a7638..b75c837585 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -646,5 +646,11 @@ namespace Tgstation.Server.Api.Models
///
[Description("The specified OpenDream version is too old!")]
OpenDreamTooOld,
+
+ ///
+ /// Failed dotnet diagnostics dump.
+ ///
+ [Description("Could not create dump as dotnet diagnostics threw an exception!")]
+ DotnetDiagnosticsFailure,
}
}
diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
index 4f64dba5b1..0b93c41fec 100644
--- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
+++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
index 62e15714d5..5cff1a09a8 100644
--- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
+++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
@@ -11,9 +11,9 @@
-
+
-
+
diff --git a/src/Tgstation.Server.Host/.config/dotnet-tools.json b/src/Tgstation.Server.Host/.config/dotnet-tools.json
index 81fe5add42..d9b689bb64 100644
--- a/src/Tgstation.Server.Host/.config/dotnet-tools.json
+++ b/src/Tgstation.Server.Host/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "8.0.1",
+ "version": "8.0.2",
"commands": [
"dotnet-ef"
]
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
index 3b6695313b..d61b2ffc68 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
@@ -424,21 +424,22 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The to clean.
void CleanRegisteredCompileJob(CompileJob job)
{
- async Task HandleCleanup()
+ Task HandleCleanup()
{
- // First kill the GitHub deployment
- var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager(metadata, job);
-
- // DCT: None available
- var deploymentJob = remoteDeploymentManager.MarkInactive(job, CancellationToken.None);
-
- var deleteTask = DeleteCompileJobContent(job.DirectoryName!.Value.ToString(), cleanupCts.Token);
var otherTask = cleanupTask;
async Task WrapThrowableTasks()
{
try
{
+ // First kill the GitHub deployment
+ var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager(metadata, job);
+
+ // DCT: None available
+ var deploymentJob = remoteDeploymentManager.MarkInactive(job, CancellationToken.None);
+
+ var deleteTask = DeleteCompileJobContent(job.DirectoryName!.Value.ToString(), cleanupCts.Token);
+
await ValueTaskExtensions.WhenAll(deleteTask, deploymentJob);
}
catch (Exception ex)
@@ -447,7 +448,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
}
- await Task.WhenAll(otherTask, WrapThrowableTasks());
+ return Task.WhenAll(otherTask, WrapThrowableTasks());
}
lock (jobLockCounts)
diff --git a/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs
index 820796c318..7d5f0fd6a9 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbProvider.cs
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Components.Deployment
string DmbName { get; }
///
- /// The primary game directory with a trailing directory separator.
+ /// The primary game directory.
///
string Directory { get; }
diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs
index 81a68470e7..c7c73e3a23 100644
--- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs
@@ -1,6 +1,7 @@
using System;
+using System.Collections.Generic;
using System.Globalization;
-using System.Text;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -9,7 +10,6 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
-using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -23,31 +23,21 @@ namespace Tgstation.Server.Host.Components.Engine
///
protected const string ByondBinPath = "byond/bin";
+ ///
+ /// The path to the cfg directory.
+ ///
+ protected const string CfgDirectoryName = "cfg";
+
///
/// The name of BYOND's cache directory.
///
const string CacheDirectoryName = "cache";
- ///
- /// The path to the cfg directory.
- ///
- const string CfgDirectoryName = "cfg";
-
- ///
- /// The name of the list of trusted .dmb files in the user's BYOND cfg directory.
- ///
- const string TrustedDmbFileName = "trusted.txt";
-
///
/// The first of BYOND that supports the '-map-threads' parameter on DreamDaemon.
///
static readonly Version MapThreadsVersion = new(515, 1609);
- ///
- /// for writing to files in the user's BYOND directory.
- ///
- static readonly SemaphoreSlim UserFilesSemaphore = new(1);
-
///
protected override EngineType TargetEngineType => EngineType.Byond;
@@ -144,18 +134,10 @@ namespace Tgstation.Server.Host.Components.Engine
localCfgDirectory,
cancellationToken);
- // Delete trusted.txt so it doesn't grow too large
- var trustedFilePath =
- IOManager.ConcatPath(
- localCfgDirectory,
- TrustedDmbFileName);
+ var additionalCleanTasks = AdditionalCacheCleanFilePaths(localCfgDirectory)
+ .Select(path => IOManager.DeleteFile(path, cancellationToken));
- Logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
- var trustedDmbDeleteTask = IOManager.DeleteFile(
- trustedFilePath,
- cancellationToken);
-
- await Task.WhenAll(cacheCleanTask, cfgCreateTask, trustedDmbDeleteTask);
+ await Task.WhenAll(cacheCleanTask, cfgCreateTask, Task.WhenAll(additionalCleanTasks));
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
@@ -163,49 +145,6 @@ namespace Tgstation.Server.Host.Components.Engine
}
}
- ///
- public override async ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
- {
- ArgumentNullException.ThrowIfNull(version);
- ArgumentNullException.ThrowIfNull(fullDmbPath);
-
- var byondDir = PathToUserFolder;
- var cfgDir = IOManager.ConcatPath(
- byondDir,
- CfgDirectoryName);
- var trustedFilePath = IOManager.ConcatPath(
- cfgDir,
- TrustedDmbFileName);
-
- Logger.LogDebug("Adding .dmb ({dmbPath}) to {trustedFilePath}", fullDmbPath, trustedFilePath);
-
- using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
- {
- string trustedFileText;
- var filePreviouslyExisted = await IOManager.FileExists(trustedFilePath, cancellationToken);
- if (filePreviouslyExisted)
- {
- var trustedFileBytes = await IOManager.ReadAllBytes(trustedFilePath, cancellationToken);
- trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
- trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
- }
- else
- trustedFileText = String.Empty;
-
- if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
- return;
-
- trustedFileText = $"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
-
- var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
-
- if (!filePreviouslyExisted)
- await IOManager.CreateDirectory(cfgDir, cancellationToken);
-
- await IOManager.WriteAllBytes(trustedFilePath, newTrustedFileBytes, cancellationToken);
- }
- }
-
///
public override async ValueTask DownloadVersion(EngineVersion version, JobProgressReporter? progressReporter, CancellationToken cancellationToken)
{
@@ -240,6 +179,13 @@ namespace Tgstation.Server.Host.Components.Engine
/// The file name of the DreamDaemon executable.
protected abstract string GetDreamDaemonName(Version byondVersion, out bool supportsCli);
+ ///
+ /// List off additional file paths in the to delete.
+ ///
+ /// The full path to the relevant .
+ /// An of paths in to clean.
+ protected virtual IEnumerable AdditionalCacheCleanFilePaths(string configDirectory) => Enumerable.Empty();
+
///
/// Create a pointing to the location of the download for a given .
///
diff --git a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs
index ef019aa354..fff62b3b3f 100644
--- a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs
@@ -67,7 +67,7 @@ namespace Tgstation.Server.Host.Components.Engine
Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile,
Environment.SpecialFolderOption.DoNotVerify),
- "./byond/cache"));
+ "./.byond/cache"));
}
///
@@ -119,6 +119,16 @@ namespace Tgstation.Server.Host.Components.Engine
return ValueTask.CompletedTask;
}
+ ///
+ public override ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(version);
+ ArgumentNullException.ThrowIfNull(fullDmbPath);
+
+ Logger.LogTrace("No need to trust .dmb path \"{path}\" on POSIX", fullDmbPath);
+ return ValueTask.CompletedTask;
+ }
+
///
protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
{
diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs
index 3b6790cee1..1534bfc500 100644
--- a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs
@@ -47,6 +47,16 @@ namespace Tgstation.Server.Host.Components.Engine
///
const string TgsFirewalledDDFile = "TGSFirewalledDD";
+ ///
+ /// The name of the list of trusted .dmb files in the user's BYOND cfg directory.
+ ///
+ const string TrustedDmbFileName = "trusted.txt";
+
+ ///
+ /// for writing to files in the user's BYOND directory.
+ ///
+ static readonly SemaphoreSlim UserFilesSemaphore = new(1, 1);
+
///
/// The first version of BYOND to ship with dd.exe on the Windows build.
///
@@ -168,6 +178,49 @@ namespace Tgstation.Server.Host.Components.Engine
await AddDreamDaemonToFirewall(version, path, true, cancellationToken);
}
+ ///
+ public override async ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(version);
+ ArgumentNullException.ThrowIfNull(fullDmbPath);
+
+ var byondDir = PathToUserFolder;
+ var cfgDir = IOManager.ConcatPath(
+ byondDir,
+ CfgDirectoryName);
+ var trustedFilePath = IOManager.ConcatPath(
+ cfgDir,
+ TrustedDmbFileName);
+
+ Logger.LogDebug("Adding .dmb ({dmbPath}) to {trustedFilePath}", fullDmbPath, trustedFilePath);
+
+ using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
+ {
+ string trustedFileText;
+ var filePreviouslyExisted = await IOManager.FileExists(trustedFilePath, cancellationToken);
+ if (filePreviouslyExisted)
+ {
+ var trustedFileBytes = await IOManager.ReadAllBytes(trustedFilePath, cancellationToken);
+ trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
+ trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
+ }
+ else
+ trustedFileText = String.Empty;
+
+ if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
+ return;
+
+ trustedFileText = $"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
+
+ var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
+
+ if (!filePreviouslyExisted)
+ await IOManager.CreateDirectory(cfgDir, cancellationToken);
+
+ await IOManager.WriteAllBytes(trustedFilePath, newTrustedFileBytes, cancellationToken);
+ }
+ }
+
///
protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
{
@@ -175,6 +228,19 @@ namespace Tgstation.Server.Host.Components.Engine
return supportsCli ? "dd.exe" : "dreamdaemon.exe";
}
+ ///
+ protected override IEnumerable AdditionalCacheCleanFilePaths(string configDirectory)
+ {
+ // Delete trusted.txt so it doesn't grow too large
+ var trustedFilePath =
+ IOManager.ConcatPath(
+ configDirectory,
+ TrustedDmbFileName);
+
+ Logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
+ yield return trustedFilePath;
+ }
+
///
/// Creates the BYOND cfg file that prevents the trusted mode dialog from appearing when launching DreamDaemon.
///
diff --git a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs
index 559e584a27..fa98e56607 100644
--- a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs
+++ b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs
@@ -30,6 +30,10 @@ namespace Tgstation.Server.Host.Components.Events
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
+ ///
+ public ValueTask? HandleCustomEvent(string eventName, IEnumerable parameters, CancellationToken cancellationToken)
+ => configuration.HandleCustomEvent(eventName, parameters, cancellationToken);
+
///
public async ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
{
diff --git a/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs
index ba93d34cf9..7d772c2318 100644
--- a/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs
+++ b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
namespace Tgstation.Server.Host.Components.Events
{
@@ -12,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Events
///
/// The name and order of the scripts the event script the runs.
///
- public IReadOnlyList ScriptNames { get; }
+ public string[] ScriptNames { get; }
///
/// Initializes a new instance of the class.
diff --git a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs
index 4d268011f1..a12547e8c2 100644
--- a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs
+++ b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs
@@ -18,5 +18,14 @@ namespace Tgstation.Server.Host.Components.Events
/// The for the operation.
/// A representing the running operation.
ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken);
+
+ ///
+ /// Handles a given custom event.
+ ///
+ /// The name of the event.
+ /// An of parameters for the event.
+ /// The for the operation.
+ /// A representing the running operation if the event was triggered successfully, if it matched a TGS event and wasn't executed.
+ ValueTask? HandleCustomEvent(string eventName, IEnumerable parameters, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs
index dde777572a..880a45c9ef 100644
--- a/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs
+++ b/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs
@@ -12,5 +12,9 @@ namespace Tgstation.Server.Host.Components.Events
///
public ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
+
+ ///
+ public ValueTask? HandleCustomEvent(string eventName, IEnumerable parameters, CancellationToken cancellationToken)
+ => ValueTask.CompletedTask;
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs
index 8c18f74c39..287e9aceda 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeCommandType.cs
@@ -39,5 +39,10 @@
/// DreamDaemon attempting to send a longer bridge message.
///
Chunk,
+
+ ///
+ /// DreamDaemon requesting a custom event to be triggered.
+ ///
+ Event,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs
index 1240bc81cb..228c467768 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs
@@ -51,6 +51,11 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
///
public ushort? TopicPort { get; set; }
+ ///
+ /// The being triggered.
+ ///
+ public CustomEventInvocation? EventInvocation { get; set; }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs
index 620241fafb..da3a3bb563 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs
@@ -21,5 +21,10 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
/// The s missing from a chunked request.
///
public IReadOnlyCollection? MissingChunks { get; set; }
+
+ ///
+ /// The triggered event ID for requests.
+ ///
+ public string? EventId { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/CustomEventInvocation.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/CustomEventInvocation.cs
new file mode 100644
index 0000000000..afb6ab66cf
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/CustomEventInvocation.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+
+namespace Tgstation.Server.Host.Components.Interop.Bridge
+{
+ ///
+ /// Parameters for invoking a custom event.
+ ///
+ public sealed class CustomEventInvocation
+ {
+ ///
+ /// The name of the event being invoked.
+ ///
+ public string? EventName { get; set; }
+
+ ///
+ /// The parameters for the invoked event.
+ ///
+ public ICollection? Parameters { get; set; }
+
+ ///
+ /// If the DMAPI should be notified when the event compeletes.
+ ///
+ public bool? NotifyCompletion { get; set; }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs
index 75eb3bc9f4..1284940b12 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs
@@ -15,12 +15,12 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
/// The triggered.
///
/// Nullable to prevent ignoring when serializing.
- public EventType? Type { get; }
+ public EventType Type { get; }
///
/// The set of parameters.
///
- public IReadOnlyCollection Parameters { get; }
+ public IReadOnlyCollection? Parameters { get; }
///
/// Initializes a new instance of the class.
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs
index 286c605d07..c7d21332c3 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicCommandType.cs
@@ -67,5 +67,10 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
/// Sending a broadcast message.
///
Broadcast,
+
+ ///
+ /// Notifying about the completion of a custom event.
+ ///
+ CompleteEvent,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
index bdc8405b02..1b2745ab6a 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
@@ -62,6 +62,11 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
public ChunkData? Chunk { get; }
+ ///
+ /// The completed custom event ID.
+ ///
+ public string? EventId { get; set; }
+
///
/// Whether or not the constitute a priority request.
///
@@ -74,6 +79,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
or TopicCommandType.InstanceRenamed
or TopicCommandType.ChatChannelsUpdate
or TopicCommandType.Broadcast
+ or TopicCommandType.CompleteEvent
or TopicCommandType.ServerRestarted => true,
TopicCommandType.ChatCommand
or TopicCommandType.HealthCheck
@@ -174,6 +180,16 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
Chunk = chunk ?? throw new ArgumentNullException(nameof(chunk));
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The containig the value of .
+ public TopicParameters(Guid eventId)
+ : this(TopicCommandType.CompleteEvent)
+ {
+ EventId = eventId.ToString();
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
index 110506c6e2..e32c251eb5 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
@@ -19,6 +19,7 @@ using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Chat.Commands;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Components.Engine;
+using Tgstation.Server.Host.Components.Events;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Interop.Bridge;
using Tgstation.Server.Host.Components.Interop.Topic;
@@ -159,6 +160,11 @@ namespace Tgstation.Server.Host.Components.Session
///
readonly IDotnetDumpService dotnetDumpService;
+ ///
+ /// The for the .
+ ///
+ readonly IEventConsumer eventConsumer;
+
///
/// The that completes when DD makes it's first bridge request.
///
@@ -170,9 +176,9 @@ namespace Tgstation.Server.Host.Components.Session
readonly Api.Models.Instance metadata;
///
- /// A used for the topic send operation made on reattaching.
+ /// A used for tasks that should not exceed the lifetime of the session.
///
- readonly CancellationTokenSource reattachTopicCts;
+ readonly CancellationTokenSource sessionDurationCts;
///
/// for port updates and .
@@ -204,6 +210,11 @@ namespace Tgstation.Server.Host.Components.Session
///
volatile Task rebootGate;
+ ///
+ /// The representing calls to .
+ ///
+ volatile Task customEventProcessingTask;
+
///
/// for shutting down the server if it is taking too long after validation.
///
@@ -248,6 +259,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The for the .
/// The value of .
/// The value of .
+ /// The value of .
/// The value of .
/// The returning a to be run after the ends.
/// The optional time to wait before failing the .
@@ -265,6 +277,7 @@ namespace Tgstation.Server.Host.Components.Session
IAssemblyInformationProvider assemblyInformationProvider,
IAsyncDelayer asyncDelayer,
IDotnetDumpService dotnetDumpService,
+ IEventConsumer eventConsumer,
ILogger logger,
Func postLifetimeCallback,
uint? startupTimeout,
@@ -285,6 +298,7 @@ namespace Tgstation.Server.Host.Components.Session
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService));
+ this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
apiValidationSession = apiValidate;
@@ -297,12 +311,13 @@ namespace Tgstation.Server.Host.Components.Session
primeTcs = new TaskCompletionSource();
rebootGate = Task.CompletedTask;
+ customEventProcessingTask = Task.CompletedTask;
// Run this asynchronously because we want to try to avoid any effects sending topics to the server while the initial bridge request is processing
// It MAY be the source of a DD crash. See this gist https://gist.github.com/Cyberboss/7776bbeff3a957d76affe0eae95c9f14
// Worth further investigation as to if that sequence of events is a reliable crash vector and opening a BYOND bug if it is
initialBridgeRequestTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- reattachTopicCts = new CancellationTokenSource();
+ sessionDurationCts = new CancellationTokenSource();
TopicSendSemaphore = new FifoSemaphore();
synchronizationLock = new object();
@@ -356,7 +371,7 @@ namespace Tgstation.Server.Host.Components.Session
Logger.LogTrace("Disposing...");
- reattachTopicCts.Cancel();
+ sessionDurationCts.Cancel();
var cancellationToken = CancellationToken.None; // DCT: None available
var semaphoreLockTask = TopicSendSemaphore.Lock(cancellationToken);
@@ -381,13 +396,15 @@ namespace Tgstation.Server.Host.Components.Session
await regularDmbDisposeTask;
chatTrackingContext.Dispose();
- reattachTopicCts.Dispose();
+ sessionDurationCts.Dispose();
if (!released)
await Lifetime; // finish the async callback
(await semaphoreLockTask).Dispose();
TopicSendSemaphore.Dispose();
+
+ await customEventProcessingTask;
}
///
@@ -547,7 +564,7 @@ namespace Tgstation.Server.Host.Components.Session
assemblyInformationProvider.Version,
ReattachInformation.RuntimeInformation!.ServerPort),
true,
- reattachTopicCts.Token);
+ sessionDurationCts.Token);
if (reattachResponse != null)
{
@@ -735,6 +752,8 @@ namespace Tgstation.Server.Host.Components.Session
break;
case BridgeCommandType.Chunk:
return await ProcessChunk(ProcessBridgeCommand, BridgeError, parameters.Chunk, cancellationToken);
+ case BridgeCommandType.Event:
+ return TriggerCustomEvent(parameters.EventInvocation);
case null:
return BridgeError("Missing commandType!");
default:
@@ -1102,5 +1121,81 @@ namespace Tgstation.Server.Host.Components.Session
return fullResponse;
}
+
+ ///
+ /// Trigger a custom event from a given .
+ ///
+ /// The .
+ /// An appropriate .
+ BridgeResponse TriggerCustomEvent(CustomEventInvocation? invocation)
+ {
+ if (invocation == null)
+ return BridgeError("Missing eventInvocation!");
+
+ var eventName = invocation.EventName;
+ if (eventName == null)
+ return BridgeError("Missing eventName!");
+
+ var notifyCompletion = invocation.NotifyCompletion;
+ if (!notifyCompletion.HasValue)
+ return BridgeError("Missing notifyCompletion!");
+
+ var eventParams = new List
+ {
+ ReattachInformation.Dmb.Directory,
+ };
+
+ eventParams.AddRange(invocation
+ .Parameters?
+ .Where(param => param != null)
+ .Cast()
+ ?? Enumerable.Empty());
+
+ var eventId = Guid.NewGuid();
+ Logger.LogInformation("Triggering custom event \"{eventName}\": {eventId}", eventName, eventId);
+
+ var cancellationToken = sessionDurationCts.Token;
+ ValueTask? eventTask = eventConsumer.HandleCustomEvent(eventName, eventParams, cancellationToken);
+
+ async Task ProcessEvent()
+ {
+ try
+ {
+ await eventTask.Value;
+
+ if (notifyCompletion.Value)
+ await SendCommand(
+ new TopicParameters(eventId),
+ cancellationToken);
+ else
+ Logger.LogTrace("Finished custom event {eventId}, not sending notification.", eventId);
+ }
+ catch (OperationCanceledException ex)
+ {
+ Logger.LogDebug(ex, "Custom event invocation {eventId} aborted!", eventId);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning(ex, "Custom event invocation {eventId} errored!", eventId);
+ }
+ }
+
+ if (!eventTask.HasValue)
+ return BridgeError("Event refused to execute due to matching a TGS event!");
+
+ lock (sessionDurationCts)
+ {
+ var previousEventProcessingTask = customEventProcessingTask;
+ var eventProcessingTask = ProcessEvent();
+ customEventProcessingTask = Task.WhenAll(customEventProcessingTask, eventProcessingTask);
+ }
+
+ return new BridgeResponse
+ {
+ EventId = notifyCompletion.Value
+ ? eventId.ToString()
+ : null,
+ };
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
index 4c7fab19b9..5e89fe8cb2 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
@@ -355,6 +355,7 @@ namespace Tgstation.Server.Host.Components.Session
assemblyInformationProvider,
asyncDelayer,
dotnetDumpService,
+ eventConsumer,
loggerFactory.CreateLogger(),
() => LogDDOutput(
process,
@@ -446,6 +447,7 @@ namespace Tgstation.Server.Host.Components.Session
assemblyInformationProvider,
asyncDelayer,
dotnetDumpService,
+ eventConsumer,
loggerFactory.CreateLogger(),
() => ValueTask.CompletedTask,
null,
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
index 8261afe918..f25f515a60 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
@@ -71,11 +71,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
///
/// Map of s to the filename of the event scripts they trigger.
///
- public static IReadOnlyDictionary> EventTypeScriptFileNameMap { get; } = new Dictionary>(
+ public static IReadOnlyDictionary EventTypeScriptFileNameMap { get; } = new Dictionary(
Enum.GetValues(typeof(EventType))
.Cast()
.Select(
- eventType => new KeyValuePair>(
+ eventType => new KeyValuePair(
eventType,
typeof(EventType)
.GetField(eventType.ToString())!
@@ -600,70 +600,39 @@ namespace Tgstation.Server.Host.Components.StaticFiles
public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken);
///
- public async ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
+ public ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
- await EnsureDirectories(cancellationToken);
-
if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptNames))
- return;
-
- // always execute in serial
- using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
{
- var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, false, cancellationToken);
- var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory);
-
- var scriptFiles = files
- .Select(x => ioManager.GetFileName(x))
- .Where(x => scriptNames.Any(
- scriptName => x.StartsWith(scriptName, StringComparison.Ordinal)))
- .ToList();
-
- if (scriptFiles.Count == 0)
- {
- logger.LogTrace("No event scripts starting with \"{scriptName}\" detected", String.Join("\" or \"", scriptNames));
- return;
- }
-
- foreach (var scriptFile in scriptFiles)
- {
- logger.LogTrace("Running event script {scriptFile}...", scriptFile);
- await using (var script = processExecutor.LaunchProcess(
- ioManager.ConcatPath(resolvedScriptsDir, scriptFile),
- resolvedScriptsDir,
- String.Join(
- ' ',
- parameters.Select(arg =>
- {
- if (arg == null)
- return "(NULL)";
-
- if (!arg.Contains(' ', StringComparison.Ordinal))
- return arg;
-
- arg = arg.Replace("\"", "\\\"", StringComparison.Ordinal);
-
- return $"\"{arg}\"";
- })),
- readStandardHandles: true,
- noShellExecute: true))
- using (cancellationToken.Register(() => script.Terminate()))
- {
- if (sessionConfiguration.LowPriorityDeploymentProcesses)
- script.AdjustPriority(false);
-
- var exitCode = await script.Lifetime;
- cancellationToken.ThrowIfCancellationRequested();
- var scriptOutput = await script.GetCombinedOutput(cancellationToken);
- if (exitCode != 0)
- throw new JobException($"Script {scriptFile} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}");
- else
- logger.LogDebug("Script output:{newLine}{scriptOutput}", Environment.NewLine, scriptOutput);
- }
- }
+ logger.LogTrace("No event script for event {event}!", eventType);
+ return ValueTask.CompletedTask;
}
+
+ return ExecuteEventScripts(parameters, deploymentPipeline, cancellationToken, scriptNames);
+ }
+
+ ///
+ public ValueTask? HandleCustomEvent(string scriptName, IEnumerable parameters, CancellationToken cancellationToken)
+ {
+ var scriptNameIsTgsEventName = EventTypeScriptFileNameMap
+ .Values
+ .SelectMany(scriptNames => scriptNames)
+ .Any(tgsScriptName => tgsScriptName.Equals(
+ scriptName,
+ platformIdentifier.IsWindows
+ ? StringComparison.OrdinalIgnoreCase
+ : StringComparison.Ordinal));
+ if (scriptNameIsTgsEventName)
+ {
+ logger.LogWarning("DMAPI attempted to execute TGS reserved event: {eventName}", scriptName);
+ return null;
+ }
+
+#pragma warning disable CA2012 // Use ValueTasks correctly
+ return ExecuteEventScripts(parameters, false, cancellationToken, scriptName);
+#pragma warning restore CA2012 // Use ValueTasks correctly
}
///
@@ -758,5 +727,74 @@ namespace Tgstation.Server.Host.Components.StaticFiles
throw new InvalidOperationException("Attempted to access file outside of configuration manager!");
return resolved;
}
+
+ ///
+ /// Execute a set of given .
+ ///
+ /// An of parameters for the .
+ /// If this event is part of the deployment pipeline.
+ /// The for the operation.
+ /// The names of the scripts to execute.
+ /// A representing the running operation.
+ async ValueTask ExecuteEventScripts(IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken, params string[] scriptNames)
+ {
+ await EnsureDirectories(cancellationToken);
+
+ // always execute in serial
+ using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
+ {
+ var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, false, cancellationToken);
+ var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory);
+
+ var scriptFiles = files
+ .Select(x => ioManager.GetFileName(x))
+ .Where(x => scriptNames.Any(
+ scriptName => x.StartsWith(scriptName, StringComparison.Ordinal)))
+ .ToList();
+
+ if (scriptFiles.Count == 0)
+ {
+ logger.LogTrace("No event scripts starting with \"{scriptName}\" detected", String.Join("\" or \"", scriptNames));
+ return;
+ }
+
+ foreach (var scriptFile in scriptFiles)
+ {
+ logger.LogTrace("Running event script {scriptFile}...", scriptFile);
+ await using (var script = processExecutor.LaunchProcess(
+ ioManager.ConcatPath(resolvedScriptsDir, scriptFile),
+ resolvedScriptsDir,
+ String.Join(
+ ' ',
+ parameters.Select(arg =>
+ {
+ if (arg == null)
+ return "(NULL)";
+
+ if (!arg.Contains(' ', StringComparison.Ordinal))
+ return arg;
+
+ arg = arg.Replace("\"", "\\\"", StringComparison.Ordinal);
+
+ return $"\"{arg}\"";
+ })),
+ readStandardHandles: true,
+ noShellExecute: true))
+ using (cancellationToken.Register(() => script.Terminate()))
+ {
+ if (sessionConfiguration.LowPriorityDeploymentProcesses && deploymentPipeline)
+ script.AdjustPriority(false);
+
+ var exitCode = await script.Lifetime;
+ cancellationToken.ThrowIfCancellationRequested();
+ var scriptOutput = await script.GetCombinedOutput(cancellationToken);
+ if (exitCode != 0)
+ throw new JobException($"Script {scriptFile} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}");
+ else
+ logger.LogDebug("Script output:{newLine}{scriptOutput}", Environment.NewLine, scriptOutput);
+ }
+ }
+ }
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index 8e891f10c3..211249e0df 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -507,6 +507,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
HandleChatResponses(result);
}
+ ///
+ ValueTask? IEventConsumer.HandleCustomEvent(string eventName, IEnumerable parameters, CancellationToken cancellationToken)
+ => throw new NotSupportedException("Watchdogs do not support custom events!");
+
///
/// Starts all s.
///
diff --git a/src/Tgstation.Server.Host/System/DotnetDumpService.cs b/src/Tgstation.Server.Host/System/DotnetDumpService.cs
index f813b44230..de24e5387a 100644
--- a/src/Tgstation.Server.Host/System/DotnetDumpService.cs
+++ b/src/Tgstation.Server.Host/System/DotnetDumpService.cs
@@ -5,6 +5,9 @@ using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Extensions.Logging;
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Host.Jobs;
+
namespace Tgstation.Server.Host.System
{
///
@@ -42,13 +45,20 @@ namespace Tgstation.Server.Host.System
var pid = process.Id;
logger.LogDebug("dotnet-dump requested for PID {pid}...", pid);
var client = new DiagnosticsClient(pid);
- await client.WriteDumpAsync(
- minidump
- ? DumpType.Normal
- : DumpType.Full,
- outputFile,
- false,
- cts.Token);
+ try
+ {
+ await client.WriteDumpAsync(
+ minidump
+ ? DumpType.Normal
+ : DumpType.Full,
+ outputFile,
+ false,
+ cts.Token);
+ }
+ catch (Exception ex)
+ {
+ throw new JobException(ErrorCode.GCoreFailure, ex);
+ }
}
}
}
diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
index 2ea5dcd4ed..7ef4d1c18c 100644
--- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
+++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
@@ -78,35 +78,35 @@
-
+
-
+
-
+
-
+
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
-
+
-
+
-
+
@@ -128,7 +128,7 @@
-
+
diff --git a/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm
index 022088fc67..6eecddfaa7 100644
--- a/tests/DMAPI/BasicOperation/Test.dm
+++ b/tests/DMAPI/BasicOperation/Test.dm
@@ -15,10 +15,31 @@
sleep(50)
world.TgsTargetedChatBroadcast("Sample admin-only message", TRUE)
+ world.log << "params check"
var/list/world_params = world.params
if(!("test" in world_params) || world_params["test"] != "bababooey")
FailTest("Expected parameter test=bababooey but did not receive", "test_fail_reason.txt")
+ world.log << "file check 1"
+ fdel("test_event_output.txt")
+ var/test_data = "nwfiuurhfu"
+ world.TgsTriggerEvent("test_event", list(test_data), TRUE)
+ if(!fexists("test_event_output.txt"))
+ FailTest("Expected test_event_output.txt to exist here", "test_fail_reason.txt")
+
+ var/test_contents = copytext(file2text("test_event_output.txt"), 1, length(test_data) + 1)
+ if(test_contents != test_data)
+ FailTest("Expected test_event_output.txt to contain [test_data] here. Got [test_contents]", "test_fail_reason.txt")
+
+ world.log << "file check 1"
+ fdel("test_event_output.txt")
+
+ var/start_time = world.timeofday
+ world.TgsTriggerEvent("test_event", list("asdf"), FALSE)
+
+ if((world.timeofday - start_time) <= 50 && fexists("test_event_output.txt"))
+ FailTest("Expected test_event_output.txt to not exist here", "test_fail_reason.txt")
+
world.log << "sleep2"
sleep(150)
world.log << "Terminating..."
diff --git a/tests/DMAPI/BasicOperation/test_event-qwer.bat b/tests/DMAPI/BasicOperation/test_event-qwer.bat
new file mode 100644
index 0000000000..7eb6a4dff9
--- /dev/null
+++ b/tests/DMAPI/BasicOperation/test_event-qwer.bat
@@ -0,0 +1,5 @@
+echo "Running test_event script"
+C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -Command "Start-Sleep -Seconds 5"
+cd %1
+cd tests\DMAPI\BasicOperation
+echo %2 > test_event_output.txt
diff --git a/tests/DMAPI/BasicOperation/test_event-qwer.sh b/tests/DMAPI/BasicOperation/test_event-qwer.sh
new file mode 100755
index 0000000000..185bc88fed
--- /dev/null
+++ b/tests/DMAPI/BasicOperation/test_event-qwer.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e
+
+echo "Running test_event script - $1 - $2"
+
+sleep 5
+
+cd $1
+cd tests/DMAPI/BasicOperation
+
+echo $2 > test_event_output.txt
+
diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm
index 90164c7e80..2735c8e0ab 100644
--- a/tests/DMAPI/LongRunning/Test.dm
+++ b/tests/DMAPI/LongRunning/Test.dm
@@ -34,7 +34,7 @@
if(!res)
FailTest("Failed to resource!")
- var/res_contents = file2text(res) // we need a .rsc to be generated
+ var/res_contents = TGS_FILE2TEXT_NATIVE(res) // we need a .rsc to be generated
if(!res_contents)
FailTest("Failed to resource? No contents!")
@@ -203,7 +203,7 @@ var/run_bridge_test
DetachedChatMessageQueuingP2()
/proc/DetachedChatMessageQueuingP2()
- sleep(1)
+ sleep(world.tick_lag)
DetachedChatMessageQueuingP3()
/proc/DetachedChatMessageQueuingP3()
@@ -240,7 +240,7 @@ var/received_health_check = FALSE
DelayCheckDetach()
/proc/DelayCheckDetach()
- sleep(1)
+ sleep(world.tick_lag)
// hack hack, calling world.TgsChatChannelInfo() will try to delay until the channels come back
var/datum/tgs_api/v5/api = TGS_READ_GLOBAL(tgs)
if(length(api.chat_channels))
diff --git a/tests/DMAPI/test_setup.dm b/tests/DMAPI/test_setup.dm
index 56f4c03eb8..ac345230b7 100644
--- a/tests/DMAPI/test_setup.dm
+++ b/tests/DMAPI/test_setup.dm
@@ -32,4 +32,4 @@
text2file(reason, "test_fail_reason.txt")
world.log << "Terminating..."
del(world)
- sleep(1) // https://www.byond.com/forum/post/2894866
+ sleep(world.tick_lag) // https://www.byond.com/forum/post/2894866
diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj
index d6f3aa8e39..87023f4cfd 100644
--- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj
+++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs
index 9ef929701b..eb39799414 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs
@@ -118,7 +118,7 @@ namespace Tgstation.Server.Tests.Live.Instance
await using var memoryStream2 = new MemoryStream(Encoding.UTF8.GetBytes("bbb"));
await configurationClient.Write(staticFile2, memoryStream2, cancellationToken);
- async ValueTask UploadScript(string scriptId)
+ async ValueTask UploadScript(string scriptId, bool basic)
{
var shellScriptExtension = new PlatformIdentifier().IsWindows ? ".bat" : ".sh";
var scriptName = $"{scriptId}{shellScriptExtension}";
@@ -127,15 +127,16 @@ namespace Tgstation.Server.Tests.Live.Instance
Path = $"/EventScripts/{scriptName}"
};
- await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/LongRunning/{scriptName}", false);
+ await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/{(basic ? "BasicOperation" : "LongRunning")}/{scriptName}", false);
await configurationClient.Write(
resourcingScript,
readStream,
cancellationToken);
}
- await UploadScript("PreCompile-GenerateRandomResource");
- await UploadScript("EngineActiveVersionChange-SetupEnv");
+ await UploadScript("PreCompile-GenerateRandomResource", false);
+ await UploadScript("EngineActiveVersionChange-SetupEnv", false);
+ await UploadScript("test_event-qwer", true);
}
return ValueTaskExtensions.WhenAll(
diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
index fd927a684a..4a453d4f51 100644
--- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
+++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
@@ -1485,7 +1485,7 @@ namespace Tgstation.Server.Tests.Live.Instance
var newStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
Assert.IsTrue(newStatus.SoftShutdown.Value || newStatus.Status.Value == WatchdogStatus.Offline);
- var timeout = 20;
+ var timeout = 40;
do
{
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
diff --git a/tgstation-server.sln b/tgstation-server.sln
index fead8a204d..ad10c6fa1b 100644
--- a/tgstation-server.sln
+++ b/tgstation-server.sln
@@ -176,6 +176,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BasicOperation", "BasicOper
tests\DMAPI\BasicOperation\basic operation_test.dme = tests\DMAPI\BasicOperation\basic operation_test.dme
tests\DMAPI\BasicOperation\Config.dm = tests\DMAPI\BasicOperation\Config.dm
tests\DMAPI\BasicOperation\Test.dm = tests\DMAPI\BasicOperation\Test.dm
+ tests\DMAPI\BasicOperation\test_event-qwer.sh = tests\DMAPI\BasicOperation\test_event-qwer.sh
+ tests\DMAPI\BasicOperation\test_event-qwer.bat = tests\DMAPI\BasicOperation\test_event-qwer.bat
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildFail", "BuildFail", "{103C61AB-67D6-46FE-AA47-CC633B88EE0F}"
diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs
index b8d1e86f79..6130bbc17a 100644
--- a/tools/Tgstation.Server.ReleaseNotes/Program.cs
+++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs
@@ -788,7 +788,7 @@ namespace Tgstation.Server.ReleaseNotes
var versionsPropertyGroup = project.Elements().First(x => x.Name == xmlNamespace + "PropertyGroup");
var coreVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsCoreVersion").Value);
- const string BodyForPRSha = "184dccf9de3e3e4abe289a46648af42017ad6f09";
+ const string BodyForPRSha = "bec143988b4b8ddeb586ed97aaf0647803110d98";
var prBody = $@"# Automated Pull Request
This pull request was generated by our [deployment pipeline]({actionUrl}) as a result of the release of [tgstation-server-v{coreVersion}](https://github.com/tgstation/tgstation-server/releases/tag/tgstation-server-v{coreVersion}). Validation was performed as part of the process.
@@ -803,7 +803,7 @@ The user account that created this pull request is available to correct any issu
- Validation is performed as a prerequisite to deployment.
- [x] Have you tested your manifest locally with `winget install --manifest `?
- Manifest installation and uninstallation is performed as a prerequisite to deployment.
-- [x] Does your manifest conform to the [1.5 schema](https://github.com/microsoft/winget-pkgs/tree/master/doc/manifest/schema/1.5.0)?
+- [x] Does your manifest conform to the [1.6 schema](https://github.com/microsoft/winget-pkgs/tree/master/doc/manifest/schema/1.6.0)?
###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-pkgs/pull/$PR_NUMBER_SUBST$)";