mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 20:47:28 +01:00
Merge pull request #1788 from tgstation/DMAPIFixup [DMDeploy][APIDeploy][NugetDeploy]
DMAPI Custom Events. Fix potential missing `/home` directory in `.deb`. Standardize DMAPI sleeps. Add support for `file2text` redefinitions in DMAPI. Fix event script priorities. Fix creating invalid `trusted.txt` on Linux
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- Usage: Hard to say what exactly this is for, but not including it removes the test icon and breaks vstest.console.exe for some reason -->
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" Condition="'$(TgsTestNoSdk)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" Condition="'$(TgsTestNoSdk)' != 'true'" />
|
||||
<!-- Usage: Dependency mocking for tests -->
|
||||
<!-- Pinned: Be VERY careful about updating https://github.com/moq/moq/issues/1372 -->
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<!-- Usage: MSTest execution -->
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.1" />
|
||||
<!-- Usage: MSTest asserts etc... -->
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+7
-7
@@ -3,21 +3,21 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.2.0</TgsCoreVersion>
|
||||
<TgsCoreVersion>6.3.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.1.0</TgsConfigVersion>
|
||||
<TgsApiVersion>10.1.0</TgsApiVersion>
|
||||
<TgsApiVersion>10.2.0</TgsApiVersion>
|
||||
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
|
||||
<TgsApiLibraryVersion>13.1.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>15.1.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.0.2</TgsDmapiVersion>
|
||||
<TgsInteropVersion>5.8.0</TgsInteropVersion>
|
||||
<TgsApiLibraryVersion>13.2.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>15.2.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.1.0</TgsDmapiVersion>
|
||||
<TgsInteropVersion>5.9.0</TgsInteropVersion>
|
||||
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
|
||||
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
|
||||
<TgsMigratorVersion>2.0.0</TgsMigratorVersion>
|
||||
<TgsNugetNetFramework>netstandard2.0</TgsNugetNetFramework>
|
||||
<TgsNetMajorVersion>8</TgsNetMajorVersion>
|
||||
<!-- Update this frequently with dotnet runtime patches. MAJOR MUST MATCH ABOVE! -->
|
||||
<TgsDotnetRedistUrl>https://download.visualstudio.microsoft.com/download/pr/016c6447-764a-4210-a260-bf7a2880d5c0/a5746437a3862d7803284ae8c2290200/dotnet-hosting-8.0.1-win.exe</TgsDotnetRedistUrl>
|
||||
<TgsDotnetRedistUrl>https://download.visualstudio.microsoft.com/download/pr/98ff0a08-a283-428f-8e54-19841d97154c/8c7d5f9600eadf264f04c82c813b7aab/dotnet-hosting-8.0.2-win.exe</TgsDotnetRedistUrl>
|
||||
<TgsMariaDBRedistVersion>10.11.6</TgsMariaDBRedistVersion>
|
||||
<TgsYarnVersion>1.22.21</TgsYarnVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<!-- This is in it's own file to help incremental building, changing it causes a complete rebuild of the web panel -->
|
||||
<TgsWebpanelVersion>5.5.0</TgsWebpanelVersion>
|
||||
<TgsWebpanelVersion>5.5.1</TgsWebpanelVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"wix": {
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.4",
|
||||
"commands": [
|
||||
"wix"
|
||||
]
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="WixToolset.Sdk/4.0.2" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
|
||||
<Project Sdk="WixToolset.Sdk/4.0.4" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
|
||||
<Import Project="../../../Common.props" />
|
||||
<PropertyGroup>
|
||||
<DefineConstants>ProductVersion=$(TgsCoreVersion);NetMajorVersion=$(TgsNetMajorVersion);DotnetRedistUrl=$(TgsDotnetRedistUrl);MariaDBRedistUrl=https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v$(TgsCoreVersion)/mariadb-$(TgsMariaDBRedistVersion)-winx64.msi</DefineConstants>
|
||||
@@ -24,8 +24,8 @@
|
||||
<Content Include="Theme.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.2" />
|
||||
<PackageReference Include="WixToolset.Netfx.wixext" Version="4.0.2" />
|
||||
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.4" />
|
||||
<PackageReference Include="WixToolset.Netfx.wixext" Version="4.0.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Host.Service.Wix\Tgstation.Server.Host.Service.Wix.wixproj" />
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="4.0.2" />
|
||||
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="4.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="WixToolset.Sdk/4.0.2">
|
||||
<Project Sdk="WixToolset.Sdk/4.0.4">
|
||||
<Import Project="../../../Common.props" />
|
||||
<PropertyGroup>
|
||||
<DefineConstants>ProductVersion=$(TgsCoreVersion)</DefineConstants>
|
||||
@@ -25,8 +25,8 @@
|
||||
</HarvestDirectory>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Heat" Version="4.0.2" />
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.2" />
|
||||
<PackageReference Include="WixToolset.Heat" Version="4.0.4" />
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Host.Service.Wix.Extensions\Tgstation.Server.Host.Service.Wix.Extensions.csproj" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+18
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]")
|
||||
|
||||
@@ -1 +1 @@
|
||||
"5.8.0"
|
||||
"5.9.0"
|
||||
|
||||
@@ -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"
|
||||
|
||||
+37
-1
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -646,5 +646,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("The specified OpenDream version is too old!")]
|
||||
OpenDreamTooOld,
|
||||
|
||||
/// <summary>
|
||||
/// Failed dotnet diagnostics dump.
|
||||
/// </summary>
|
||||
[Description("Could not create dump as dotnet diagnostics threw an exception!")]
|
||||
DotnetDiagnosticsFailure,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<!-- Usage: HTTP constants reference -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<!-- Usage: Decoding the 'nbf' property of JWTs -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.1" />
|
||||
<!-- Usage: Primary JSON library -->
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<!-- Usage: Data model annotating -->
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Connecting to SignalR hubs in API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.2" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "8.0.1",
|
||||
"version": "8.0.2",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
||||
@@ -424,21 +424,22 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <param name="job">The <see cref="CompileJob"/> to clean.</param>
|
||||
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)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
string DmbName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The primary game directory with a trailing directory separator.
|
||||
/// The primary game directory.
|
||||
/// </summary>
|
||||
string Directory { get; }
|
||||
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
protected const string ByondBinPath = "byond/bin";
|
||||
|
||||
/// <summary>
|
||||
/// The path to the cfg directory.
|
||||
/// </summary>
|
||||
protected const string CfgDirectoryName = "cfg";
|
||||
|
||||
/// <summary>
|
||||
/// The name of BYOND's cache directory.
|
||||
/// </summary>
|
||||
const string CacheDirectoryName = "cache";
|
||||
|
||||
/// <summary>
|
||||
/// The path to the cfg directory.
|
||||
/// </summary>
|
||||
const string CfgDirectoryName = "cfg";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the list of trusted .dmb files in the user's BYOND cfg directory.
|
||||
/// </summary>
|
||||
const string TrustedDmbFileName = "trusted.txt";
|
||||
|
||||
/// <summary>
|
||||
/// The first <see cref="Version"/> of BYOND that supports the '-map-threads' parameter on DreamDaemon.
|
||||
/// </summary>
|
||||
static readonly Version MapThreadsVersion = new(515, 1609);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SemaphoreSlim"/> for writing to files in the user's BYOND directory.
|
||||
/// </summary>
|
||||
static readonly SemaphoreSlim UserFilesSemaphore = new(1);
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<IEngineInstallationData> DownloadVersion(EngineVersion version, JobProgressReporter? progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -240,6 +179,13 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <returns>The file name of the DreamDaemon executable.</returns>
|
||||
protected abstract string GetDreamDaemonName(Version byondVersion, out bool supportsCli);
|
||||
|
||||
/// <summary>
|
||||
/// List off additional file paths in the <paramref name="configDirectory"/> to delete.
|
||||
/// </summary>
|
||||
/// <param name="configDirectory">The full path to the relevant <see cref="CfgDirectoryName"/>.</param>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of paths in <paramref name="configDirectory"/> to clean.</returns>
|
||||
protected virtual IEnumerable<string> AdditionalCacheCleanFilePaths(string configDirectory) => Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Uri"/> pointing to the location of the download for a given <paramref name="version"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.UserProfile,
|
||||
Environment.SpecialFolderOption.DoNotVerify),
|
||||
"./byond/cache"));
|
||||
"./.byond/cache"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -119,6 +119,16 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
|
||||
{
|
||||
|
||||
@@ -47,6 +47,16 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// </summary>
|
||||
const string TgsFirewalledDDFile = "TGSFirewalledDD";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the list of trusted .dmb files in the user's BYOND cfg directory.
|
||||
/// </summary>
|
||||
const string TrustedDmbFileName = "trusted.txt";
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SemaphoreSlim"/> for writing to files in the user's BYOND directory.
|
||||
/// </summary>
|
||||
static readonly SemaphoreSlim UserFilesSemaphore = new(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The first version of BYOND to ship with dd.exe on the Windows build.
|
||||
/// </summary>
|
||||
@@ -168,6 +178,49 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
await AddDreamDaemonToFirewall(version, path, true, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the BYOND cfg file that prevents the trusted mode dialog from appearing when launching DreamDaemon.
|
||||
/// </summary>
|
||||
|
||||
@@ -30,6 +30,10 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask? HandleCustomEvent(string eventName, IEnumerable<string?> parameters, CancellationToken cancellationToken)
|
||||
=> configuration.HandleCustomEvent(eventName, parameters, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask HandleEvent(EventType eventType, IEnumerable<string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// The name and order of the scripts the event script the <see cref="EventType"/> runs.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ScriptNames { get; }
|
||||
public string[] ScriptNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EventScriptAttribute"/> class.
|
||||
|
||||
@@ -18,5 +18,14 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
ValueTask HandleEvent(EventType eventType, IEnumerable<string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Handles a given custom event.
|
||||
/// </summary>
|
||||
/// <param name="eventName">The name of the event.</param>
|
||||
/// <param name="parameters">An <see cref="IEnumerable{T}"/> of <see cref="string"/> parameters for the event.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation if the event was triggered successfully, <see langword="null"/> if it matched a TGS event and wasn't executed.</returns>
|
||||
ValueTask? HandleCustomEvent(string eventName, IEnumerable<string?> parameters, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,9 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
/// <inheritdoc />
|
||||
public ValueTask HandleEvent(EventType eventType, IEnumerable<string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask? HandleCustomEvent(string eventName, IEnumerable<string?> parameters, CancellationToken cancellationToken)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,5 +39,10 @@
|
||||
/// DreamDaemon attempting to send a longer bridge message.
|
||||
/// </summary>
|
||||
Chunk,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon requesting a custom event to be triggered.
|
||||
/// </summary>
|
||||
Event,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
/// </summary>
|
||||
public ushort? TopicPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Bridge.CustomEventInvocation"/> being triggered.
|
||||
/// </summary>
|
||||
public CustomEventInvocation? EventInvocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BridgeParameters"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -21,5 +21,10 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
/// The <see cref="ChunkData.SequenceId"/>s missing from a chunked request.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<uint>? MissingChunks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The triggered event ID for <see cref="BridgeCommandType.Event"/> requests.
|
||||
/// </summary>
|
||||
public string? EventId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop.Bridge
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for invoking a custom event.
|
||||
/// </summary>
|
||||
public sealed class CustomEventInvocation
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the event being invoked.
|
||||
/// </summary>
|
||||
public string? EventName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parameters for the invoked event.
|
||||
/// </summary>
|
||||
public ICollection<string?>? Parameters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the DMAPI should be notified when the event compeletes.
|
||||
/// </summary>
|
||||
public bool? NotifyCompletion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,12 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// The <see cref="EventType"/> triggered.
|
||||
/// </summary>
|
||||
/// <remarks>Nullable to prevent ignoring when serializing.</remarks>
|
||||
public EventType? Type { get; }
|
||||
public EventType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The set of parameters.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string?> Parameters { get; }
|
||||
public IReadOnlyCollection<string?>? Parameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EventNotification"/> class.
|
||||
|
||||
@@ -67,5 +67,10 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// Sending a broadcast message.
|
||||
/// </summary>
|
||||
Broadcast,
|
||||
|
||||
/// <summary>
|
||||
/// Notifying about the completion of a custom event.
|
||||
/// </summary>
|
||||
CompleteEvent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// </summary>
|
||||
public ChunkData? Chunk { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The completed custom event ID.
|
||||
/// </summary>
|
||||
public string? EventId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the <see cref="TopicParameters"/> constitute a priority request.
|
||||
/// </summary>
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventId">The <see cref="Guid"/> containig the value of <see cref="EventId"/>.</param>
|
||||
public TopicParameters(Guid eventId)
|
||||
: this(TopicCommandType.CompleteEvent)
|
||||
{
|
||||
EventId = eventId.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopicParameters"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
readonly IDotnetDumpService dotnetDumpService;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IEventConsumer"/> for the <see cref="SessionController"/>.
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TaskCompletionSource"/> that completes when DD makes it's first bridge request.
|
||||
/// </summary>
|
||||
@@ -170,9 +176,9 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
readonly Api.Models.Instance metadata;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="CancellationTokenSource"/> used for the topic send operation made on reattaching.
|
||||
/// A <see cref="CancellationTokenSource"/> used for tasks that should not exceed the lifetime of the session.
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource reattachTopicCts;
|
||||
readonly CancellationTokenSource sessionDurationCts;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="lock"/> <see cref="object"/> for port updates and <see cref="disposed"/>.
|
||||
@@ -204,6 +210,11 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// </summary>
|
||||
volatile Task rebootGate;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> representing calls to <see cref="TriggerCustomEvent(CustomEventInvocation?)"/>.
|
||||
/// </summary>
|
||||
volatile Task customEventProcessingTask;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Task"/> for shutting down the server if it is taking too long after validation.
|
||||
/// </summary>
|
||||
@@ -248,6 +259,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="SessionController"/>.</param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
|
||||
/// <param name="dotnetDumpService">The value of <see cref="dotnetDumpService"/>.</param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="Chunker.Logger"/>.</param>
|
||||
/// <param name="postLifetimeCallback">The <see cref="Func{TResult}"/> returning a <see cref="ValueTask"/> to be run after the <paramref name="process"/> ends.</param>
|
||||
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/>.</param>
|
||||
@@ -265,6 +277,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IDotnetDumpService dotnetDumpService,
|
||||
IEventConsumer eventConsumer,
|
||||
ILogger<SessionController> logger,
|
||||
Func<ValueTask> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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<BridgeParameters, BridgeResponse>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a custom event from a given <paramref name="invocation"/>.
|
||||
/// </summary>
|
||||
/// <param name="invocation">The <see cref="CustomEventInvocation"/>.</param>
|
||||
/// <returns>An appropriate <see cref="BridgeResponse"/>.</returns>
|
||||
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<string>
|
||||
{
|
||||
ReattachInformation.Dmb.Directory,
|
||||
};
|
||||
|
||||
eventParams.AddRange(invocation
|
||||
.Parameters?
|
||||
.Where(param => param != null)
|
||||
.Cast<string>()
|
||||
?? Enumerable.Empty<string>());
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
assemblyInformationProvider,
|
||||
asyncDelayer,
|
||||
dotnetDumpService,
|
||||
eventConsumer,
|
||||
loggerFactory.CreateLogger<SessionController>(),
|
||||
() => LogDDOutput(
|
||||
process,
|
||||
@@ -446,6 +447,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
assemblyInformationProvider,
|
||||
asyncDelayer,
|
||||
dotnetDumpService,
|
||||
eventConsumer,
|
||||
loggerFactory.CreateLogger<SessionController>(),
|
||||
() => ValueTask.CompletedTask,
|
||||
null,
|
||||
|
||||
@@ -71,11 +71,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// <summary>
|
||||
/// Map of <see cref="EventType"/>s to the filename of the event scripts they trigger.
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<EventType, IReadOnlyList<string>> EventTypeScriptFileNameMap { get; } = new Dictionary<EventType, IReadOnlyList<string>>(
|
||||
public static IReadOnlyDictionary<EventType, string[]> EventTypeScriptFileNameMap { get; } = new Dictionary<EventType, string[]>(
|
||||
Enum.GetValues(typeof(EventType))
|
||||
.Cast<EventType>()
|
||||
.Select(
|
||||
eventType => new KeyValuePair<EventType, IReadOnlyList<string>>(
|
||||
eventType => new KeyValuePair<EventType, string[]>(
|
||||
eventType,
|
||||
typeof(EventType)
|
||||
.GetField(eventType.ToString())!
|
||||
@@ -600,70 +600,39 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask HandleEvent(EventType eventType, IEnumerable<string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
|
||||
public ValueTask HandleEvent(EventType eventType, IEnumerable<string?> 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask? HandleCustomEvent(string scriptName, IEnumerable<string?> 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
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -758,5 +727,74 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
throw new InvalidOperationException("Attempted to access file outside of configuration manager!");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a set of given <paramref name="scriptNames"/>.
|
||||
/// </summary>
|
||||
/// <param name="parameters">An <see cref="IEnumerable{T}"/> of <see cref="string"/> parameters for the <paramref name="scriptNames"/>.</param>
|
||||
/// <param name="deploymentPipeline">If this event is part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <param name="scriptNames">The names of the scripts to execute.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
async ValueTask ExecuteEventScripts(IEnumerable<string?> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +507,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
HandleChatResponses(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
ValueTask? IEventConsumer.HandleCustomEvent(string eventName, IEnumerable<string?> parameters, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException("Watchdogs do not support custom events!");
|
||||
|
||||
/// <summary>
|
||||
/// Starts all <see cref="ISessionController"/>s.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,35 +78,35 @@
|
||||
<!-- Usage: git interop -->
|
||||
<PackageReference Include="LibGit2Sharp" Version="0.29.0" />
|
||||
<!-- Usage: JWT injection into HTTP pipeline -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
|
||||
<!-- Usage: Support ""legacy"" Newotonsoft.Json in HTTP pipeline. The rest of our codebase uses Newtonsoft. -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.2" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.2" />
|
||||
<!-- Usage: Generating dumps of dotnet engine processes -->
|
||||
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.505301" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.510501" />
|
||||
<!-- Usage: Database ORM -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
|
||||
<!-- Usage: Automatic migration generation using command line -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- Usage: Sqlite ORM plugin -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
|
||||
<!-- Usage: MSSQL ORM plugin -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.2" />
|
||||
<!-- Usage: POSIX support for syscalls, signals, and symlinks -->
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<!-- Usage: YAML config plugin -->
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
|
||||
<!-- Usage: PostgresSQL ORM plugin -->
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
|
||||
<!-- Usage: GitHub.com interop -->
|
||||
<PackageReference Include="Octokit" Version="9.1.2" />
|
||||
<!-- Usage: MYSQL/MariaDB ORM plugin -->
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
|
||||
<!-- Usage: Discord interop -->
|
||||
<PackageReference Include="Remora.Discord" Version="2023.5.0" />
|
||||
<PackageReference Include="Remora.Discord" Version="2024.1.0" />
|
||||
<!-- Usage: Rich logger builder -->
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<!-- Usage: Async logging plugin -->
|
||||
@@ -128,7 +128,7 @@
|
||||
<!-- Usage: Temporary resolution to compatibility issues with EFCore 7 and .NET 8 -->
|
||||
<PackageReference Include="System.Security.Permissions" Version="8.0.0" />
|
||||
<!-- Usage: .DeleteAsync() support for IQueryable<T>s -->
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="8.101.2.1" />
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="8.102.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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..."
|
||||
|
||||
@@ -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
|
||||
+13
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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 <path>`?
|
||||
- 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$)";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user