From bd712c5a63b48f5795cbeab42a1549f7a803a149 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 18 Jan 2024 23:28:35 -0500 Subject: [PATCH 001/137] Warn when the topic call timeout is too low There is precedence for this --- .../Configuration/GeneralConfiguration.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index ae084860df..5b4f2a25a7 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -187,6 +187,9 @@ namespace Tgstation.Server.Host.Configuration else if (this.GetCopyDirectoryTaskThrottle() < 1) throw new InvalidOperationException( $"{nameof(DeploymentDirectoryCopyTasksPerCore)} is too large for the CPU core count of {Environment.ProcessorCount} and overflows a 32-bit signed integer. Please lower the value!"); + + if (ByondTopicTimeout <= 1000) + logger.LogWarning("The timeout for sending BYOND topics is very low ({ms}ms). Topic calls may fail to complete at all!"); } } } From 245dd722dc0f07cd51f22c5247a8b8aa7b716df3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 18 Jan 2024 23:36:07 -0500 Subject: [PATCH 002/137] Add missing log formatting parameter Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> --- src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 5b4f2a25a7..bc9c11e2f3 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -189,7 +189,7 @@ namespace Tgstation.Server.Host.Configuration $"{nameof(DeploymentDirectoryCopyTasksPerCore)} is too large for the CPU core count of {Environment.ProcessorCount} and overflows a 32-bit signed integer. Please lower the value!"); if (ByondTopicTimeout <= 1000) - logger.LogWarning("The timeout for sending BYOND topics is very low ({ms}ms). Topic calls may fail to complete at all!"); + logger.LogWarning("The timeout for sending BYOND topics is very low ({ms}ms). Topic calls may fail to complete at all!", ByondTopicTimeout); } } } From f5eda6a31819e61d9c1d5756c3c1f4bdfbd13fa1 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 10:34:29 -0500 Subject: [PATCH 003/137] Fix `HardLinkDmbProvider` using the minimum security level rather than the active one Fixes #1773 --- .../Deployment/HardLinkDmbProvider.cs | 23 +++++++++++++------ .../Components/Watchdog/PosixWatchdog.cs | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs index 98a8361e34..09cdfb31d5 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs @@ -46,12 +46,14 @@ namespace Tgstation.Server.Host.Components.Deployment /// The for the . /// The value of . /// The for the . + /// The launch level. public HardLinkDmbProvider( IDmbProvider baseProvider, IIOManager ioManager, IFilesystemLinkFactory linkFactory, ILogger logger, - GeneralConfiguration generalConfiguration) + GeneralConfiguration generalConfiguration, + DreamDaemonSecurity securityLevel) : base( baseProvider, ioManager, @@ -61,7 +63,7 @@ namespace Tgstation.Server.Host.Components.Deployment cancellationTokenSource = new CancellationTokenSource(); try { - mirroringTask = MirrorSourceDirectory(generalConfiguration.GetCopyDirectoryTaskThrottle(), cancellationTokenSource.Token); + mirroringTask = MirrorSourceDirectory(generalConfiguration.GetCopyDirectoryTaskThrottle(), securityLevel, cancellationTokenSource.Token); } catch { @@ -143,9 +145,10 @@ namespace Tgstation.Server.Host.Components.Deployment /// Mirror the . /// /// The optional maximum number of simultaneous tasks allowed to execute. + /// The launch level. /// The for the operation. /// A resulting in the full path to the mirrored directory. - async Task MirrorSourceDirectory(int? taskThrottle, CancellationToken cancellationToken) + async Task MirrorSourceDirectory(int? taskThrottle, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); var mirrorGuid = Guid.NewGuid(); @@ -157,7 +160,12 @@ namespace Tgstation.Server.Host.Components.Deployment var dest = IOManager.ResolvePath(mirrorGuid.ToString()); using var semaphore = taskThrottle.HasValue ? new SemaphoreSlim(taskThrottle.Value) : null; - await Task.WhenAll(MirrorDirectoryImpl(src, dest, semaphore, cancellationToken)); + await Task.WhenAll(MirrorDirectoryImpl( + src, + dest, + semaphore, + securityLevel, + cancellationToken)); stopwatch.Stop(); logger.LogDebug( @@ -175,14 +183,15 @@ namespace Tgstation.Server.Host.Components.Deployment /// The source directory path. /// The destination directory path. /// Optional used to limit degree of parallelism. + /// The launch level. /// The for the operation. /// A of s representing the running operations. The first returned is always the necessary call to . /// I genuinely don't know how this will work with symlinked files. Waiting for the issue report I guess. - IEnumerable MirrorDirectoryImpl(string src, string dest, SemaphoreSlim? semaphore, CancellationToken cancellationToken) + IEnumerable MirrorDirectoryImpl(string src, string dest, SemaphoreSlim? semaphore, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken) { var dir = new DirectoryInfo(src); Task? subdirCreationTask = null; - var dreamDaemonWillAcceptOutOfDirectorySymlinks = CompileJob.MinimumSecurityLevel == DreamDaemonSecurity.Trusted; + var dreamDaemonWillAcceptOutOfDirectorySymlinks = securityLevel == DreamDaemonSecurity.Trusted; foreach (var subDirectory in dir.EnumerateDirectories()) { var mirroredName = Path.Combine(dest, subDirectory.Name); @@ -216,7 +225,7 @@ namespace Tgstation.Server.Host.Components.Deployment logger.LogDebug("Recreating symlinked directory {name} as hard links...", subDirectory.Name); var checkingSubdirCreationTask = true; - foreach (var copyTask in MirrorDirectoryImpl(subDirectory.FullName, mirroredName, semaphore, cancellationToken)) + foreach (var copyTask in MirrorDirectoryImpl(subDirectory.FullName, mirroredName, semaphore, securityLevel, cancellationToken)) { if (subdirCreationTask == null) { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs index 27874178af..b4c7a74229 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs @@ -95,6 +95,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected override SwappableDmbProvider CreateSwappableDmbProvider(IDmbProvider dmbProvider) - => new HardLinkDmbProvider(dmbProvider, GameIOManager, LinkFactory, Logger, generalConfiguration); + => new HardLinkDmbProvider(dmbProvider, GameIOManager, LinkFactory, Logger, generalConfiguration, ActiveLaunchParameters.SecurityLevel!.Value); } } From 77306fd56b827a2c5abe52ad1c702740555a7f29 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 10:40:47 -0500 Subject: [PATCH 004/137] Closes #1738 --- build/analyzers.ruleset | 6 +++--- src/Tgstation.Server.Host/Swarm/ISwarmService.cs | 2 +- src/Tgstation.Server.Host/Swarm/SwarmService.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/analyzers.ruleset b/build/analyzers.ruleset index e61e800d00..72be43fd43 100644 --- a/build/analyzers.ruleset +++ b/build/analyzers.ruleset @@ -1,4 +1,4 @@ - + @@ -6,7 +6,7 @@ - + @@ -1048,4 +1048,4 @@ - \ No newline at end of file + diff --git a/src/Tgstation.Server.Host/Swarm/ISwarmService.cs b/src/Tgstation.Server.Host/Swarm/ISwarmService.cs index 8e827eb131..a07a8478d4 100644 --- a/src/Tgstation.Server.Host/Swarm/ISwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/ISwarmService.cs @@ -38,6 +38,6 @@ namespace Tgstation.Server.Host.Swarm /// Gets the list of s in the swarm, including the current one. /// /// A of s in the swarm. If the server is not part of a swarm, will be returned. - ICollection? GetSwarmServers(); + List? GetSwarmServers(); } } diff --git a/src/Tgstation.Server.Host/Swarm/SwarmService.cs b/src/Tgstation.Server.Host/Swarm/SwarmService.cs index 0ff0131c72..3e9ac7d151 100644 --- a/src/Tgstation.Server.Host/Swarm/SwarmService.cs +++ b/src/Tgstation.Server.Host/Swarm/SwarmService.cs @@ -349,7 +349,7 @@ namespace Tgstation.Server.Host.Swarm } /// - public ICollection? GetSwarmServers() + public List? GetSwarmServers() { if (!SwarmMode) return null; From ff4cdb156cf143a67117aae478102deaa584be68 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 10:44:26 -0500 Subject: [PATCH 005/137] Closes #1737 --- src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index e15b51557d..136c3c24e7 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Security { this.identity = identity ?? throw new ArgumentNullException(nameof(identity)); if (identity.IsAnonymous) - throw new InvalidOperationException($"Cannot use anonymous {nameof(WindowsIdentity)} as a {nameof(WindowsSystemIdentity)}!"); + throw new ArgumentException($"Cannot use anonymous {nameof(WindowsIdentity)} as a {nameof(WindowsSystemIdentity)}!", nameof(identity)); canCreateSymlinks = new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator); } @@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Security } // can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation - throw new InvalidOperationException("Cannot clone a UserPrincipal based WindowsSystemIdentity!"); + throw new NotSupportedException("Cannot clone a UserPrincipal based WindowsSystemIdentity!"); } /// @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Security { ArgumentNullException.ThrowIfNull(action); if (identity == null) - throw new InvalidOperationException("Impersonate using a UserPrincipal based WindowsSystemIdentity!"); + throw new NotSupportedException("Impersonate using a UserPrincipal based WindowsSystemIdentity!"); WindowsIdentity.RunImpersonated(identity.AccessToken, action); }, cancellationToken, From c667d2365d54cd895d22dc37d5ae15f8b873edb1 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 10:45:42 -0500 Subject: [PATCH 006/137] Version bump to 6.1.3 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index da62ceb40a..f281ec3d80 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.1.2 + 6.1.3 5.0.0 10.0.0 7.0.0 From 4d73cf2bb7cdfc4f15a69d8c5dad1861d90c3fba Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 18:21:46 -0500 Subject: [PATCH 007/137] Disable trailing slash redirect for webpanel Fixes #1761 --- .../Controllers/ControlPanelController.cs | 7 +++- .../Controllers/RootController.cs | 36 +++++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 3 +- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs index 7e450874f5..ec763dd955 100644 --- a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs @@ -30,6 +30,11 @@ namespace Tgstation.Server.Host.Controllers /// public const string ControlPanelRoute = "/app"; + /// + /// The route to the control panel channel .json. + /// + public const string ChannelJsonRoute = "channel.json"; + /// /// Header for forcing channel.json to be fetched. /// @@ -70,7 +75,7 @@ namespace Tgstation.Server.Host.Controllers /// Returns the . /// /// A with the . - [Route("channel.json")] + [Route(ChannelJsonRoute)] [HttpGet] public IActionResult GetChannelJson() { diff --git a/src/Tgstation.Server.Host/Controllers/RootController.cs b/src/Tgstation.Server.Host/Controllers/RootController.cs index 32a7987857..df363970bb 100644 --- a/src/Tgstation.Server.Host/Controllers/RootController.cs +++ b/src/Tgstation.Server.Host/Controllers/RootController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; @@ -61,6 +62,27 @@ namespace Tgstation.Server.Host.Controllers /// readonly ControlPanelConfiguration controlPanelConfiguration; + /// + /// Gets a giving the and action names for a given . + /// + /// An expression invoking the action on . + /// A containing the controller and action names. + static Tuple GetControlPanelActionLink(Expression> actionExpression) + { + var memberSelectorExpression = (MethodCallExpression)actionExpression.Body; + var method = memberSelectorExpression.Method; + + var controllerName = typeof(ControlPanelController).Name; + + const string ControllerSuffix = nameof(Controller); + if (controllerName.EndsWith(ControllerSuffix, StringComparison.Ordinal)) + controllerName = controllerName.Substring(0, controllerName.Length - ControllerSuffix.Length); + + var actionName = method.Name; + + return Tuple.Create(controllerName, actionName); + } + /// /// Initializes a new instance of the class. /// @@ -136,5 +158,19 @@ namespace Tgstation.Server.Host.Controllers return (IActionResult?)this.TryServeFile(hostEnvironment, logger, $"{logoFileName}.svg") ?? NotFound(); } + + /// + /// Workaround for the webpanel always expecting a trailing slash. + /// + /// A to the . + [HttpGet(ControlPanelController.ChannelJsonRoute)] + public RedirectToActionResult WebpanelChannelRedirect() + { + var actionTuple = GetControlPanelActionLink(controller => controller.GetChannelJson()); + + return RedirectToActionPermanent( + actionTuple.Item2, + actionTuple.Item1); + } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 702ab054da..176c946e05 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Frozen; using System.Collections.Generic; using System.Globalization; @@ -524,6 +524,7 @@ namespace Tgstation.Server.Host.Core RequestPath = ControlPanelController.ControlPanelRoute, EnableDefaultFiles = true, EnableDirectoryBrowsing = false, + RedirectToAppendTrailingSlash = false, }); } else From 6f80660d256c0b217909ffac5e7c2debb7197d4f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 18:22:13 -0500 Subject: [PATCH 008/137] Make swagger UI respect public path --- src/Tgstation.Server.Host/Core/Application.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 176c946e05..d533a167a0 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Frozen; using System.Collections.Generic; using System.Globalization; @@ -503,6 +503,10 @@ namespace Tgstation.Server.Host.Core if (generalConfiguration.HostApiDocumentation) { + var siteDocPath = Routes.ApiRoot + $"doc/{SwaggerConfiguration.DocumentName}.json"; + if (!String.IsNullOrWhiteSpace(controlPanelConfiguration.PublicPath)) + siteDocPath = controlPanelConfiguration.PublicPath.TrimEnd('/') + siteDocPath; + applicationBuilder.UseSwagger(options => { options.RouteTemplate = Routes.ApiRoot + "doc/{documentName}.{json|yaml}"; @@ -510,7 +514,7 @@ namespace Tgstation.Server.Host.Core applicationBuilder.UseSwaggerUI(options => { options.RoutePrefix = SwaggerConfiguration.DocumentationSiteRouteExtension; - options.SwaggerEndpoint(Routes.ApiRoot + $"doc/{SwaggerConfiguration.DocumentName}.json", "TGS API"); + options.SwaggerEndpoint(siteDocPath, "TGS API"); }); logger.LogTrace("Swagger API generation enabled"); } From f8c511666b07b96eb06629b8b678f307a010ac21 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 18:22:21 -0500 Subject: [PATCH 009/137] Version bump to 6.1.4 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index f281ec3d80..687396873e 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.1.3 + 6.1.4 5.0.0 10.0.0 7.0.0 From c43aac139294d75d633853c1031ef8536c24d69d Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Jan 2024 18:28:03 -0500 Subject: [PATCH 010/137] Remove winget dependency information We already provide this package in the installer so it's actually not a dependency. Refer to https://github.com/microsoft/winget-pkgs/issues/135625 --- build/package/winget/manifest/Tgstation.Server.installer.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/build/package/winget/manifest/Tgstation.Server.installer.yaml b/build/package/winget/manifest/Tgstation.Server.installer.yaml index 5678bf542a..5d0ed20324 100644 --- a/build/package/winget/manifest/Tgstation.Server.installer.yaml +++ b/build/package/winget/manifest/Tgstation.Server.installer.yaml @@ -22,9 +22,6 @@ Installers: AppsAndFeaturesEntries: - DisplayName: tgstation-server Publisher: /tg/station 13 - Dependencies: - PackageDependencies: - - PackageIdentifier: Microsoft.DotNet.HostingBundle.8 ReleaseDate: 2023-06-24 # Do not change. Set before publish by push_manifest.ps1 ManifestType: installer ManifestVersion: 1.5.0 From c11b99518f7d465c368d17aea44c1b03115b2dbe Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Jan 2024 02:50:00 -0500 Subject: [PATCH 011/137] Update bundled dotnet redistributable to 8.0.1 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 687396873e..1a2accf75c 100644 --- a/build/Version.props +++ b/build/Version.props @@ -17,7 +17,7 @@ netstandard2.0 8 - https://download.visualstudio.microsoft.com/download/pr/2a7ae819-fbc4-4611-a1ba-f3b072d4ea25/32f3b931550f7b315d9827d564202eeb/dotnet-hosting-8.0.0-win.exe + https://download.visualstudio.microsoft.com/download/pr/016c6447-764a-4210-a260-bf7a2880d5c0/a5746437a3862d7803284ae8c2290200/dotnet-hosting-8.0.1-win.exe 10.11.6 1.22.21 From d0cb99bd702b7d057591b3f00b6cfe74016bddce Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 24 Jan 2024 20:06:23 -0500 Subject: [PATCH 012/137] Fix chat message for exit code 0 sessions --- .../Components/Session/ISessionController.cs | 2 +- .../Components/Session/SessionController.cs | 9 +++++++-- .../Components/Watchdog/BasicWatchdog.cs | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index ccdaa448c4..a178e7c3c1 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -22,7 +22,7 @@ namespace Tgstation.Server.Host.Components.Session /// /// If the DreamDaemon instance sent a. /// - bool TerminationWasRequested { get; } + bool TerminationWasIntentional { get; } /// /// The DMAPI . diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index ad9bdf1728..2b0ec82db2 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -63,7 +63,7 @@ namespace Tgstation.Server.Host.Components.Session public Version? DMApiVersion { get; private set; } /// - public bool TerminationWasRequested { get; private set; } + public bool TerminationWasIntentional => terminationWasIntentional || (Lifetime.IsCompleted && Lifetime.Result == 0); /// public Task LaunchResult { get; } @@ -219,6 +219,11 @@ namespace Tgstation.Server.Host.Components.Session /// bool released; + /// + /// Backing field for overriding . + /// + bool terminationWasIntentional; + /// /// Initializes a new instance of the class. /// @@ -622,7 +627,7 @@ namespace Tgstation.Server.Host.Components.Session case BridgeCommandType.Kill: Logger.LogInformation("Bridge requested process termination!"); chatTrackingContext.Active = false; - TerminationWasRequested = true; + terminationWasIntentional = true; process.Terminate(); break; case BridgeCommandType.DeprecatedPortUpdate: diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 8494cc9aeb..8dfb10e412 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -113,12 +113,12 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (reason) { case MonitorActivationReason.ActiveServerCrashed: - var eventType = controller.TerminationWasRequested || (await controller.Lifetime) == 0 + var eventType = controller.TerminationWasIntentional ? EventType.WorldEndProcess : EventType.WatchdogCrash; await HandleEventImpl(eventType, Enumerable.Empty(), false, cancellationToken); - var exitWord = controller.TerminationWasRequested ? "exited" : "crashed"; + var exitWord = controller.TerminationWasIntentional ? "exited" : "crashed"; if (controller.RebootState == Session.RebootState.Shutdown) { // the time for graceful shutdown is now From 1b8b14de7a3cac6493bf5592ef4cd011fb32d88f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 24 Jan 2024 20:07:20 -0500 Subject: [PATCH 013/137] Version bump to 6.1.5 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 1a2accf75c..2f03087aef 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.1.4 + 6.1.5 5.0.0 10.0.0 7.0.0 From f640c2d2486f7a24e293ac3eb7c19ff18614cc17 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 24 Jan 2024 20:43:03 -0500 Subject: [PATCH 014/137] Add `TestReferenceCountingContainer` --- .../Utils/TestReferenceCountingContainer.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/Tgstation.Server.Host.Tests/Utils/TestReferenceCountingContainer.cs diff --git a/tests/Tgstation.Server.Host.Tests/Utils/TestReferenceCountingContainer.cs b/tests/Tgstation.Server.Host.Tests/Utils/TestReferenceCountingContainer.cs new file mode 100644 index 0000000000..f517d66de2 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Utils/TestReferenceCountingContainer.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Tgstation.Server.Host.Utils.Tests +{ + [TestClass] + public sealed class TestReferenceCountingContainer + { + interface IInterface + { + } + + sealed class Implementation : IInterface + { + } + + sealed class Lock : ReferenceCounter + { + public IInterface PubInstance => Instance; + } + + + [TestMethod] + public void TestReferenceCounting() + { + var impl = new Implementation(); + var container = new ReferenceCountingContainer(impl); + + Assert.IsTrue(container.OnZeroReferences.IsCompleted); + + var ref1 = container.AddReference(); + + Assert.AreSame(ref1.PubInstance, impl); + + var task = container.OnZeroReferences; + + Assert.AreSame(task, container.OnZeroReferences); + Assert.IsFalse(task.IsCompleted); + + ref1.Dispose(); + + Assert.IsTrue(task.IsCompleted); + Assert.IsTrue(container.OnZeroReferences.IsCompleted); + + ref1.Dispose(); + Assert.IsTrue(container.OnZeroReferences.IsCompleted); + + var ref2 = container.AddReference(); + Assert.IsFalse((task = container.OnZeroReferences).IsCompleted); + + + ref2.Dispose(); + Assert.IsTrue(task.IsCompleted); + Assert.IsTrue(container.OnZeroReferences.IsCompleted); + } + } +} From 36ad335ff874de74c4b29e9f40d1c23c01399cf1 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 08:45:36 -0500 Subject: [PATCH 015/137] Update deprecated actions `cache` and `setup-dotnet` from @v3 to @v4 --- .github/workflows/ci-pipeline.yml | 42 ++++++++++++++--------------- .github/workflows/code-scanning.yml | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index cc033d61a7..fa2ec2ff20 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -97,7 +97,7 @@ jobs: sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386 - name: Cache BYOND .zips - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-byond with: path: ~/byond-zips-cache @@ -174,7 +174,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.OD_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -214,7 +214,7 @@ jobs: if: (!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success') steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -316,7 +316,7 @@ jobs: sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386 - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -338,7 +338,7 @@ jobs: run: dotnet build -c ${{ matrix.configuration }}NoWindows - name: Cache BYOND .zips - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-byond with: path: ~/byond-zips-cache @@ -367,7 +367,7 @@ jobs: runs-on: windows-latest steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -389,7 +389,7 @@ jobs: run: dotnet build -c ${{ matrix.configuration }}NoWix - name: Cache BYOND .zips - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-byond with: path: ~/byond-zips-cache @@ -424,7 +424,7 @@ jobs: sqlcmd -l 600 -S "(localdb)\MSSQLLocalDB" -Q "SELECT @@VERSION;" - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | ${{ env.TGS_DOTNET_VERSION }}.0.x @@ -504,7 +504,7 @@ jobs: run: dotnet build -c ${{ matrix.configuration }} tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj - name: Cache BYOND .zips - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-byond with: path: ~/byond-zips-cache @@ -656,7 +656,7 @@ jobs: sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | ${{ env.TGS_DOTNET_VERSION }}.0.x @@ -709,7 +709,7 @@ jobs: run: dotnet build -c ${{ matrix.configuration }}NoWindows tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj - name: Cache BYOND .zips - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-byond with: path: ~/byond-zips-cache @@ -1072,7 +1072,7 @@ jobs: sudo apt-get install -y dotnet-sdk-${{ env.TGS_DOTNET_VERSION }}.0 - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1162,7 +1162,7 @@ jobs: GITHUB_TOKEN: ${{ env.WINGET_PUSH_TOKEN }} - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1288,7 +1288,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1343,7 +1343,7 @@ jobs: if: (!(cancelled() || failure()) && needs.deployment-gate.result == 'success' && contains(github.event.head_commit.message, '[APIDeploy]')) steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1407,7 +1407,7 @@ jobs: if: (!(cancelled() || failure()) && needs.deployment-gate.result == 'success' && contains(github.event.head_commit.message, '[DMDeploy]')) steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1470,7 +1470,7 @@ jobs: if: (!(cancelled() || failure()) && needs.deployment-gate.result == 'success' && contains(github.event.head_commit.message, '[NugetDeploy]')) steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1521,7 +1521,7 @@ jobs: if: (!(cancelled() || failure()) && (needs.deploy-dm.result == 'success' || needs.deploy-http.result == 'success') && !contains(github.event.head_commit.message, '[TGSDeploy]')) steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1545,7 +1545,7 @@ jobs: if: (!(cancelled() || failure()) && needs.deployment-gate.result == 'success' && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[TGSDeploy]')) steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1757,7 +1757,7 @@ jobs: if: (!(cancelled() || failure()) && needs.deploy-tgs.result == 'success') steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} @@ -1845,7 +1845,7 @@ jobs: runs-on: windows-latest steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index b661059144..f533584ce5 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -29,7 +29,7 @@ jobs: if: ${{ vars.TGS_ENABLE_CODE_QL }} == 'true' steps: - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} From ba59184f75378b8ea564aaa118ba178e778f170e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 08:57:27 -0500 Subject: [PATCH 016/137] Make tests install proper OD dotnet versions --- .github/workflows/ci-pipeline.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index fa2ec2ff20..b3bb0dd220 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -37,7 +37,8 @@ on: env: TGS_DOTNET_VERSION: 8 - OD_DOTNET_VERSION: 7 + OD_MIN_COMPAT_DOTNET_VERSION: 7 + OD_DOTNET_VERSION: 8 TGS_DOTNET_QUALITY: ga TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }} TGS_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }} @@ -175,10 +176,18 @@ jobs: steps: - name: Setup dotnet uses: actions/setup-dotnet@v4 + if: matrix.committish == 'master' with: dotnet-version: '${{ env.OD_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} + - name: Setup dotnet (min-compat) + uses: actions/setup-dotnet@v4 + if: matrix.committish == 'tgs-min-compat' + with: + dotnet-version: '${{ env.OD_MIN_COMPAT_DOTNET_VERSION }}.0.x' + dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} + - name: Checkout (Branch) uses: actions/checkout@v4 if: github.event_name == 'push' || github.event_name == 'schedule' @@ -429,6 +438,7 @@ jobs: dotnet-version: | ${{ env.TGS_DOTNET_VERSION }}.0.x ${{ env.OD_DOTNET_VERSION }}.0.x + ${{ env.OD_MIN_COMPAT_DOTNET_VERSION }}.0.x dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} - name: Set TGS_TEST_DUMP_API_SPEC @@ -661,6 +671,7 @@ jobs: dotnet-version: | ${{ env.TGS_DOTNET_VERSION }}.0.x ${{ env.OD_DOTNET_VERSION }}.0.x + ${{ env.OD_MIN_COMPAT_DOTNET_VERSION }}.0.x dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} - name: Set Sqlite Connection Info From 7add890f4d6574f5aeee8481fa57481ac27ca631 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 08:58:48 -0500 Subject: [PATCH 017/137] Update CONTRIBUTING.md with info about .NET 7.0 SDK requirement --- .github/CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0dee22f9b4..1dd8c6e4d7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -42,7 +42,8 @@ In order to build the service version and/or the Windows installer you need a to In addition, the installer project uses the Wix v4 Toolset which will cause an error on loading the .sln in Visual Studio if the [HeatWave for VS2022 Extension](https://marketplace.visualstudio.com/items?itemName=FireGiant.FireGiantHeatWaveDev17) is not installed. -In order to run the integration tests you must have the following environment variables set. To run them more accurately, include the optional ones. +In order to run the integration tests you must have the dotnet 7.0 SDK installed to properly build the OpenDream minimum compatible version. +You must also have the following environment variables set. To run them more accurately, include the optional ones. - `TGS_TEST_DATABASE_TYPE`: `MySql`, `MariaDB`, `PostgresSql`, or `SqlServer`. - `TGS_TEST_CONNECTION_STRING`: To a valid database connection string. You can use the setup wizard to create one. - (Optional) `TGS_TEST_GITHUB_TOKEN`: A GitHub personal access token with no scopes used to bypass rate limits. From 2e231da1edb5dca1a11868d1387da66fa50bf899 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 12:37:51 -0500 Subject: [PATCH 018/137] Update `upload-artifact` and `download-artifact` to v4 --- .github/workflows/ci-pipeline.yml | 120 +++++++++++++++--------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index b3bb0dd220..12e154ff5a 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -357,7 +357,7 @@ jobs: run: sudo dotnet test --no-build --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --filter TestCategory!=RequiresDatabase -c ${{ matrix.configuration }}NoWindows --collect:"XPlat Code Coverage" --settings build/ci.runsettings --results-directory ./TestResults tgstation-server.sln - name: Store Code Coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: linux-unit-test-coverage-${{ matrix.configuration }} path: ./TestResults/ @@ -408,7 +408,7 @@ jobs: run: dotnet test --no-build --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --filter TestCategory!=RequiresDatabase -c ${{ matrix.configuration }}NoWix --collect:"XPlat Code Coverage" --settings build/ci.runsettings --results-directory ./TestResults tgstation-server.sln - name: Store Code Coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: windows-unit-test-coverage-${{ matrix.configuration }} path: ./TestResults/ @@ -540,14 +540,14 @@ jobs: - name: Store Live Tests Output if: ${{ steps.live-tests.outputs.succeeded == 'YES' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: windows-integration-test-logs-${{ matrix.configuration }}-${{ matrix.watchdog-type }}-${{ matrix.database-type }} path: ./test_output.txt - name: Store Errored Live Tests Output if: ${{ steps.live-tests.outputs.succeeded != 'YES' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: errored-windows-test-logs-${{ matrix.configuration }}-${{ matrix.watchdog-type }}-${{ matrix.database-type }} path: ./test_output.txt @@ -557,14 +557,14 @@ jobs: run: exit 1 - name: Store Code Coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: windows-integration-test-coverage-${{ matrix.configuration }}-${{ matrix.watchdog-type }}-${{ matrix.database-type }} path: ./TestResults/ - name: Store OpenAPI Spec if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'SqlServer' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: openapi-spec path: C:/tgs_api.json @@ -583,7 +583,7 @@ jobs: - name: Store Server Service if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ServerService path: artifacts/Service/ @@ -733,7 +733,7 @@ jobs: dotnet test -c ${{ matrix.configuration }}NoWindows --filter TestCategory=RequiresDatabase --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --no-build --collect:"XPlat Code Coverage" --settings ../../build/ci.runsettings --results-directory ../../TestResults - name: Store Code Coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: linux-integration-test-coverage-${{ matrix.configuration }}-${{ matrix.watchdog-type }}-${{ matrix.database-type }} path: ./TestResults/ @@ -761,14 +761,14 @@ jobs: - name: Store Server Console if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'MariaDB' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ServerConsole path: artifacts/Console/ - name: Store Server Update Package if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'PostgresSql' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ServerUpdatePackage path: artifacts/ServerUpdate/ @@ -793,7 +793,7 @@ jobs: ref: "refs/pull/${{ github.event.number }}/merge" - name: Retrieve OpenAPI Spec - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: openapi-spec path: ./swagger @@ -818,235 +818,235 @@ jobs: ref: "refs/pull/${{ github.event.number }}/merge" - name: Retrieve Linux Unit Test Coverage (Debug) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-unit-test-coverage-Debug path: ./code_coverage/unit_tests/linux_unit_tests_debug - name: Retrieve Linux Unit Test Coverage (Release) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-unit-test-coverage-Release path: ./code_coverage/unit_tests/linux_unit_tests_release - name: Retrieve Linux Integration Test Coverage (Release, Advanced, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-Sqlite path: ./code_coverage/integration_tests/linux_integration_tests_release_system_sqlite - name: Retrieve Linux Integration Test Coverage (Release, Advanced, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-PostgresSql path: ./code_coverage/integration_tests/linux_integration_tests_release_system_mariadb - name: Retrieve Linux Integration Test Coverage (Release, Advanced, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-MariaDB path: ./code_coverage/integration_tests/linux_integration_tests_release_system_mysql - name: Retrieve Linux Integration Test Coverage (Release, Advanced, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-MySql path: ./code_coverage/integration_tests/linux_integration_tests_release_system_mysql - name: Retrieve Linux Integration Test Coverage (Release, Basic, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-Sqlite path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_sqlite - name: Retrieve Linux Integration Test Coverage (Release, Basic, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-PostgresSql path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_mariadb - name: Retrieve Linux Integration Test Coverage (Release, Basic, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-MariaDB path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_mysql - name: Retrieve Linux Integration Test Coverage (Release, Basic, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Release-Advanced-MySql path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_mysql - name: Retrieve Linux Integration Test Coverage (Debug, Advanced, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-Sqlite path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_sqlite - name: Retrieve Linux Integration Test Coverage (Debug, Advanced, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-PostgresSql path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_mariadb - name: Retrieve Linux Integration Test Coverage (Debug, Advanced, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-MariaDB path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_mysql - name: Retrieve Linux Integration Test Coverage (Debug, Advanced, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-MySql path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_mysql - name: Retrieve Linux Integration Test Coverage (Debug, Basic, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-Sqlite path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_sqlite - name: Retrieve Linux Integration Test Coverage (Debug, Basic, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-PostgresSql path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_mariadb - name: Retrieve Linux Integration Test Coverage (Debug, Basic, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-MariaDB path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_mysql - name: Retrieve Linux Integration Test Coverage (Debug, Basic, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-integration-test-coverage-Debug-Advanced-MySql path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_mysql - name: Retrieve Windows Unit Test Coverage (Release) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-unit-test-coverage-Release path: ./code_coverage/unit_tests/windows_unit_tests_release - name: Retrieve Windows Integration Test Coverage (Debug, Basic, SqlServer) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Basic-SqlServer path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_sqlserver - name: Retrieve Windows Integration Test Coverage (Release, Basic, SqlServer) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Basic-SqlServer path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_sqlserver - name: Retrieve Windows Integration Test Coverage (Debug, Advanced, SqlServer) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Advanced-SqlServer path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_sqlserver - name: Retrieve Windows Integration Test Coverage (Release, Advanced, SqlServer) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Advanced-SqlServer path: ./code_coverage/integration_tests/windows_integration_tests_release_system_sqlserver - name: Retrieve Windows Integration Test Coverage (Debug, Basic, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Basic-MariaDB path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_mariadb - name: Retrieve Windows Integration Test Coverage (Release, Basic, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Basic-MariaDB path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_mariadb - name: Retrieve Windows Integration Test Coverage (Debug, Advanced, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Advanced-MariaDB path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_mariadb - name: Retrieve Windows Integration Test Coverage (Release, Advanced, MariaDB) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Advanced-MariaDB path: ./code_coverage/integration_tests/windows_integration_tests_release_system_mariadb - name: Retrieve Windows Integration Test Coverage (Debug, Basic, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Basic-MySql path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_mysql - name: Retrieve Windows Integration Test Coverage (Release, Basic, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Basic-MySql path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_mysql - name: Retrieve Windows Integration Test Coverage (Debug, Advanced, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Advanced-MySql path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_mysql - name: Retrieve Windows Integration Test Coverage (Release, Advanced, MySql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Advanced-MySql path: ./code_coverage/integration_tests/windows_integration_tests_release_system_mysql - name: Retrieve Windows Integration Test Coverage (Debug, Basic, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Basic-PostgresSql path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_postgressql - name: Retrieve Windows Integration Test Coverage (Release, Basic, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Basic-PostgresSql path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_postgressql - name: Retrieve Windows Integration Test Coverage (Debug, Advanced, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Advanced-PostgresSql path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_postgressql - name: Retrieve Windows Integration Test Coverage (Release, Advanced, PostgresSql) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Advanced-PostgresSql path: ./code_coverage/integration_tests/windows_integration_tests_release_system_postgressql - name: Retrieve Windows Integration Test Coverage (Debug, Basic, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Basic-Sqlite path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_sqlite - name: Retrieve Windows Integration Test Coverage (Release, Basic, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Basic-Sqlite path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_sqlite - name: Retrieve Windows Integration Test Coverage (Debug, Advanced, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Debug-Advanced-Sqlite path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_sqlite - name: Retrieve Windows Integration Test Coverage (Release, Advanced, Sqlite) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-integration-test-coverage-Release-Advanced-Sqlite path: ./code_coverage/integration_tests/windows_integration_tests_release_system_sqlite @@ -1156,7 +1156,7 @@ jobs: run: tar cfJ tgstation-server-v${{ env.TGS_VERSION }}.debian.packaging.tar.xz tgstation-server_* - name: Upload Packaging Archive - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packaging-debian path: tgstation-server-v${{ env.TGS_VERSION }}.debian.packaging.tar.xz @@ -1287,7 +1287,7 @@ jobs: } - name: Upload Unsigned Installer .exe - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packaging-preview-windows path: build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/bin/Release/tgstation-server-installer.exe @@ -1376,7 +1376,7 @@ jobs: echo "TGS_API_VERSION=$apiVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: Retrieve OpenAPI Spec - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: openapi-spec path: swagger @@ -1602,37 +1602,37 @@ jobs: echo "MARIADB_VERSION=$mariaDBVerison" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: Upload .msi - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packaging-windows-raw-msi path: build/package/winget/Tgstation.Server.Host.Service.Wix/bin/Release/en-US/tgstation-server.msi - name: Retrieve Server Service - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ServerService path: ServerService - name: Retrieve Server Console - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ServerConsole path: ServerConsole - name: Retrieve Server Update Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ServerUpdatePackage path: ServerUpdatePackage - name: Retrieve OpenAPI Spec - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: openapi-spec path: swagger - name: Retrieve Debian Packaging Archive - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packaging-debian path: packaging-debian @@ -1876,7 +1876,7 @@ jobs: run: dotnet build -c Release -p:TGS_HOST_NO_WEBPANEL=true tools/Tgstation.Server.ReleaseNotes - name: Retrieve Server Service - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packaging-windows-raw-msi path: artifacts From b9f33ac5bc0af39f35ee9e72561ed05fc5b525b2 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 12:54:26 -0500 Subject: [PATCH 019/137] More attempts to suppress OD build warnings --- .github/workflows/ci-pipeline.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 12e154ff5a..1ad9880ec8 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -206,10 +206,20 @@ jobs: git checkout ${{ matrix.committish }} git submodule update --init --recursive + - name: Restore OpenDream + run: | + cd $HOME/OpenDream + dotnet restore + + - name: Build OpenDream + run: | + cd $HOME/OpenDream/OpenDreamPackageTool + dotnet build -c Release --nologo -v q --property WarningLevel=0 /clp:ErrorsOnly + - name: Create TGS Deployment run: | cd $HOME/OpenDream - dotnet run -c Release --project OpenDreamPackageTool --property WarningLevel=0 -- --tgs -o tgs_deploy + dotnet run -c Release --project OpenDreamPackageTool --no-build -- --tgs -o tgs_deploy - name: Build DMAPI run: | From 9961ba66446695ad278435a75486c26468df6f7f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 15:14:29 -0500 Subject: [PATCH 020/137] Add `CI_STATUSES_TOKEN` Fold Code Scanning workflow into CI Pipeline Fixes #1638 Update to v3 CodeQL action --- .github/CONTRIBUTING.md | 6 ++-- .github/workflows/ci-pipeline.yml | 42 ++++++++++++++++++++++-- .github/workflows/code-scanning.yml | 51 ----------------------------- 3 files changed, 42 insertions(+), 57 deletions(-) delete mode 100644 .github/workflows/code-scanning.yml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1dd8c6e4d7..7c0274a445 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -60,11 +60,11 @@ You must also have the following environment variables set. To run them more acc For the full CI gambit, the following repository configuration must be set: -- Setting `Workflow Permissions` to `Read and write permissions`: Enables CodeQL uploads and GitHub Actions comments. +- Setting `Workflow Permissions` to `Read and write permissions`: Enables GitHub Actions comments. ![image](https://github.com/tgstation/tgstation-server/assets/8171642/ab17fa74-364f-4e66-b7c4-b9bb24c6a599) - Label `CI Cleared`: To allow PRs from forks to run CI with secrets after approval. -- Variable `TGS_ENABLE_CODE_QL` to `true`: Enables CodeQL scanning in actions. - Integration [CodeCov](https://github.com/apps/codecov): Enables CodeCov status checks. +- Secret `CI_STATUSES_TOKEN`: A GitHub token with read access to the repository's contents/actions and write access to the repository's checks/security events. Used to create CI completion statuses. - Secret `CODECOV_TOKEN`: A CodeCov repo token to work around https://github.com/codecov/codecov-action/issues/837. - Secret `LIVE_TESTS_TOKEN`: A GitHub token with read access to the repository and write access to https://github.com/Cyberboss/common_core (TODO: Make the target repository here configurable). Despite it's name, it may be used across the entire test suite. - Secret `TGS_TEST_DISCORD_TOKEN`: See above note about test environment variables. @@ -74,7 +74,7 @@ For the full CI gambit, the following repository configuration must be set: If you don't plan on deploying TGS, the following secrets can be omitted: -- Secret `DEV_PUSH_TOKEN`: A GitHub token with write access to the repository. Enables doxygen pushes to `gh-pages` branch, and releases creation. +- Secret `DEV_PUSH_TOKEN`: A GitHub token with read/write access to the repository. Enables doxygen pushes to `gh-pages` branch, and releases creation. - Secret `DOCKER_USERNAME`: Login username for Docker image push. - Secret `DOCKER_PASSWORD`: Login password for Docker image push. - Secret `NUGET_API_KEY`: Nuget.org API Key for client libraries push. diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 1ad9880ec8..c8612242b9 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -81,6 +81,36 @@ jobs: - name: GitHub Requires at Least One Step for a Job run: exit 0 + code-scanning: + name: Code Scanning + needs: start-ci-run-gate + runs-on: ubuntu-latest + steps: + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' + dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} + + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: csharp + token: ${{ secrets.CI_STATUSES_TOKEN }} + + - name: Build + run: dotnet build -c ReleaseNoWindows -p:TGS_HOST_NO_WEBPANEL=true + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:csharp" + token: ${{ secrets.CI_STATUSES_TOKEN }} + + dmapi-build: name: Build DMAPI needs: start-ci-run-gate @@ -1341,12 +1371,18 @@ jobs: ci-completion-gate: # This job exists so there isn't a moving target for branch protections name: CI Completion Gate - needs: [ pages-build, docker-build, build-deb, build-msi, validate-openapi-spec, upload-code-coverage, check-winget-pr-template ] + needs: [ pages-build, docker-build, build-deb, build-msi, validate-openapi-spec, upload-code-coverage, check-winget-pr-template, code-scanning ] runs-on: ubuntu-latest if: (!(cancelled() || failure()) && needs.pages-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.build-msi.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && needs.check-winget-pr-template.result == 'success') steps: - - name: GitHub Requires at Least One Step for a Job - run: exit 0 + - name: Create Completion Check + uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 + with: + token: ${{ secrets.CI_STATUSES_TOKEN }} + name: CI Completion + conclusion: success + output: | + {"summary":"The CI Pipeline completed successfully"} deployment-gate: name: Deployment Start Gate diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml deleted file mode 100644 index f533584ce5..0000000000 --- a/.github/workflows/code-scanning.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: 'Code Scanning' - -on: - push: - branches: - - dev - - master - pull_request: - branches: - - dev - - master - -env: - TGS_DOTNET_VERSION: 8 - TGS_DOTNET_QUALITY: ga - -concurrency: - group: "code-scanning-${{ github.head_ref || github.run_id }}-${{ github.event_name }}" - cancel-in-progress: true - -jobs: - analyze: - name: Code Scanning - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - if: ${{ vars.TGS_ENABLE_CODE_QL }} == 'true' - steps: - - name: Setup dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' - dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} - - - name: Checkout - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: csharp - - - name: Build - run: dotnet build -c ReleaseNoWindows -p:TGS_HOST_NO_WEBPANEL=true - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:csharp" From d8b06818a362cf4f1fca88afa5a101f30f99a934 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 16:40:41 -0500 Subject: [PATCH 021/137] Workaround for GitHub API sometimes returning duplicate results --- tools/Tgstation.Server.ReleaseNotes/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs index 5f832bc44c..b8d1e86f79 100644 --- a/tools/Tgstation.Server.ReleaseNotes/Program.cs +++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs @@ -883,7 +883,7 @@ The user account that created this pull request is available to correct any issu var results = await RLR(() => apiCall(apiOptions)); var distinctEntries = new Dictionary(results.Count); foreach (var result in results) - distinctEntries.Add(idSelector(result).ToString(), result); + distinctEntries.TryAdd(idSelector(result).ToString(), result); if (results.Count > 100) { From 41a70a7bc62e9793a43025cd72823ddbd98cacbf Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 17:48:30 -0500 Subject: [PATCH 022/137] Update to actions/checkout@v4 for master merge workflow --- .github/workflows/stable-merge.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stable-merge.yml b/.github/workflows/stable-merge.yml index 8b77f4d7d3..9135dc5f7a 100644 --- a/.github/workflows/stable-merge.yml +++ b/.github/workflows/stable-merge.yml @@ -12,7 +12,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Merge master into dev uses: robotology/gh-action-nightly-merge@22f5e45d028f22837d617fa07512925457eec184 #v1.3.3 From 62423db1c52b9fc8d308cff2267ce64c67333a3b Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 17:49:02 -0500 Subject: [PATCH 023/137] Fix Code Scanning start conditional --- .github/workflows/ci-pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index c8612242b9..2df78f5bf2 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -85,6 +85,7 @@ jobs: name: Code Scanning needs: start-ci-run-gate runs-on: ubuntu-latest + if: (!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success') steps: - name: Setup dotnet uses: actions/setup-dotnet@v4 From ad43286ba88dfc3d173769b84c908e3be5f932ab Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 22:52:56 -0500 Subject: [PATCH 024/137] Remove the need for the extra CI token --- .github/CONTRIBUTING.md | 1 - .github/workflows/ci-pipeline.yml | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7c0274a445..48b19a8566 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -64,7 +64,6 @@ For the full CI gambit, the following repository configuration must be set: ![image](https://github.com/tgstation/tgstation-server/assets/8171642/ab17fa74-364f-4e66-b7c4-b9bb24c6a599) - Label `CI Cleared`: To allow PRs from forks to run CI with secrets after approval. - Integration [CodeCov](https://github.com/apps/codecov): Enables CodeCov status checks. -- Secret `CI_STATUSES_TOKEN`: A GitHub token with read access to the repository's contents/actions and write access to the repository's checks/security events. Used to create CI completion statuses. - Secret `CODECOV_TOKEN`: A CodeCov repo token to work around https://github.com/codecov/codecov-action/issues/837. - Secret `LIVE_TESTS_TOKEN`: A GitHub token with read access to the repository and write access to https://github.com/Cyberboss/common_core (TODO: Make the target repository here configurable). Despite it's name, it may be used across the entire test suite. - Secret `TGS_TEST_DISCORD_TOKEN`: See above note about test environment variables. diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 2df78f5bf2..4f653587be 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -85,6 +85,9 @@ jobs: name: Code Scanning needs: start-ci-run-gate runs-on: ubuntu-latest + permissions: + security-events: write + actions: read if: (!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success') steps: - name: Setup dotnet @@ -100,7 +103,6 @@ jobs: uses: github/codeql-action/init@v3 with: languages: csharp - token: ${{ secrets.CI_STATUSES_TOKEN }} - name: Build run: dotnet build -c ReleaseNoWindows -p:TGS_HOST_NO_WEBPANEL=true @@ -109,8 +111,6 @@ jobs: uses: github/codeql-action/analyze@v3 with: category: "/language:csharp" - token: ${{ secrets.CI_STATUSES_TOKEN }} - dmapi-build: name: Build DMAPI @@ -1374,12 +1374,15 @@ jobs: name: CI Completion Gate needs: [ pages-build, docker-build, build-deb, build-msi, validate-openapi-spec, upload-code-coverage, check-winget-pr-template, code-scanning ] runs-on: ubuntu-latest - if: (!(cancelled() || failure()) && needs.pages-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.build-msi.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && needs.check-winget-pr-template.result == 'success') + permissions: + checks: write + contents: read + if: (!(cancelled() || failure()) && needs.pages-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.build-msi.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && needs.check-winget-pr-template.result == 'success' && needs.code-scanning.result == 'success') steps: - name: Create Completion Check uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 with: - token: ${{ secrets.CI_STATUSES_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} name: CI Completion conclusion: success output: | From 4993304389d9607db9ec59f536792edbafe71d9e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 30 Jan 2024 20:19:58 -0500 Subject: [PATCH 025/137] Add support for setting environment variables with `IProcessExecutor` --- .../Components/Engine/OpenDreamInstaller.cs | 1 + .../Session/SessionControllerFactory.cs | 1 + .../System/IProcessExecutor.cs | 6 +++++- .../System/ProcessExecutor.cs | 20 +++++++++++++++---- .../System/TestPosixSignalHandler.cs | 1 + .../Live/TestLiveServer.cs | 1 + .../TestSystemInteraction.cs | 4 ++-- tests/Tgstation.Server.Tests/TestVersions.cs | 1 + 8 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index a2b74e1445..f0d39c2b34 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -258,6 +258,7 @@ namespace Tgstation.Server.Host.Components.Engine shortenedPath, $"run -c Release --project OpenDreamPackageTool -- --tgs -o {shortenedDeployPath}", null, + null, !GeneralConfiguration.OpenDreamSuppressInstallOutput, !GeneralConfiguration.OpenDreamSuppressInstallOutput); diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 70bed0f5ba..316202a7ba 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -507,6 +507,7 @@ namespace Tgstation.Server.Host.Components.Session engineLock.ServerExePath, dmbProvider.Directory, arguments, + null, logFilePath, engineLock.HasStandardOutput, true); diff --git a/src/Tgstation.Server.Host/System/IProcessExecutor.cs b/src/Tgstation.Server.Host/System/IProcessExecutor.cs index aae807eec2..34962811e1 100644 --- a/src/Tgstation.Server.Host/System/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/IProcessExecutor.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.System +using System.Collections.Generic; + +namespace Tgstation.Server.Host.System { /// /// For launching '. @@ -11,6 +13,7 @@ /// The full path to the executable file. /// The working directory for the . /// The arguments for the . + /// A of environment variables to set. /// File to write process output and error streams to. Requires to be . /// If the process output and error streams should be read. /// If shell execute should not be used. Must be set if is set. @@ -19,6 +22,7 @@ string fileName, string workingDirectory, string arguments, + IReadOnlyDictionary? environment = null, string? fileRedirect = null, bool readStandardHandles = false, bool noShellExecute = false); diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 7820ca11b3..445a333ffd 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Channels; @@ -107,6 +109,7 @@ namespace Tgstation.Server.Host.System string fileName, string workingDirectory, string arguments, + IReadOnlyDictionary? environment, string? fileRedirect, bool readStandardHandles, bool noShellExecute) @@ -115,24 +118,33 @@ namespace Tgstation.Server.Host.System ArgumentNullException.ThrowIfNull(workingDirectory); ArgumentNullException.ThrowIfNull(arguments); + var enviromentLogLines = environment == null + ? String.Empty + : String.Concat(environment.Select(kvp => $"{Environment.NewLine}\t- {kvp.Key}={kvp.Value}")); if (noShellExecute) logger.LogDebug( - "Launching process in {workingDirectory}: {exe} {arguments}", + "Launching process in {workingDirectory}: {exe} {arguments}{environment}", workingDirectory, fileName, - arguments); + arguments, + enviromentLogLines); else logger.LogDebug( - "Shell launching process in {workingDirectory}: {exe} {arguments}", + "Shell launching process in {workingDirectory}: {exe} {arguments}{environment}", workingDirectory, fileName, - arguments); + arguments, + enviromentLogLines); var handle = new global::System.Diagnostics.Process(); try { handle.StartInfo.FileName = fileName; handle.StartInfo.Arguments = arguments; + if (environment != null) + foreach (var kvp in environment) + handle.StartInfo.Environment.Add(kvp!); + handle.StartInfo.WorkingDirectory = workingDirectory; handle.StartInfo.UseShellExecute = !noShellExecute; diff --git a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs index 083657a075..121df115e3 100644 --- a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs @@ -69,6 +69,7 @@ namespace Tgstation.Server.Host.System.Tests pathToSignalTestApp, $"run -c {CurrentConfig} --no-build", null, + null, true, true); diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index 91466db1d0..5e058d24eb 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -1105,6 +1105,7 @@ namespace Tgstation.Server.Tests.Live repoPath, args, null, + null, true, true); diff --git a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs index 98b7c82f44..a2b43af6fd 100644 --- a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs +++ b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Tests Mock.Of>(), loggerFactory); - await using var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, true, true); + await using var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, null, true, true); using var cts = new CancellationTokenSource(); cts.CancelAfter(3000); var exitCode = await process.Lifetime.WaitAsync(cts.Token); @@ -63,7 +63,7 @@ namespace Tgstation.Server.Tests File.Delete(tempFile); try { - await using (var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, tempFile, true, true)) + await using (var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, tempFile, true, true)) { using var cts = new CancellationTokenSource(); cts.CancelAfter(3000); diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 7b52b79773..591517a46f 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -503,6 +503,7 @@ namespace Tgstation.Server.Tests Environment.CurrentDirectory, "fake.dmb -map-threads 3 -close", null, + null, true, true); From 3246ac93bb14de31ff91fd2a7d3a620befd213ed Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 29 Jan 2024 22:56:36 -0500 Subject: [PATCH 026/137] Use the correct checkout for Code Scanning --- .github/workflows/ci-pipeline.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 4f653587be..e7cc858520 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -96,8 +96,15 @@ jobs: dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} - - name: Checkout + - name: Checkout (Branch) uses: actions/checkout@v4 + if: github.event_name == 'push' || github.event_name == 'schedule' + + - name: Checkout (PR Merge) + uses: actions/checkout@v4 + if: github.event_name != 'push' && github.event_name != 'schedule' + with: + ref: "refs/pull/${{ github.event.number }}/merge" - name: Initialize CodeQL uses: github/codeql-action/init@v3 From c4e7f0b04ac20fc68e186a77eb2640617b0d670f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 30 Jan 2024 22:03:59 -0500 Subject: [PATCH 027/137] .env files for engine installations --- .../Components/Deployment/DreamMaker.cs | 2 + .../Components/Engine/ByondInstallation.cs | 4 ++ .../Components/Engine/ByondInstallerBase.cs | 15 +++--- .../Components/Engine/EngineExecutableLock.cs | 4 ++ .../Engine/EngineInstallationBase.cs | 53 +++++++++++++++++++ .../Components/Engine/IEngineInstallation.cs | 9 ++++ .../Engine/OpenDreamInstallation.cs | 15 +++--- .../Components/Engine/OpenDreamInstaller.cs | 2 +- .../Session/SessionControllerFactory.cs | 3 +- .../Tgstation.Server.Host.csproj | 2 + .../EngineActiveVersionChange-SetupEnv.bat | 4 ++ .../EngineActiveVersionChange-SetupEnv.sh | 7 +++ .../Live/Instance/ConfigurationTest.cs | 26 +++++---- tgstation-server.sln | 4 +- 14 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.bat create mode 100644 tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.sh diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index ac5c4576e3..408cc07bfb 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -852,6 +852,7 @@ namespace Tgstation.Server.Host.Components.Deployment /// A resulting in if compilation succeeded, otherwise. async ValueTask RunDreamMaker(IEngineExecutableLock engineLock, Models.CompileJob job, CancellationToken cancellationToken) { + var environment = await engineLock.LoadEnv(logger, true, cancellationToken); var arguments = engineLock.FormatCompilerArguments($"{job.DmeName}.{DmeExtension}"); await using var dm = processExecutor.LaunchProcess( @@ -859,6 +860,7 @@ namespace Tgstation.Server.Host.Components.Deployment ioManager.ResolvePath( job.DirectoryName!.Value.ToString()), arguments, + environment, readStandardHandles: true, noShellExecute: true); diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs index ecfb965e58..781add748f 100644 --- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Engine { @@ -75,6 +76,7 @@ namespace Tgstation.Server.Host.Components.Engine /// /// Initializes a new instance of the class. /// + /// The for the . /// The value of . /// The value of . /// The value of . @@ -82,12 +84,14 @@ namespace Tgstation.Server.Host.Components.Engine /// If a CLI application is being used. /// The value of . public ByondInstallation( + IIOManager installationIOManager, Task installationTask, EngineVersion version, string dreamDaemonPath, string dreamMakerPath, bool supportsCli, bool supportsMapThreads) + : base(installationIOManager) { InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask)); ArgumentNullException.ThrowIfNull(version); diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs index a992073d10..81a68470e7 100644 --- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs +++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs @@ -88,21 +88,22 @@ namespace Tgstation.Server.Host.Components.Engine { CheckVersionValidity(version); - var binPathForVersion = IOManager.ConcatPath(path, ByondBinPath); + var installationIOManager = new ResolvingIOManager(IOManager, path); var supportsMapThreads = version.Version >= MapThreadsVersion; return new ByondInstallation( + installationIOManager, installationTask, version, - IOManager.ResolvePath( - IOManager.ConcatPath( - binPathForVersion, + installationIOManager.ResolvePath( + installationIOManager.ConcatPath( + ByondBinPath, GetDreamDaemonName( version.Version!, out var supportsCli))), - IOManager.ResolvePath( - IOManager.ConcatPath( - binPathForVersion, + installationIOManager.ResolvePath( + installationIOManager.ConcatPath( + ByondBinPath, DreamMakerName)), supportsCli, supportsMapThreads); diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs index 5d7a1d7fef..3e1a92be14 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs @@ -62,5 +62,9 @@ namespace Tgstation.Server.Host.Components.Engine accessIdentifier, port, cancellationToken); + + /// + public ValueTask?> LoadEnv(ILogger logger, bool forCompiler, CancellationToken cancellationToken) + => Instance.LoadEnv(logger, forCompiler, cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs index 5edf44609f..77666748c7 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; +using DotEnv.Core; + using Microsoft.Extensions.Logging; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Engine @@ -38,6 +42,11 @@ namespace Tgstation.Server.Host.Components.Engine /// public abstract Task InstallationTask { get; } + /// + /// The pointing to the installation directory. + /// + protected IIOManager InstallationIOManager { get; } + /// /// Encode given parameters for passing as world.params on the command line. /// @@ -56,6 +65,15 @@ namespace Tgstation.Server.Host.Components.Engine return parametersString; } + /// + /// Initializes a new instance of the class. + /// + /// The value of . + public EngineInstallationBase(IIOManager installationIOManager) + { + InstallationIOManager = installationIOManager ?? throw new ArgumentNullException(nameof(installationIOManager)); + } + /// public abstract string FormatCompilerArguments(string dmePath); @@ -69,10 +87,45 @@ namespace Tgstation.Server.Host.Components.Engine /// public virtual async ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(logger); cancellationToken.ThrowIfCancellationRequested(); logger.LogTrace("Terminating engine server process..."); process.Terminate(); await process.Lifetime; } + + /// + public async ValueTask?> LoadEnv(ILogger logger, bool forCompiler, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(logger); + + var envFile = forCompiler + ? "compiler.env" + : "server.env"; + + if (!await InstallationIOManager.FileExists(envFile, cancellationToken)) + { + logger.LogTrace("No {envFile} present in engine installation {version}", envFile, Version); + return null; + } + + logger.LogDebug("Loading {envFile} for engine installation {version}...", envFile, Version); + + var fileBytes = await InstallationIOManager.ReadAllBytes(envFile, cancellationToken); + var fileContents = Encoding.UTF8.GetString(fileBytes); + var parser = new EnvParser(); + + try + { + var variables = parser.Parse(fileContents); + + return variables.ToDictionary(); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Unable to parse {envFile}!", envFile); + return null; + } + } } } diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs index ff2e4155f3..402eac4a76 100644 --- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs @@ -82,5 +82,14 @@ namespace Tgstation.Server.Host.Components.Engine /// The for the operation. /// A representing the running operation. ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken); + + /// + /// Loads the environment settings for either the server or compiler. + /// + /// The to write to. + /// If server.env will be loaded. If compiler.env will be loaded. + /// The for the operation. + /// A resulting in the environment or if the target environment file doesn't exist. + ValueTask?> LoadEnv(ILogger logger, bool forCompiler, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs index ab9eb923d8..ba400090e0 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs @@ -47,11 +47,6 @@ namespace Tgstation.Server.Host.Components.Engine /// public override Task InstallationTask { get; } - /// - /// The for the . - /// - readonly IIOManager ioManager; - /// /// The for the . /// @@ -65,7 +60,7 @@ namespace Tgstation.Server.Host.Components.Engine /// /// Initializes a new instance of the class. /// - /// The value of . + /// The for the . /// The value of . /// The value of . /// The value of . @@ -73,15 +68,15 @@ namespace Tgstation.Server.Host.Components.Engine /// The value of . /// The value of . public OpenDreamInstallation( - IIOManager ioManager, + IIOManager installationIOManager, IAsyncDelayer asyncDelayer, IAbstractHttpClientFactory httpClientFactory, string serverExePath, string compilerExePath, Task installationTask, EngineVersion version) + : base(installationIOManager) { - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); ServerExePath = serverExePath ?? throw new ArgumentNullException(nameof(serverExePath)); @@ -109,7 +104,7 @@ namespace Tgstation.Server.Host.Components.Engine var parametersString = EncodeParameters(parameters, launchParameters); - var arguments = $"--cvar {(logFilePath != null ? $"log.path=\"{ioManager.GetDirectoryName(logFilePath)}\" --cvar log.format=\"{ioManager.GetFileName(logFilePath)}\"" : "log.enabled=false")} --cvar watchdog.token={accessIdentifier} --cvar log.runtimelog=false --cvar net.port={launchParameters.Port!.Value} --cvar opendream.topic_port=0 --cvar opendream.world_params=\"{parametersString}\" --cvar opendream.json_path=\"./{dmbProvider.DmbName}\""; + var arguments = $"--cvar {(logFilePath != null ? $"log.path=\"{InstallationIOManager.GetDirectoryName(logFilePath)}\" --cvar log.format=\"{InstallationIOManager.GetFileName(logFilePath)}\"" : "log.enabled=false")} --cvar watchdog.token={accessIdentifier} --cvar log.runtimelog=false --cvar net.port={launchParameters.Port!.Value} --cvar opendream.topic_port=0 --cvar opendream.world_params=\"{parametersString}\" --cvar opendream.json_path=\"./{dmbProvider.DmbName}\""; return arguments; } @@ -125,6 +120,8 @@ namespace Tgstation.Server.Host.Components.Engine ushort port, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(logger); + const int MaximumTerminationSeconds = 5; logger.LogTrace("Attempting Robust.Server graceful exit (Timeout: {seconds}s)...", MaximumTerminationSeconds); diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index f0d39c2b34..dbb5577bc8 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Components.Engine CheckVersionValidity(version); GetExecutablePaths(path, out var serverExePath, out var compilerExePath); return new OpenDreamInstallation( - IOManager, + new ResolvingIOManager(IOManager, path), asyncDelayer, httpClientFactory, serverExePath, diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 316202a7ba..41c087b22f 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -490,6 +490,7 @@ namespace Tgstation.Server.Host.Components.Session CancellationToken cancellationToken) { // important to run on all ports to allow port changing + var environment = await engineLock.LoadEnv(logger, false, cancellationToken); var arguments = engineLock.FormatServerArguments( dmbProvider, new Dictionary @@ -507,7 +508,7 @@ namespace Tgstation.Server.Host.Components.Session engineLock.ServerExePath, dmbProvider.Directory, arguments, - null, + environment, logFilePath, engineLock.HasStandardOutput, true); diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index e6a0a19d97..af011cc872 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -69,6 +69,8 @@ + + diff --git a/tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.bat b/tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.bat new file mode 100644 index 0000000000..6f026682c0 --- /dev/null +++ b/tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.bat @@ -0,0 +1,4 @@ +cd /D "%~dp0" +cd ../../Byond/%1 +echo # Comment > server.env +echo NOTA=Real Comment>> server.env diff --git a/tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.sh b/tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.sh new file mode 100644 index 0000000000..52b8090bd1 --- /dev/null +++ b/tests/DMAPI/LongRunning/EngineActiveVersionChange-SetupEnv.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +cd "../../Byond/$1" + +echo -e '# This is a comment\nNOTA=Real Comment\n\n\n' > server.env diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs index 73c658487b..9ef929701b 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/ConfigurationTest.cs @@ -118,18 +118,24 @@ namespace Tgstation.Server.Tests.Live.Instance await using var memoryStream2 = new MemoryStream(Encoding.UTF8.GetBytes("bbb")); await configurationClient.Write(staticFile2, memoryStream2, cancellationToken); - var shellScriptExtension = new PlatformIdentifier().IsWindows ? ".bat" : ".sh"; - var scriptName = $"PreCompile-GenerateRandomResource{shellScriptExtension}"; - var resourcingScript = new ConfigurationFileRequest + async ValueTask UploadScript(string scriptId) { - Path = $"/EventScripts/{scriptName}" - }; + var shellScriptExtension = new PlatformIdentifier().IsWindows ? ".bat" : ".sh"; + var scriptName = $"{scriptId}{shellScriptExtension}"; + var resourcingScript = new ConfigurationFileRequest + { + Path = $"/EventScripts/{scriptName}" + }; - await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/LongRunning/{scriptName}", false); - await configurationClient.Write( - resourcingScript, - readStream, - cancellationToken); + await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/LongRunning/{scriptName}", false); + await configurationClient.Write( + resourcingScript, + readStream, + cancellationToken); + } + + await UploadScript("PreCompile-GenerateRandomResource"); + await UploadScript("EngineActiveVersionChange-SetupEnv"); } return ValueTaskExtensions.WhenAll( diff --git a/tgstation-server.sln b/tgstation-server.sln index 581410a016..fead8a204d 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -155,6 +155,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LongRunning", "LongRunning", "{EB1DDE8C-CA6F-4BE3-947B-597CA8EABEA5}" ProjectSection(SolutionItems) = preProject tests\DMAPI\LongRunning\Config.dm = tests\DMAPI\LongRunning\Config.dm + tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.bat = tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.bat + tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.sh = tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.sh tests\DMAPI\LongRunning\long_running_test.dme = tests\DMAPI\LongRunning\long_running_test.dme tests\DMAPI\LongRunning\long_running_test_copy.dme = tests\DMAPI\LongRunning\long_running_test_copy.dme tests\DMAPI\LongRunning\long_running_test_rooted.dme = tests\DMAPI\LongRunning\long_running_test_rooted.dme @@ -171,8 +173,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiFree", "ApiFree", "{7B8F EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BasicOperation", "BasicOperation", "{F32B9514-AAD9-429D-841A-ED810FC2598C}" ProjectSection(SolutionItems) = preProject - tests\DMAPI\BasicOperation\Config.dm = tests\DMAPI\BasicOperation\Config.dm 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 EndProjectSection EndProject From 2468633ebff311ee7c7eb28bbde62999144bcf66 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 31 Jan 2024 17:27:05 -0500 Subject: [PATCH 028/137] Update `README.md` for env files and add a missing OD reference --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 333a62165f..2efb724ec3 100644 --- a/README.md +++ b/README.md @@ -539,7 +539,11 @@ Manual operations on the repository while an instance is running may lead to git #### Byond -The `Byond` folder contains installations of [BYOND](https://www.byond.com/) versions. The version which is used by your game code can be changed on a whim (Note that only versions >= 511.1385 have been thouroughly tested. Lower versions should work but if one doesn't function, please open an issue report) and the server will take care of installing it. +The `Byond` folder contains installations of [BYOND](https://www.byond.com/) or [OpenDream](https://github.com/OpenDreamProject/OpenDream) versions. The version which is used by your game code can be changed on a whim (Note that only versions >= 511.1385 have been thouroughly tested. Lower versions should work but if one doesn't function, please open an issue report) and the server will take care of installing it. + +##### Environment Variables + +You can specify additional environment variables to launch your server/compiler with by adding `server.env`/`compiler.env` to your engine installation directory (i.e. `/Byond/515.1530/server.env`). These are [.env](https://hexdocs.pm/dotenvy/dotenv-file-format.html) files. #### Compiler From 0497dd328ebd3c19ffa10e82a46e7537121d0f9a Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 31 Jan 2024 17:16:57 -0500 Subject: [PATCH 029/137] Fix duplicate artefact uploads/steps --- .github/workflows/ci-pipeline.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index e7cc858520..49d1a5bc69 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -618,7 +618,7 @@ jobs: path: C:/tgs_api.json - name: Package Server Service - if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }} + if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' && matrix.database-type == 'PostgresSql' }} run: | cd src/Tgstation.Server.Host.Service dotnet publish -c ${{ matrix.configuration }} -o ../../artifacts/Service @@ -630,14 +630,14 @@ jobs: build/RemoveUnsupportedServiceRuntimes.ps1 artifacts/Service - name: Store Server Service - if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }} + if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' && matrix.database-type == 'PostgresSql' }} uses: actions/upload-artifact@v4 with: name: ServerService path: artifacts/Service/ - name: Install Code Signing Certificate - if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }} + if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' && matrix.database-type == 'PostgresSql' }} shell: powershell run: | $pfxBytes = [convert]::FromBase64String("${{ secrets.CODE_SIGNING_BASE64 }}") @@ -647,7 +647,7 @@ jobs: rm tg_codesigning.pfx - name: Test Sign Service .exe - if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }} + if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' && matrix.database-type == 'PostgresSql' }} shell: powershell run: Set-AuthenticodeSignature artifacts/Service/Tgstation.Server.Host.Service.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com" From 033f263ec1cb250a64bd9363a9cea038fcb43cba Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 31 Jan 2024 20:06:59 -0500 Subject: [PATCH 030/137] Update Nuget packages --- build/TestCommon.props | 4 ++-- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/TestCommon.props b/build/TestCommon.props index c533dded96..9811a480b8 100644 --- a/build/TestCommon.props +++ b/build/TestCommon.props @@ -18,9 +18,9 @@ - + - + diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 1c5c6e8c65..4f64dba5b1 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.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index af011cc872..300d9ffe1c 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -100,7 +100,7 @@ - + @@ -126,7 +126,7 @@ - + From 8d9233659ff290f2ebdcdd1d332a0c0a37a8b467 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 31 Jan 2024 23:04:51 -0500 Subject: [PATCH 031/137] Use `dotnet-dump` when dumping OpenDream Closes #1750 --- build/Version.props | 6 +- src/Tgstation.Server.Api/Models/ErrorCode.cs | 18 +- .../Components/Engine/ByondInstallation.cs | 3 + .../Components/Engine/EngineExecutableLock.cs | 3 + .../Engine/EngineInstallationBase.cs | 3 + .../Components/Engine/EngineManager.cs | 35 ++- .../Components/Engine/IEngineInstallation.cs | 5 + .../Engine/OpenDreamInstallation.cs | 3 + .../Components/Engine/OpenDreamInstaller.cs | 17 +- .../Components/InstanceFactory.cs | 16 +- .../Components/Session/SessionController.cs | 19 +- .../Session/SessionControllerFactory.cs | 10 + src/Tgstation.Server.Host/Core/Application.cs | 7 +- .../Extensions/IOManagerExtensions.cs | 31 +++ .../System/DotnetDumpService.cs | 225 ++++++++++++++++++ .../System/DotnetHelper.cs | 47 ++++ .../System/IDotnetDumpService.cs | 28 +++ .../System/PosixProcessFeatures.cs | 2 +- .../Live/Instance/WatchdogTest.cs | 2 +- 19 files changed, 445 insertions(+), 35 deletions(-) create mode 100644 src/Tgstation.Server.Host/Extensions/IOManagerExtensions.cs create mode 100644 src/Tgstation.Server.Host/System/DotnetDumpService.cs create mode 100644 src/Tgstation.Server.Host/System/DotnetHelper.cs create mode 100644 src/Tgstation.Server.Host/System/IDotnetDumpService.cs diff --git a/build/Version.props b/build/Version.props index d244ecd7fa..c58250e7c5 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,10 +5,10 @@ 6.1.5 5.1.0 - 10.0.0 + 10.1.0 7.0.0 - 13.0.1 - 15.0.1 + 14.0.0 + 16.0.0 7.0.2 5.8.0 1.4.1 diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 28f39a7638..1e1a42ce4d 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -528,10 +528,10 @@ namespace Tgstation.Server.Api.Models MissingGCore, /// - /// Non-zero gcore exit code. + /// Non-zero gcore/dotnet-dump exit code. /// - [Description("Could not create dump as gcore exited with a non-zero exit code!")] - GCoreFailure, + [Description("Could not create dump as the dumping process exited with a non-zero exit code!")] + DumpProcessFailure, /// /// Attempted to test merge with an invalid remote repository. @@ -636,15 +636,21 @@ namespace Tgstation.Server.Api.Models BroadcastFailure, /// - /// Could not compile OpenDream due to a missing dotnet executable. + /// Unable to locate the dotnet executable for a necessary operation. /// - [Description("OpenDream could not be compiled due to being unable to locate the dotnet executable!")] - OpenDreamCantFindDotnet, + [Description("Unable to locate the dotnet executable!")] + CantFindDotnet, /// /// Could not install OpenDream due to it not meeting the minimum version requirements. /// [Description("The specified OpenDream version is too old!")] OpenDreamTooOld, + + /// + /// Could not locally install the dotnet-dump tool. + /// + [Description("Could not locally install the dotnet-dump tool!")] + CantInstallDotnetDump, } } diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs index 781add748f..b3ddd1cc1a 100644 --- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs @@ -33,6 +33,9 @@ namespace Tgstation.Server.Host.Components.Engine /// public override bool PreferFileLogging => false; + /// + public override bool UseDotnetDump => false; + /// public override Task InstallationTask { get; } diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs index 3e1a92be14..3136e10aef 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs @@ -36,6 +36,9 @@ namespace Tgstation.Server.Host.Components.Engine /// public Task InstallationTask => Instance.InstallationTask; + /// + public bool UseDotnetDump => Instance.UseDotnetDump; + /// public void DoNotDeleteThisSession() => DangerousDropReference(); diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs index 77666748c7..22c1c14987 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs @@ -39,6 +39,9 @@ namespace Tgstation.Server.Host.Components.Engine /// public abstract bool PromptsForNetworkAccess { get; } + /// + public abstract bool UseDotnetDump { get; } + /// public abstract Task InstallationTask { get; } diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs index 00290840f0..362099251e 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs @@ -14,6 +14,7 @@ using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; +using Tgstation.Server.Host.System; using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Components.Engine @@ -59,6 +60,11 @@ namespace Tgstation.Server.Host.Components.Engine /// readonly IEventConsumer eventConsumer; + /// + /// The for the . + /// + readonly IDotnetDumpService dotnetDumpService; + /// /// The for the . /// @@ -100,12 +106,14 @@ namespace Tgstation.Server.Host.Components.Engine /// The value of . /// The value of . /// The value of . + /// The value of . /// The value of . - public EngineManager(IIOManager ioManager, IEngineInstaller engineInstaller, IEventConsumer eventConsumer, ILogger logger) + public EngineManager(IIOManager ioManager, IEngineInstaller engineInstaller, IEventConsumer eventConsumer, IDotnetDumpService dotnetDumpService, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.engineInstaller = engineInstaller ?? throw new ArgumentNullException(nameof(engineInstaller)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); installedVersions = new Dictionary>(); @@ -380,6 +388,23 @@ namespace Tgstation.Server.Host.Components.Engine await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken); } } + + bool needsDotnetDump; + lock (installedVersions) + needsDotnetDump = installedVersions.Values.Any(container => container.Instance.UseDotnetDump); + + if (needsDotnetDump) + { + logger.LogDebug("One or more engine installations uses dotnet-dump. Ensuring installation..."); + try + { + await dotnetDumpService.EnsureInstalled(true, cancellationToken); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to install dotnet-dump! Engine versions that use it will instead use standard process dumps!"); + } + } } /// @@ -473,6 +498,14 @@ namespace Tgstation.Server.Host.Components.Engine var versionString = version.ToString(); await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List { versionString }, false, cancellationToken); + if (installLock.UseDotnetDump) + { + if (progressReporter != null) + progressReporter.StageName = "Installing dotnet-dump"; + + await dotnetDumpService.EnsureInstalled(false, cancellationToken); + } + await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken); ourTcs.SetResult(); diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs index 402eac4a76..bdcfe2bf90 100644 --- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs @@ -46,6 +46,11 @@ namespace Tgstation.Server.Host.Components.Engine /// bool PreferFileLogging { get; } + /// + /// If dotnet-dump should be used to create process dumps for this installation. + /// + bool UseDotnetDump { get; } + /// /// The that completes when the BYOND version finished installing. /// diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs index ba400090e0..c522b9cedd 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs @@ -44,6 +44,9 @@ namespace Tgstation.Server.Host.Components.Engine /// public override bool PreferFileLogging => true; + /// + public override bool UseDotnetDump => true; + /// public override Task InstallationTask { get; } diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index dbb5577bc8..e4ee5c47d6 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Options; using Tgstation.Server.Api.Models; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Common.Http; -using Tgstation.Server.Host.Common; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.IO; @@ -232,21 +231,7 @@ namespace Tgstation.Server.Host.Components.Engine await Task.WhenAll(dirsMoveTasks.Concat(filesMoveTask)); } - var dotnetPaths = DotnetHelper.GetPotentialDotnetPaths(platformIdentifier.IsWindows) - .ToList(); - var tasks = dotnetPaths - .Select(path => IOManager.FileExists(path, cancellationToken)) - .ToList(); - - await Task.WhenAll(tasks); - - var selectedPathIndex = tasks.FindIndex(pathValidTask => pathValidTask.Result); - - if (selectedPathIndex == -1) - throw new JobException(ErrorCode.OpenDreamCantFindDotnet); - - var dotnetPath = dotnetPaths[selectedPathIndex]; - + var dotnetPath = await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken); const string DeployDir = "tgs_deploy"; int? buildExitCode = null; await HandleExtremelyLongPathOperation( diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 2b24bcb6cb..53dc6bbc5d 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -135,6 +135,11 @@ namespace Tgstation.Server.Host.Components /// readonly IAsyncDelayer asyncDelayer; + /// + /// The for the . + /// + readonly IDotnetDumpService dotnetDumpService; + /// /// The for the . /// @@ -177,6 +182,7 @@ namespace Tgstation.Server.Host.Components /// The value of . /// The value of . /// The value of . + /// The value of . /// The containing the value of . /// The containing the value of . public InstanceFactory( @@ -201,6 +207,7 @@ namespace Tgstation.Server.Host.Components IFileTransferTicketProvider fileTransferService, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IAsyncDelayer asyncDelayer, + IDotnetDumpService dotnetDumpService, IOptions generalConfigurationOptions, IOptions sessionConfigurationOptions) { @@ -225,6 +232,7 @@ namespace Tgstation.Server.Host.Components this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService)); this.remoteDeploymentManagerFactory = remoteDeploymentManagerFactory ?? throw new ArgumentNullException(nameof(remoteDeploymentManagerFactory)); this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService)); generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions)); } @@ -271,7 +279,12 @@ namespace Tgstation.Server.Host.Components var repoManager = repositoryManagerFactory.CreateRepositoryManager(repoIoManager, eventConsumer); try { - var engineManager = new EngineManager(byondIOManager, engineInstaller, eventConsumer, loggerFactory.CreateLogger()); + var engineManager = new EngineManager( + byondIOManager, + engineInstaller, + eventConsumer, + dotnetDumpService, + loggerFactory.CreateLogger()); var dmbFactory = new DmbFactory( databaseContextFactory, @@ -309,6 +322,7 @@ namespace Tgstation.Server.Host.Components serverPortProvider, eventConsumer, asyncDelayer, + dotnetDumpService, loggerFactory, loggerFactory.CreateLogger(), sessionConfiguration, diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 2b0ec82db2..0abbe96b1a 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -149,6 +149,11 @@ namespace Tgstation.Server.Host.Components.Session /// readonly IAsyncDelayer asyncDelayer; + /// + /// The for the . + /// + readonly IDotnetDumpService dotnetDumpService; + /// /// The that completes when DD makes it's first bridge request. /// @@ -236,7 +241,8 @@ namespace Tgstation.Server.Host.Components.Session /// The value of . /// The value of . /// The for the . - /// The for the . + /// 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 . @@ -253,6 +259,7 @@ namespace Tgstation.Server.Host.Components.Session IChatManager chat, IAssemblyInformationProvider assemblyInformationProvider, IAsyncDelayer asyncDelayer, + IDotnetDumpService dotnetDumpService, ILogger logger, Func postLifetimeCallback, uint? startupTimeout, @@ -272,6 +279,7 @@ namespace Tgstation.Server.Host.Components.Session ArgumentNullException.ThrowIfNull(assemblyInformationProvider); this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService)); apiValidationSession = apiValidate; @@ -474,7 +482,14 @@ namespace Tgstation.Server.Host.Components.Session cancellationToken); /// - public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) => process.CreateDump(outputFile, cancellationToken); + public async ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) + { + if (engineLock.UseDotnetDump + && await dotnetDumpService.Dump(process, outputFile, cancellationToken)) + return; + + await process.CreateDump(outputFile, cancellationToken); + } /// /// The for . diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 41c087b22f..4c7fab19b9 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -106,6 +106,11 @@ namespace Tgstation.Server.Host.Components.Session /// readonly IAsyncDelayer asyncDelayer; + /// + /// The for the . + /// + readonly IDotnetDumpService dotnetDumpService; + /// /// The for the . /// @@ -178,6 +183,7 @@ namespace Tgstation.Server.Host.Components.Session /// The value of . /// The value of . /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . @@ -196,6 +202,7 @@ namespace Tgstation.Server.Host.Components.Session IServerPortProvider serverPortProvider, IEventConsumer eventConsumer, IAsyncDelayer asyncDelayer, + IDotnetDumpService dotnetDumpService, ILoggerFactory loggerFactory, ILogger logger, SessionConfiguration sessionConfiguration, @@ -215,6 +222,7 @@ namespace Tgstation.Server.Host.Components.Session this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.sessionConfiguration = sessionConfiguration ?? throw new ArgumentNullException(nameof(sessionConfiguration)); @@ -346,6 +354,7 @@ namespace Tgstation.Server.Host.Components.Session chat, assemblyInformationProvider, asyncDelayer, + dotnetDumpService, loggerFactory.CreateLogger(), () => LogDDOutput( process, @@ -436,6 +445,7 @@ namespace Tgstation.Server.Host.Components.Session chat, assemblyInformationProvider, asyncDelayer, + dotnetDumpService, loggerFactory.CreateLogger(), () => ValueTask.CompletedTask, null, diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index d533a167a0..bad47cde0b 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -365,11 +365,9 @@ namespace Tgstation.Server.Host.Core } // only global repo manager should be for the OD repo + // god help me if we need more var openDreamRepositoryDirectory = ioManager.ConcatPath( - Environment.GetFolderPath( - Environment.SpecialFolder.LocalApplicationData, - Environment.SpecialFolderOption.DoNotVerify), - assemblyInformationProvider.VersionPrefix, + ioManager.GetPathInLocalDirectory(assemblyInformationProvider), "OpenDreamRepository"); services.AddSingleton( services => services @@ -416,6 +414,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // configure misc services services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/Extensions/IOManagerExtensions.cs b/src/Tgstation.Server.Host/Extensions/IOManagerExtensions.cs new file mode 100644 index 0000000000..1992eeb435 --- /dev/null +++ b/src/Tgstation.Server.Host/Extensions/IOManagerExtensions.cs @@ -0,0 +1,31 @@ +using System; + +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.System; + +namespace Tgstation.Server.Host.Extensions +{ + /// + /// Extension methods for . + /// + static class IOManagerExtensions + { + /// + /// Gets the local application data folder used by TGS. + /// + /// The to use. + /// The to use. + /// The path to the local application data directory used by TGS. + public static string GetPathInLocalDirectory(this IIOManager ioManager, IAssemblyInformationProvider assemblyInformationProvider) + { + ArgumentNullException.ThrowIfNull(ioManager); + ArgumentNullException.ThrowIfNull(assemblyInformationProvider); + + return ioManager.ConcatPath( + Environment.GetFolderPath( + Environment.SpecialFolder.LocalApplicationData, // we use local application data here instead of comman application data because we store stuff here we don't want other users interfering with + Environment.SpecialFolderOption.DoNotVerify), + assemblyInformationProvider.VersionPrefix); + } + } +} diff --git a/src/Tgstation.Server.Host/System/DotnetDumpService.cs b/src/Tgstation.Server.Host/System/DotnetDumpService.cs new file mode 100644 index 0000000000..1c9d632606 --- /dev/null +++ b/src/Tgstation.Server.Host/System/DotnetDumpService.cs @@ -0,0 +1,225 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Extensions; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; +using Tgstation.Server.Host.Utils; + +namespace Tgstation.Server.Host.System +{ + /// + sealed class DotnetDumpService : IDotnetDumpService, IDisposable + { + /// + /// The for the . + /// + readonly IProcessExecutor processExecutor; + + /// + /// The for the . + /// + readonly IIOManager ioManager; + + /// + /// The for the . + /// + readonly IAssemblyInformationProvider assemblyInformationProvider; + + /// + /// The for the . + /// + readonly IPlatformIdentifier platformIdentifier; + + /// + /// The for the . + /// + readonly ILogger logger; + + /// + /// The for the . + /// + readonly SessionConfiguration sessionConfiguration; + + /// + /// used for checking for the presence of and installing dotnet-dump. + /// + readonly SemaphoreSlim installCheckSemaphore; + + /// + /// The result of the last installation check. means installed. means not installed. means the check was never run. + /// + bool? lastInstallCheckResult; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The value of . + /// The containing the value of . + public DotnetDumpService( + IProcessExecutor processExecutor, + IIOManager ioManager, + IAssemblyInformationProvider assemblyInformationProvider, + IPlatformIdentifier platformIdentifier, + ILogger logger, + IOptions sessionConfigurationOptions) + { + this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions)); + + installCheckSemaphore = new SemaphoreSlim(1); + } + + /// + public void Dispose() => installCheckSemaphore.Dispose(); + + /// + public async ValueTask EnsureInstalled(bool deploymentPipeline, CancellationToken cancellationToken) + { + logger.LogTrace("EnsureInstalled"); + + if (lastInstallCheckResult == true) + return; + + using (await SemaphoreSlimContext.Lock(installCheckSemaphore, cancellationToken)) + { + var installDir = await CheckInstalled(cancellationToken); + if (lastInstallCheckResult == true) + return; + + await Install(installDir ?? GetDirectoryPath(), deploymentPipeline, cancellationToken); + } + } + + /// + public async ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken) + { + logger.LogTrace("dotnet-dump requested..."); + string? installDir = null; + if (!lastInstallCheckResult.HasValue) + using (await SemaphoreSlimContext.Lock(installCheckSemaphore, cancellationToken)) + installDir = await CheckInstalled(cancellationToken); + + if (lastInstallCheckResult != true) + return false; + + installDir ??= GetDirectoryPath(); + var exeExtension = platformIdentifier.IsWindows + ? ".exe" + : String.Empty; + + var resolvedInstallDir = ioManager.ResolvePath(installDir); + + var executablePath = ioManager.ConcatPath( + resolvedInstallDir, + $"dotnet-dump{exeExtension}"); + + await using var dumpProcess = processExecutor.LaunchProcess( + executablePath, + resolvedInstallDir, + $"collect -p {process.Id} -o \"{outputFile}\"", + readStandardHandles: true, + noShellExecute: true); + + int? exitCode; + using (cancellationToken.Register(() => dumpProcess.Terminate())) + exitCode = await dumpProcess.Lifetime; + + var output = await dumpProcess.GetCombinedOutput(cancellationToken); + + if (exitCode != 0) + throw new JobException( + ErrorCode.DumpProcessFailure, + new JobException( + $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}")); + + logger.LogDebug("dotnet-dump output:{newline}{output}", Environment.NewLine, output); + + return true; + } + + /// + /// Sets if it is . + /// + /// The for the operation. + /// if was not . The result of otherwise. + async ValueTask CheckInstalled(CancellationToken cancellationToken) + { + if (lastInstallCheckResult.HasValue) + return null; + + logger.LogTrace("Checking if dotnet-dump is installed..."); + + var directory = GetDirectoryPath(); + lastInstallCheckResult = await ioManager.DirectoryExists(directory, cancellationToken); + + logger.LogTrace("dotnet-dump installed: {result}", lastInstallCheckResult.Value); + + return directory; + } + + /// + /// Locally install the dotnet-dump tool. + /// + /// The directory to install dotnet dump in. + /// If this operation is part of the deployment pipeline. + /// The for the operation. + /// A representing the running operation. + async ValueTask Install(string installDir, bool deploymentPipeline, CancellationToken cancellationToken) + { + var dotnetPath = await DotnetHelper.GetDotnetPath(platformIdentifier, ioManager, cancellationToken); + + logger.LogTrace("Ensuring installation directory is gone..."); + await ioManager.DeleteDirectory(installDir, cancellationToken); + + var resolvedInstallDir = ioManager.ResolvePath(installDir); + + logger.LogTrace("Installing dotnet-dump..."); + await using var installProcess = processExecutor.LaunchProcess( + dotnetPath, + ioManager.ResolvePath(), + $"tool install --tool-path \"{resolvedInstallDir}\" dotnet-dump", + readStandardHandles: true, + noShellExecute: true); + + if (deploymentPipeline && sessionConfiguration.LowPriorityDeploymentProcesses) + installProcess.AdjustPriority(false); + + int? exitCode; + using (cancellationToken.Register(() => installProcess.Terminate())) + exitCode = await installProcess.Lifetime; + + var output = await installProcess.GetCombinedOutput(cancellationToken); + + if (exitCode != 0) + throw new JobException( + ErrorCode.CantInstallDotnetDump, + new JobException( + $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}")); + + logger.LogDebug("dotnet tool install output:{newline}{output}", Environment.NewLine, output); + } + + /// + /// Get the path to the dotnet-dump installation directory TGS uses. + /// + /// The path to the dotnet-dump installation directory. + string GetDirectoryPath() => ioManager.ConcatPath( + ioManager.GetPathInLocalDirectory(assemblyInformationProvider), + "dotnet-dump"); + } +} diff --git a/src/Tgstation.Server.Host/System/DotnetHelper.cs b/src/Tgstation.Server.Host/System/DotnetHelper.cs new file mode 100644 index 0000000000..9894e98a04 --- /dev/null +++ b/src/Tgstation.Server.Host/System/DotnetHelper.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.IO; +using Tgstation.Server.Host.Jobs; + +namespace Tgstation.Server.Host.System +{ + /// + /// Helper methods for working with the dotnet executable. + /// + static class DotnetHelper + { + /// + /// Locate a dotnet executable to use. + /// + /// The to use. + /// The to use. + /// The for the operation. + /// A dotnet executable path to use. + public static async ValueTask GetDotnetPath(IPlatformIdentifier platformIdentifier, IIOManager ioManager, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(platformIdentifier); + ArgumentNullException.ThrowIfNull(ioManager); + + var dotnetPaths = Common.DotnetHelper.GetPotentialDotnetPaths(platformIdentifier.IsWindows) + .ToList(); + var tasks = dotnetPaths + .Select(path => ioManager.FileExists(path, cancellationToken)) + .ToList(); + + await Task.WhenAll(tasks); + + var selectedPathIndex = tasks.FindIndex(pathValidTask => pathValidTask.Result); + + if (selectedPathIndex == -1) + throw new JobException(ErrorCode.CantFindDotnet); + + var dotnetPath = dotnetPaths[selectedPathIndex]; + + return dotnetPath; + } + } +} diff --git a/src/Tgstation.Server.Host/System/IDotnetDumpService.cs b/src/Tgstation.Server.Host/System/IDotnetDumpService.cs new file mode 100644 index 0000000000..c8a1263e0b --- /dev/null +++ b/src/Tgstation.Server.Host/System/IDotnetDumpService.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.System +{ + /// + /// Service for managing the dotnet-dump installation. + /// + public interface IDotnetDumpService + { + /// + /// Attempt to install dotnet-dump if it is not installed. + /// + /// If this operation is part of the deployment pipeline. + /// The for the operation. + /// A representing the running operation. + ValueTask EnsureInstalled(bool deploymentPipeline, CancellationToken cancellationToken); + + /// + /// Attempt to dump a given . + /// + /// The to dump. + /// The path to the output dump file. + /// The for the operation. + /// if the dump proceeded, if dotnet-dump was not installed. + ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs index 8077577aaf..695fc776c5 100644 --- a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.System if (exitCode != 0) throw new JobException( - ErrorCode.GCoreFailure, + ErrorCode.DumpProcessFailure, new JobException( $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}")); diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 4d5e1685a5..c963d90fa9 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -575,7 +575,7 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(restartJob, 20, false, null, cancellationToken); } - Assert.IsTrue(job.ErrorCode == ErrorCode.GameServerOffline || job.ErrorCode == ErrorCode.GCoreFailure, $"{job.ErrorCode}: {job.ExceptionDetails}"); + Assert.IsTrue(job.ErrorCode == ErrorCode.GameServerOffline || job.ErrorCode == ErrorCode.DumpProcessFailure, $"{job.ErrorCode}: {job.ExceptionDetails}"); var restartJob2 = await instanceClient.DreamDaemon.Restart(cancellationToken); await WaitForJob(restartJob2, 20, false, null, cancellationToken); From 8a3655c6f59a5112165deea802bcef9f0e53f1cf Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 31 Jan 2024 23:11:54 -0500 Subject: [PATCH 032/137] Fix `EngineManager` not respecting `Session:LowPriorityDeploymentProcesses` --- .../Engine/DelegatingEngineInstaller.cs | 4 ++-- .../Components/Engine/EngineInstallerBase.cs | 2 +- .../Components/Engine/EngineManager.cs | 21 ++++++++++++------- .../Components/Engine/IEngineInstaller.cs | 3 ++- .../Components/Engine/OpenDreamInstaller.cs | 4 ++-- .../Components/Engine/PosixByondInstaller.cs | 2 +- .../Engine/WindowsByondInstaller.cs | 11 +++++----- .../Engine/WindowsOpenDreamInstaller.cs | 9 +++++--- .../Engine/TestPosixByondInstaller.cs | 6 +++--- tests/Tgstation.Server.Tests/TestVersions.cs | 2 +- 10 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs index 1157dff565..91887e7314 100644 --- a/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs @@ -41,8 +41,8 @@ namespace Tgstation.Server.Host.Components.Engine => DelegateCall(version, installer => installer.DownloadVersion(version, jobProgressReporter, cancellationToken)); /// - public ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken) - => DelegateCall(version, installer => installer.Install(version, path, cancellationToken)); + public ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken) + => DelegateCall(version, installer => installer.Install(version, path, deploymentPipelineProcesses, cancellationToken)); /// public ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs b/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs index 7c25c0a13c..12cf8e657c 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs @@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components.Engine public abstract Task CleanCache(CancellationToken cancellationToken); /// - public abstract ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken); + public abstract ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken); /// public abstract ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs index 362099251e..865c61ab27 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs @@ -463,6 +463,7 @@ namespace Tgstation.Server.Host.Components.Engine installLock = installationContainer.AddReference(); } + var deploymentPipelineProcesses = !neededForLock; try { if (installedOrInstalling) @@ -496,26 +497,26 @@ namespace Tgstation.Server.Host.Components.Engine progressReporter.StageName = "Running event"; var versionString = version.ToString(); - await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List { versionString }, false, cancellationToken); + await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List { versionString }, deploymentPipelineProcesses, cancellationToken); if (installLock.UseDotnetDump) { if (progressReporter != null) progressReporter.StageName = "Installing dotnet-dump"; - await dotnetDumpService.EnsureInstalled(false, cancellationToken); + await dotnetDumpService.EnsureInstalled(deploymentPipelineProcesses, cancellationToken); } - await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken); + await InstallVersionFiles(progressReporter, version, customVersionStream, deploymentPipelineProcesses, cancellationToken); ourTcs.SetResult(); - await eventConsumer.HandleEvent(EventType.EngineInstallComplete, new List { versionString }, false, cancellationToken); + await eventConsumer.HandleEvent(EventType.EngineInstallComplete, new List { versionString }, deploymentPipelineProcesses, cancellationToken); } catch (Exception ex) { if (ex is not OperationCanceledException) - await eventConsumer.HandleEvent(EventType.EngineInstallFail, new List { ex.Message }, false, cancellationToken); + await eventConsumer.HandleEvent(EventType.EngineInstallFail, new List { ex.Message }, deploymentPipelineProcesses, cancellationToken); lock (installedVersions) installedVersions.Remove(version); @@ -539,9 +540,15 @@ namespace Tgstation.Server.Host.Components.Engine /// The optional for the operation. /// The being installed with the number set if appropriate. /// Custom zip file to use. Will cause a number to be added. + /// If processes should be launched as part of the deployment pipeline. /// The for the operation. /// A representing the running operation. - async ValueTask InstallVersionFiles(JobProgressReporter? progressReporter, EngineVersion version, Stream? customVersionStream, CancellationToken cancellationToken) + async ValueTask InstallVersionFiles( + JobProgressReporter? progressReporter, + EngineVersion version, + Stream? customVersionStream, + bool deploymentPipelineProcesses, + CancellationToken cancellationToken) { var installFullPath = ioManager.ResolvePath(version.ToString()); async ValueTask DirectoryCleanup() @@ -587,7 +594,7 @@ namespace Tgstation.Server.Host.Components.Engine if (progressReporter != null) progressReporter.StageName = "Running installation actions"; - await engineInstaller.Install(version, installFullPath, cancellationToken); + await engineInstaller.Install(version, installFullPath, deploymentPipelineProcesses, cancellationToken); if (progressReporter != null) progressReporter.StageName = "Writing version file"; diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs index 7169ffe4b5..a4ad57a4c0 100644 --- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs @@ -34,9 +34,10 @@ namespace Tgstation.Server.Host.Components.Engine /// /// The being installed. /// The path to the installation. + /// If the operation should consider processes it launches to be part of the deployment pipeline. /// The for the operation. /// A representing the running operation. - ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken); + ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken); /// /// Does actions necessary to get upgrade a version installed by a previous version of TGS. diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index e4ee5c47d6..bdf08387bb 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -192,7 +192,7 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public override async ValueTask Install(EngineVersion version, string installPath, CancellationToken cancellationToken) + public override async ValueTask Install(EngineVersion version, string installPath, bool deploymentPipelineProcesses, CancellationToken cancellationToken) { CheckVersionValidity(version); ArgumentNullException.ThrowIfNull(installPath); @@ -247,7 +247,7 @@ namespace Tgstation.Server.Host.Components.Engine !GeneralConfiguration.OpenDreamSuppressInstallOutput, !GeneralConfiguration.OpenDreamSuppressInstallOutput); - if (SessionConfiguration.LowPriorityDeploymentProcesses) + if (deploymentPipelineProcesses && SessionConfiguration.LowPriorityDeploymentProcesses) buildProcess.AdjustPriority(false); using (cancellationToken.Register(() => buildProcess.Terminate())) diff --git a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs index 037cacd437..ef019aa354 100644 --- a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs @@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public override ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken) + public override ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken) { CheckVersionValidity(version); ArgumentNullException.ThrowIfNull(path); diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs index 4bafeb9e05..3b6790cee1 100644 --- a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs @@ -127,7 +127,7 @@ namespace Tgstation.Server.Host.Components.Engine public void Dispose() => semaphore.Dispose(); /// - public override ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken) + public override ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken) { CheckVersionValidity(version); ArgumentNullException.ThrowIfNull(path); @@ -142,7 +142,7 @@ namespace Tgstation.Server.Host.Components.Engine if (!generalConfiguration.SkipAddingByondFirewallException) { - var firewallTask = AddDreamDaemonToFirewall(version, path, cancellationToken); + var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken); tasks.Add(firewallTask); } @@ -165,7 +165,7 @@ namespace Tgstation.Server.Host.Components.Engine return; Logger.LogInformation("BYOND Version {version} needs dd.exe added to firewall", version); - await AddDreamDaemonToFirewall(version, path, cancellationToken); + await AddDreamDaemonToFirewall(version, path, true, cancellationToken); } /// @@ -243,9 +243,10 @@ namespace Tgstation.Server.Host.Components.Engine /// /// The BYOND . /// The path to the BYOND installation. + /// If the operation is part of the deployment pipeline. /// The for the operation. /// A representing the running operation. - async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, CancellationToken cancellationToken) + async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken) { var dreamDaemonName = GetDreamDaemonName(version.Version!, out var usesDDExe); @@ -268,7 +269,7 @@ namespace Tgstation.Server.Host.Components.Engine Logger, ruleName, dreamDaemonPath, - sessionConfiguration.LowPriorityDeploymentProcesses, + deploymentPipelineProcesses && sessionConfiguration.LowPriorityDeploymentProcesses, cancellationToken); } catch (Exception ex) diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs index 25968446fe..1cc8da52c5 100644 --- a/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs @@ -66,15 +66,17 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public override ValueTask Install(EngineVersion version, string installPath, CancellationToken cancellationToken) + public override ValueTask Install(EngineVersion version, string installPath, bool deploymentPipelineProcesses, CancellationToken cancellationToken) { var installTask = base.Install( version, installPath, + deploymentPipelineProcesses, cancellationToken); var firewallTask = AddServerFirewallException( version, installPath, + deploymentPipelineProcesses, cancellationToken); return ValueTaskExtensions.WhenAll(installTask, firewallTask); @@ -101,9 +103,10 @@ namespace Tgstation.Server.Host.Components.Engine /// /// The BYOND . /// The path to the BYOND installation. + /// If the operation is part of the deployment pipeline. /// The for the operation. /// A representing the running operation. - async ValueTask AddServerFirewallException(EngineVersion version, string path, CancellationToken cancellationToken) + async ValueTask AddServerFirewallException(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken) { if (GeneralConfiguration.SkipAddingByondFirewallException) return; @@ -123,7 +126,7 @@ namespace Tgstation.Server.Host.Components.Engine Logger, ruleName, serverExePath, - SessionConfiguration.LowPriorityDeploymentProcesses, + deploymentPipelineProcesses && SessionConfiguration.LowPriorityDeploymentProcesses, cancellationToken); } catch (Exception ex) diff --git a/tests/Tgstation.Server.Host.Tests/Components/Engine/TestPosixByondInstaller.cs b/tests/Tgstation.Server.Host.Tests/Components/Engine/TestPosixByondInstaller.cs index 22cc145683..60e86ad63e 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Engine/TestPosixByondInstaller.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Engine/TestPosixByondInstaller.cs @@ -90,7 +90,7 @@ namespace Tgstation.Server.Host.Components.Engine.Tests var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object); const string FakePath = "fake"; - await Assert.ThrowsExceptionAsync(() => installer.Install(null, null, default).AsTask()); + await Assert.ThrowsExceptionAsync(() => installer.Install(null, null, false, default).AsTask()); var byondVersion = new EngineVersion { @@ -98,10 +98,10 @@ namespace Tgstation.Server.Host.Components.Engine.Tests Version = new Version(123, 252345), }; - await Assert.ThrowsExceptionAsync(() => installer.Install(byondVersion, null, default).AsTask()); + await Assert.ThrowsExceptionAsync(() => installer.Install(byondVersion, null, false, default).AsTask()); byondVersion.Version = new Version(511, 1385); - await installer.Install(byondVersion, FakePath, default); + await installer.Install(byondVersion, FakePath, false, default); mockPostWriteHandler.Verify(x => x.HandleWrite(It.IsAny()), Times.Exactly(4)); } diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 591517a46f..0b4cc591bb 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -477,7 +477,7 @@ namespace Tgstation.Server.Tests if (byondInstaller is WindowsByondInstaller) typeof(WindowsByondInstaller).GetField("installedDirectX", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(byondInstaller, true); - await byondInstaller.Install(engineVersion, tempPath, default); + await byondInstaller.Install(engineVersion, tempPath, false, default); var binPath = (string)typeof(ByondInstallerBase).GetField("ByondBinPath", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null); var ddNameFunc = installerType.GetMethod("GetDreamDaemonName", BindingFlags.Instance | BindingFlags.NonPublic); From 198c3c1eafc1ca466ad7f465fa1b120e8108b5a0 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 31 Jan 2024 23:21:54 -0500 Subject: [PATCH 033/137] Dotnet dumps will use the `.net.dmp` extension Also fix weirdness with file extension when two dumps were created in the same second --- .../Components/Session/ISessionController.cs | 5 +++++ .../Components/Session/SessionController.cs | 5 +++++ .../Components/Watchdog/WatchdogBase.cs | 16 +++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index a178e7c3c1..bba0dd4152 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -84,6 +84,11 @@ namespace Tgstation.Server.Host.Components.Session /// bool DMApiAvailable { get; } + /// + /// The file extension to use for process dumps created from this session. + /// + string DumpFileExtension { get; } + /// /// Releases the without terminating it. Also calls . /// diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 0abbe96b1a..cf0568544a 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -104,6 +104,11 @@ namespace Tgstation.Server.Host.Components.Session /// public bool ProcessingRebootBridgeRequest => rebootBridgeRequestsProcessing > 0; + /// + public string DumpFileExtension => engineLock.UseDotnetDump + ? ".net.dmp" + : ".dmp"; + /// /// The up to date . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 13f62157f1..43454924ce 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -1225,21 +1225,27 @@ namespace Tgstation.Server.Host.Components.Watchdog async ValueTask CreateDumpNoLock(CancellationToken cancellationToken) { const string DumpDirectory = "ProcessDumps"; + + var session = GetActiveController(); + if (session?.Lifetime.IsCompleted != false) + throw new JobException(ErrorCode.GameServerOffline); + + var dumpFileExtension = session.DumpFileExtension; + var dumpFileNameTemplate = diagnosticsIOManager.ResolvePath( diagnosticsIOManager.ConcatPath( DumpDirectory, - $"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}.dmp")); + $"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}")); - var dumpFileName = dumpFileNameTemplate; + var dumpFileName = $"{dumpFileNameTemplate}{dumpFileExtension}"; var iteration = 0; while (await diagnosticsIOManager.FileExists(dumpFileName, cancellationToken)) - dumpFileName = $"{dumpFileNameTemplate} ({++iteration})"; + dumpFileName = $"{dumpFileNameTemplate} ({++iteration}){dumpFileExtension}"; if (iteration == 0) await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken); - var session = GetActiveController(); - if (session?.Lifetime.IsCompleted != false) + if (session.Lifetime.IsCompleted) throw new JobException(ErrorCode.GameServerOffline); Logger.LogInformation("Dumping session to {dumpFileName}...", dumpFileName); From a31d0241e1872db9de8ff117449f00bfeca1873f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 1 Feb 2024 18:49:56 -0500 Subject: [PATCH 034/137] Switch to using `Microsoft.Diagnostics.NETCore.Client` for dotnet dumps Much simpler --- build/Version.props | 6 +- src/Tgstation.Server.Api/Models/ErrorCode.cs | 18 +- .../Components/Engine/EngineManager.cs | 35 +-- .../Components/Engine/OpenDreamInstaller.cs | 3 + .../Components/InstanceFactory.cs | 1 - .../Components/Session/SessionController.cs | 9 +- .../System/DotnetDumpService.cs | 209 ++---------------- .../System/DotnetHelper.cs | 8 +- .../System/IDotnetDumpService.cs | 12 +- .../System/PosixProcessFeatures.cs | 2 +- .../Tgstation.Server.Host.csproj | 2 + .../Live/Instance/WatchdogTest.cs | 16 +- 12 files changed, 56 insertions(+), 265 deletions(-) diff --git a/build/Version.props b/build/Version.props index c58250e7c5..d244ecd7fa 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,10 +5,10 @@ 6.1.5 5.1.0 - 10.1.0 + 10.0.0 7.0.0 - 14.0.0 - 16.0.0 + 13.0.1 + 15.0.1 7.0.2 5.8.0 1.4.1 diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 1e1a42ce4d..28f39a7638 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -528,10 +528,10 @@ namespace Tgstation.Server.Api.Models MissingGCore, /// - /// Non-zero gcore/dotnet-dump exit code. + /// Non-zero gcore exit code. /// - [Description("Could not create dump as the dumping process exited with a non-zero exit code!")] - DumpProcessFailure, + [Description("Could not create dump as gcore exited with a non-zero exit code!")] + GCoreFailure, /// /// Attempted to test merge with an invalid remote repository. @@ -636,21 +636,15 @@ namespace Tgstation.Server.Api.Models BroadcastFailure, /// - /// Unable to locate the dotnet executable for a necessary operation. + /// Could not compile OpenDream due to a missing dotnet executable. /// - [Description("Unable to locate the dotnet executable!")] - CantFindDotnet, + [Description("OpenDream could not be compiled due to being unable to locate the dotnet executable!")] + OpenDreamCantFindDotnet, /// /// Could not install OpenDream due to it not meeting the minimum version requirements. /// [Description("The specified OpenDream version is too old!")] OpenDreamTooOld, - - /// - /// Could not locally install the dotnet-dump tool. - /// - [Description("Could not locally install the dotnet-dump tool!")] - CantInstallDotnetDump, } } diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs index 865c61ab27..a7cbd10f4f 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs @@ -14,7 +14,6 @@ using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; -using Tgstation.Server.Host.System; using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Components.Engine @@ -60,11 +59,6 @@ namespace Tgstation.Server.Host.Components.Engine /// readonly IEventConsumer eventConsumer; - /// - /// The for the . - /// - readonly IDotnetDumpService dotnetDumpService; - /// /// The for the . /// @@ -106,14 +100,12 @@ namespace Tgstation.Server.Host.Components.Engine /// The value of . /// The value of . /// The value of . - /// The value of . /// The value of . - public EngineManager(IIOManager ioManager, IEngineInstaller engineInstaller, IEventConsumer eventConsumer, IDotnetDumpService dotnetDumpService, ILogger logger) + public EngineManager(IIOManager ioManager, IEngineInstaller engineInstaller, IEventConsumer eventConsumer, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.engineInstaller = engineInstaller ?? throw new ArgumentNullException(nameof(engineInstaller)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); - this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); installedVersions = new Dictionary>(); @@ -388,23 +380,6 @@ namespace Tgstation.Server.Host.Components.Engine await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken); } } - - bool needsDotnetDump; - lock (installedVersions) - needsDotnetDump = installedVersions.Values.Any(container => container.Instance.UseDotnetDump); - - if (needsDotnetDump) - { - logger.LogDebug("One or more engine installations uses dotnet-dump. Ensuring installation..."); - try - { - await dotnetDumpService.EnsureInstalled(true, cancellationToken); - } - catch (Exception ex) - { - logger.LogWarning(ex, "Failed to install dotnet-dump! Engine versions that use it will instead use standard process dumps!"); - } - } } /// @@ -499,14 +474,6 @@ namespace Tgstation.Server.Host.Components.Engine var versionString = version.ToString(); await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List { versionString }, deploymentPipelineProcesses, cancellationToken); - if (installLock.UseDotnetDump) - { - if (progressReporter != null) - progressReporter.StageName = "Installing dotnet-dump"; - - await dotnetDumpService.EnsureInstalled(deploymentPipelineProcesses, cancellationToken); - } - await InstallVersionFiles(progressReporter, version, customVersionStream, deploymentPipelineProcesses, cancellationToken); ourTcs.SetResult(); diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index bdf08387bb..eb0bca9450 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -232,6 +232,9 @@ namespace Tgstation.Server.Host.Components.Engine } var dotnetPath = await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken); + if (dotnetPath == null) + throw new JobException(ErrorCode.OpenDreamCantFindDotnet); + const string DeployDir = "tgs_deploy"; int? buildExitCode = null; await HandleExtremelyLongPathOperation( diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 53dc6bbc5d..f12eb28fa5 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -283,7 +283,6 @@ namespace Tgstation.Server.Host.Components byondIOManager, engineInstaller, eventConsumer, - dotnetDumpService, loggerFactory.CreateLogger()); var dmbFactory = new DmbFactory( diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index cf0568544a..44d96340b0 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -487,13 +487,12 @@ namespace Tgstation.Server.Host.Components.Session cancellationToken); /// - public async ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) + public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) { - if (engineLock.UseDotnetDump - && await dotnetDumpService.Dump(process, outputFile, cancellationToken)) - return; + if (engineLock.UseDotnetDump) + return dotnetDumpService.Dump(process, outputFile, cancellationToken); - await process.CreateDump(outputFile, cancellationToken); + return process.CreateDump(outputFile, cancellationToken); } /// diff --git a/src/Tgstation.Server.Host/System/DotnetDumpService.cs b/src/Tgstation.Server.Host/System/DotnetDumpService.cs index 1c9d632606..f7ff800535 100644 --- a/src/Tgstation.Server.Host/System/DotnetDumpService.cs +++ b/src/Tgstation.Server.Host/System/DotnetDumpService.cs @@ -2,224 +2,47 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -using Tgstation.Server.Api.Models; -using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Extensions; -using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Jobs; -using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.System { /// - sealed class DotnetDumpService : IDotnetDumpService, IDisposable + sealed class DotnetDumpService : IDotnetDumpService { - /// - /// The for the . - /// - readonly IProcessExecutor processExecutor; - - /// - /// The for the . - /// - readonly IIOManager ioManager; - - /// - /// The for the . - /// - readonly IAssemblyInformationProvider assemblyInformationProvider; - - /// - /// The for the . - /// - readonly IPlatformIdentifier platformIdentifier; - /// /// The for the . /// readonly ILogger logger; - /// - /// The for the . - /// - readonly SessionConfiguration sessionConfiguration; - - /// - /// used for checking for the presence of and installing dotnet-dump. - /// - readonly SemaphoreSlim installCheckSemaphore; - - /// - /// The result of the last installation check. means installed. means not installed. means the check was never run. - /// - bool? lastInstallCheckResult; - /// /// Initializes a new instance of the class. /// - /// The value of . - /// The value of . - /// The value of . - /// The value of . /// The value of . - /// The containing the value of . public DotnetDumpService( - IProcessExecutor processExecutor, - IIOManager ioManager, - IAssemblyInformationProvider assemblyInformationProvider, - IPlatformIdentifier platformIdentifier, - ILogger logger, - IOptions sessionConfigurationOptions) + ILogger logger) { - this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider)); - this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions)); - - installCheckSemaphore = new SemaphoreSlim(1); } /// - public void Dispose() => installCheckSemaphore.Dispose(); - - /// - public async ValueTask EnsureInstalled(bool deploymentPipeline, CancellationToken cancellationToken) + public async ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken) { - logger.LogTrace("EnsureInstalled"); + // need to use an extra timeout here because if the process is truly deadlocked. A cooperative dump will hang forever + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - if (lastInstallCheckResult == true) - return; - - using (await SemaphoreSlimContext.Lock(installCheckSemaphore, cancellationToken)) + const int TimeoutMinutes = 5; + cts.CancelAfter(TimeSpan.FromMinutes(TimeoutMinutes)); + cts.Token.Register(() => { - var installDir = await CheckInstalled(cancellationToken); - if (lastInstallCheckResult == true) - return; + if (!cancellationToken.IsCancellationRequested) + logger.LogError("dotnet-dump timed out after {minutes} minutes!", TimeoutMinutes); + }); - await Install(installDir ?? GetDirectoryPath(), deploymentPipeline, cancellationToken); - } + var pid = process.Id; + logger.LogDebug("dotnet-dump requested for PID {pid}...", pid); + var client = new DiagnosticsClient(pid); + await client.WriteDumpAsync(DumpType.Full, outputFile, false, cts.Token); } - - /// - public async ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken) - { - logger.LogTrace("dotnet-dump requested..."); - string? installDir = null; - if (!lastInstallCheckResult.HasValue) - using (await SemaphoreSlimContext.Lock(installCheckSemaphore, cancellationToken)) - installDir = await CheckInstalled(cancellationToken); - - if (lastInstallCheckResult != true) - return false; - - installDir ??= GetDirectoryPath(); - var exeExtension = platformIdentifier.IsWindows - ? ".exe" - : String.Empty; - - var resolvedInstallDir = ioManager.ResolvePath(installDir); - - var executablePath = ioManager.ConcatPath( - resolvedInstallDir, - $"dotnet-dump{exeExtension}"); - - await using var dumpProcess = processExecutor.LaunchProcess( - executablePath, - resolvedInstallDir, - $"collect -p {process.Id} -o \"{outputFile}\"", - readStandardHandles: true, - noShellExecute: true); - - int? exitCode; - using (cancellationToken.Register(() => dumpProcess.Terminate())) - exitCode = await dumpProcess.Lifetime; - - var output = await dumpProcess.GetCombinedOutput(cancellationToken); - - if (exitCode != 0) - throw new JobException( - ErrorCode.DumpProcessFailure, - new JobException( - $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}")); - - logger.LogDebug("dotnet-dump output:{newline}{output}", Environment.NewLine, output); - - return true; - } - - /// - /// Sets if it is . - /// - /// The for the operation. - /// if was not . The result of otherwise. - async ValueTask CheckInstalled(CancellationToken cancellationToken) - { - if (lastInstallCheckResult.HasValue) - return null; - - logger.LogTrace("Checking if dotnet-dump is installed..."); - - var directory = GetDirectoryPath(); - lastInstallCheckResult = await ioManager.DirectoryExists(directory, cancellationToken); - - logger.LogTrace("dotnet-dump installed: {result}", lastInstallCheckResult.Value); - - return directory; - } - - /// - /// Locally install the dotnet-dump tool. - /// - /// The directory to install dotnet dump in. - /// If this operation is part of the deployment pipeline. - /// The for the operation. - /// A representing the running operation. - async ValueTask Install(string installDir, bool deploymentPipeline, CancellationToken cancellationToken) - { - var dotnetPath = await DotnetHelper.GetDotnetPath(platformIdentifier, ioManager, cancellationToken); - - logger.LogTrace("Ensuring installation directory is gone..."); - await ioManager.DeleteDirectory(installDir, cancellationToken); - - var resolvedInstallDir = ioManager.ResolvePath(installDir); - - logger.LogTrace("Installing dotnet-dump..."); - await using var installProcess = processExecutor.LaunchProcess( - dotnetPath, - ioManager.ResolvePath(), - $"tool install --tool-path \"{resolvedInstallDir}\" dotnet-dump", - readStandardHandles: true, - noShellExecute: true); - - if (deploymentPipeline && sessionConfiguration.LowPriorityDeploymentProcesses) - installProcess.AdjustPriority(false); - - int? exitCode; - using (cancellationToken.Register(() => installProcess.Terminate())) - exitCode = await installProcess.Lifetime; - - var output = await installProcess.GetCombinedOutput(cancellationToken); - - if (exitCode != 0) - throw new JobException( - ErrorCode.CantInstallDotnetDump, - new JobException( - $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}")); - - logger.LogDebug("dotnet tool install output:{newline}{output}", Environment.NewLine, output); - } - - /// - /// Get the path to the dotnet-dump installation directory TGS uses. - /// - /// The path to the dotnet-dump installation directory. - string GetDirectoryPath() => ioManager.ConcatPath( - ioManager.GetPathInLocalDirectory(assemblyInformationProvider), - "dotnet-dump"); } } diff --git a/src/Tgstation.Server.Host/System/DotnetHelper.cs b/src/Tgstation.Server.Host/System/DotnetHelper.cs index 9894e98a04..33adbbeb7e 100644 --- a/src/Tgstation.Server.Host/System/DotnetHelper.cs +++ b/src/Tgstation.Server.Host/System/DotnetHelper.cs @@ -3,9 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Api.Models; using Tgstation.Server.Host.IO; -using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.System { @@ -20,8 +18,8 @@ namespace Tgstation.Server.Host.System /// The to use. /// The to use. /// The for the operation. - /// A dotnet executable path to use. - public static async ValueTask GetDotnetPath(IPlatformIdentifier platformIdentifier, IIOManager ioManager, CancellationToken cancellationToken) + /// A resulting in a dotnet executable path to use on success, otherwise. + public static async ValueTask GetDotnetPath(IPlatformIdentifier platformIdentifier, IIOManager ioManager, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(platformIdentifier); ArgumentNullException.ThrowIfNull(ioManager); @@ -37,7 +35,7 @@ namespace Tgstation.Server.Host.System var selectedPathIndex = tasks.FindIndex(pathValidTask => pathValidTask.Result); if (selectedPathIndex == -1) - throw new JobException(ErrorCode.CantFindDotnet); + return null; var dotnetPath = dotnetPaths[selectedPathIndex]; diff --git a/src/Tgstation.Server.Host/System/IDotnetDumpService.cs b/src/Tgstation.Server.Host/System/IDotnetDumpService.cs index c8a1263e0b..f745e3c51a 100644 --- a/src/Tgstation.Server.Host/System/IDotnetDumpService.cs +++ b/src/Tgstation.Server.Host/System/IDotnetDumpService.cs @@ -8,21 +8,13 @@ namespace Tgstation.Server.Host.System /// public interface IDotnetDumpService { - /// - /// Attempt to install dotnet-dump if it is not installed. - /// - /// If this operation is part of the deployment pipeline. - /// The for the operation. - /// A representing the running operation. - ValueTask EnsureInstalled(bool deploymentPipeline, CancellationToken cancellationToken); - /// /// Attempt to dump a given . /// /// The to dump. /// The path to the output dump file. /// The for the operation. - /// if the dump proceeded, if dotnet-dump was not installed. - ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs index 695fc776c5..8077577aaf 100644 --- a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.System if (exitCode != 0) throw new JobException( - ErrorCode.DumpProcessFailure, + ErrorCode.GCoreFailure, new JobException( $"Exit Code: {exitCode}{Environment.NewLine}Output:{Environment.NewLine}{output}")); diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 300d9ffe1c..044027945f 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -83,6 +83,8 @@ + + diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index c963d90fa9..0870512703 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -575,7 +575,7 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(restartJob, 20, false, null, cancellationToken); } - Assert.IsTrue(job.ErrorCode == ErrorCode.GameServerOffline || job.ErrorCode == ErrorCode.DumpProcessFailure, $"{job.ErrorCode}: {job.ExceptionDetails}"); + Assert.IsTrue(job.ErrorCode == ErrorCode.GameServerOffline || job.ErrorCode == ErrorCode.GCoreFailure, $"{job.ErrorCode}: {job.ExceptionDetails}"); var restartJob2 = await instanceClient.DreamDaemon.Restart(cancellationToken); await WaitForJob(restartJob2, 20, false, null, cancellationToken); @@ -813,7 +813,21 @@ namespace Tgstation.Server.Tests.Live.Instance ourProcessHandler.SuspendProcess(); global::System.Console.WriteLine($"WATCHDOG TEST {instanceClient.Metadata.Id}: FINISH PROCESS SUSPEND FOR HEALTH CHECK DEATH. WAITING FOR LIFETIME {ourProcessHandler.Id}."); + if (testVersion.Engine == EngineType.OpenDream && checkDump) + { + // because dotnet diagnostics relies on the engine process to write its own dump, we actually have to unpause it after the watchdog has decided to kill it + // incredibly cursed, because we don't have the means to accurately tell when that will happen. ESP in CI + return; // CBA rn + /* + await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken); + ourProcessHandler.ResumeProcess(); + global::System.Console.WriteLine($"WATCHDOG TEST {instanceClient.Metadata.Id}: PROCESS RESUMING FOR DOTNET DUMP. WAITING FOR LIFETIME {ourProcessHandler.Id}.");*/ + } + await Task.WhenAny(ourProcessHandler.Lifetime, Task.Delay(TimeSpan.FromMinutes(4), cancellationToken)); + if (testVersion.Engine == EngineType.OpenDream && checkDump && !ourProcessHandler.Lifetime.IsCompleted) + return; + Assert.IsTrue(ourProcessHandler.Lifetime.IsCompleted); var timeout = 20; From 79e8e3b01c2d8edbf982988312cbf1eb4e203f92 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 14:25:54 -0500 Subject: [PATCH 035/137] Cleanup security clearance for PRs - Add the `CI Approval Required` label. - Add required `permissions`. - Pin action dependencies. - Make comment message generic. --- .github/workflows/ci-pipeline.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 49d1a5bc69..cb1fd2094b 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -54,20 +54,35 @@ jobs: security-checkpoint: name: Check CI Clearance runs-on: ubuntu-latest + permissions: + pull-requests: write if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id && github.event.pull_request.state == 'open' steps: - name: Comment on new Fork PR if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared') - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 with: - message: Thank you for contributing to tgstation-server! As this pull request is from a fork, we can't allow the CI actions which require repository secrets to run on it without approval. After a brief review to make sure you're not misusing those secrets, a maintainer will add the `CI Cleared` label to allow the CI suite to run. Maintainers, please note that any changes to workflow files will not be reflected in the CI run. + message: Thank you for contributing to ${{ github.event.pull_request.base.repo.name }}! The workflow '${{ github.workflow }}' requires repository secrets amd will not run without approval. Maintainers can add the `CI Cleared` label to allow the CI suite to run. Please note that any changes to the workflow file will not be reflected in the CI run. - name: "Remove Stale 'CI Cleared' Label" if: github.event.action == 'synchronize' || github.event.action == 'reopened' - uses: actions-ecosystem/action-remove-labels@v1 + uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 with: labels: CI Cleared + - name: "Add 'CI Approval Required' Label" + if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared')) + uses: actions-ecosystem/action-add-labels@bd52874380e3909a1ac983768df6976535ece7f8 + with: + labels: CI Approval Required + github_token: ${{ github.token }} + + - name: "Remove 'CI Approval Required' Label" + if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared')) + uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 + with: + labels: CI Approval Required + - name: Fail Clearance Check if PR has Unlabeled new Commits from Fork if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared')) run: exit 1 From 279f4512b297ffe5650a9d297c589efcf82fbf7f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 14:38:28 -0500 Subject: [PATCH 036/137] Update Octokit to v9.1.2 --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 044027945f..2ea5dcd4ed 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -1,4 +1,4 @@ - + @@ -102,7 +102,7 @@ - + From f415bab22be9059bea036e1132521616b4a2ea4b Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 14:39:25 -0500 Subject: [PATCH 037/137] Update to latest stylecop beta --- build/SrcCommon.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/SrcCommon.props b/build/SrcCommon.props index d6e835dad0..a98bf6bad8 100644 --- a/build/SrcCommon.props +++ b/build/SrcCommon.props @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 00c2ee715f8a4c3037b716a302b9b1c6aeae4f57 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 15:10:45 -0500 Subject: [PATCH 038/137] Update `dotnet-ef` version --- src/Tgstation.Server.Host/.config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/.config/dotnet-tools.json b/src/Tgstation.Server.Host/.config/dotnet-tools.json index c03564f970..81fe5add42 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.0", + "version": "8.0.1", "commands": [ "dotnet-ef" ] From 0617d1048452f4c9526475668452ad511dbea5be Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 15:25:33 -0500 Subject: [PATCH 039/137] Look specifically for `.net.dmp` files in OpenDream test --- tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 0870512703..3ad12c9743 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -1,4 +1,4 @@ -using Byond.TopicSender; +using Byond.TopicSender; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -526,7 +526,7 @@ namespace Tgstation.Server.Tests.Live.Instance await WaitForJob(dumpJob, 30, false, null, cancellationToken); var dumpFiles = Directory.GetFiles(Path.Combine( - instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), "*.dmp"); + instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), testVersion.Engine == EngineType.OpenDream ? "*.net.dmp" : "*.dmp"); Assert.AreEqual(1, dumpFiles.Length); File.Delete(dumpFiles.Single()); From 98801c4a901e71237b5803acfdce132b0a7d0f69 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 16:00:15 -0500 Subject: [PATCH 040/137] Add `Minidump` watchdog option to allow for smaller dumps Previously, on Windows, these were exclusively full dumps. On Linux, they were exclusively minidumps. Added API/DB Migrations change. Versions updated Closes #1741 --- build/Version.props | 6 +- .../Internal/DreamDaemonLaunchParameters.cs | 9 +- .../Rights/DreamDaemonRights.cs | 5 + .../Components/Session/SessionController.cs | 6 +- .../Components/Watchdog/WatchdogBase.cs | 2 +- .../Controllers/DreamDaemonController.cs | 7 +- .../Controllers/InstanceController.cs | 1 + .../Database/DatabaseContext.cs | 18 +- ...202202038_MSAddMinidumpsOption.Designer.cs | 1080 ++++++++++++++++ .../20240202202038_MSAddMinidumpsOption.cs | 37 + ...202202051_MYAddMinidumpsOption.Designer.cs | 1114 +++++++++++++++++ .../20240202202051_MYAddMinidumpsOption.cs | 37 + ...202202106_PGAddMinidumpsOption.Designer.cs | 1074 ++++++++++++++++ .../20240202202106_PGAddMinidumpsOption.cs | 37 + ...202202121_SLAddMinidumpsOption.Designer.cs | 1046 ++++++++++++++++ .../20240202202121_SLAddMinidumpsOption.cs | 37 + .../MySqlDatabaseContextModelSnapshot.cs | 6 +- ...PostgresSqlDatabaseContextModelSnapshot.cs | 6 +- .../SqlServerDatabaseContextModelSnapshot.cs | 6 +- .../SqliteDatabaseContextModelSnapshot.cs | 6 +- .../System/DotnetDumpService.cs | 10 +- .../System/IDotnetDumpService.cs | 3 +- .../System/IProcessBase.cs | 3 +- .../System/IProcessFeatures.cs | 3 +- .../System/PosixProcessFeatures.cs | 6 +- src/Tgstation.Server.Host/System/Process.cs | 4 +- .../System/WindowsProcessFeatures.cs | 16 +- .../Live/Instance/WatchdogTest.cs | 17 +- tests/Tgstation.Server.Tests/TestDatabase.cs | 1 + 29 files changed, 4562 insertions(+), 41 deletions(-) create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.cs diff --git a/build/Version.props b/build/Version.props index d244ecd7fa..ced1da5478 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,10 +5,10 @@ 6.1.5 5.1.0 - 10.0.0 + 10.1.0 7.0.0 - 13.0.1 - 15.0.1 + 13.1.0 + 15.1.0 7.0.2 5.8.0 1.4.1 diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index b21ce258ff..72b9ef88d6 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -98,6 +98,13 @@ namespace Tgstation.Server.Api.Models.Internal [ResponseOptions] public uint? MapThreads { get; set; } + /// + /// If minidumps should be taken instead of full dumps. + /// + [Required] + [ResponseOptions] + public bool? Minidumps { get; set; } + /// /// Check if we match a given set of . is excluded. /// @@ -116,7 +123,7 @@ namespace Tgstation.Server.Api.Models.Internal && AdditionalParameters == otherParameters.AdditionalParameters && StartProfiler == otherParameters.StartProfiler && LogOutput == otherParameters.LogOutput - && MapThreads == otherParameters.MapThreads; // We intentionally don't check StartupTimeout, health check seconds, or health check dump as they don't matter in terms of the watchdog + && MapThreads == otherParameters.MapThreads; // We intentionally don't check StartupTimeout, Minidumps, health check seconds, or health check dump as they don't matter in terms of the watchdog } } } diff --git a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs index 9af0522856..279c49c81b 100644 --- a/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamDaemonRights.cs @@ -117,5 +117,10 @@ namespace Tgstation.Server.Api.Rights /// User can use . /// BroadcastMessage = 1 << 20, + + /// + /// User can use . + /// + SetMinidumps = 1 << 21, } } diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 44d96340b0..110506c6e2 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -487,12 +487,12 @@ namespace Tgstation.Server.Host.Components.Session cancellationToken); /// - public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) + public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken) { if (engineLock.UseDotnetDump) - return dotnetDumpService.Dump(process, outputFile, cancellationToken); + return dotnetDumpService.Dump(process, outputFile, minidump, cancellationToken); - return process.CreateDump(outputFile, cancellationToken); + return process.CreateDump(outputFile, minidump, cancellationToken); } /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 43454924ce..8e891f10c3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -1249,7 +1249,7 @@ namespace Tgstation.Server.Host.Components.Watchdog throw new JobException(ErrorCode.GameServerOffline); Logger.LogInformation("Dumping session to {dumpFileName}...", dumpFileName); - await session.CreateDump(dumpFileName, cancellationToken); + await session.CreateDump(dumpFileName, ActiveLaunchParameters.Minidumps!.Value, cancellationToken); } } } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index cce77f0dc3..17006bfa2e 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -149,7 +149,8 @@ namespace Tgstation.Server.Host.Controllers | DreamDaemonRights.SetProfiler | DreamDaemonRights.SetLogOutput | DreamDaemonRights.SetMapThreads - | DreamDaemonRights.BroadcastMessage)] + | DreamDaemonRights.BroadcastMessage + | DreamDaemonRights.SetMinidumps)] [ProducesResponseType(typeof(DreamDaemonResponse), 200)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] #pragma warning disable CA1502 // TODO: Decomplexify @@ -222,7 +223,8 @@ namespace Tgstation.Server.Host.Controllers || CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters) || CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler) || CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput) - || CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads)) + || CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads) + || CheckModified(x => x.Minidumps, DreamDaemonRights.SetMinidumps)) return Forbid(); return await WithComponentInstance( @@ -379,6 +381,7 @@ namespace Tgstation.Server.Host.Controllers result.StartProfiler = settings.StartProfiler; result.LogOutput = settings.LogOutput; result.MapThreads = settings.MapThreads; + result.Minidumps = settings.Minidumps; } if (revision) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index ded9313475..cbc0f802dd 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -732,6 +732,7 @@ namespace Tgstation.Server.Host.Controllers StartProfiler = false, LogOutput = false, MapThreads = 0, + Minidumps = true, }, DreamMakerSettings = new DreamMakerSettings { diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 978a73076b..7dd49e7f2e 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -375,22 +375,22 @@ namespace Tgstation.Server.Host.Database /// /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. /// - internal static readonly Type MSLatestMigration = typeof(MSAddTopicPort); + internal static readonly Type MSLatestMigration = typeof(MSAddMinidumpsOption); /// /// Used by unit tests to remind us to setup the correct MYSQL migration downgrades. /// - internal static readonly Type MYLatestMigration = typeof(MYAddTopicPort); + internal static readonly Type MYLatestMigration = typeof(MYAddMinidumpsOption); /// /// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades. /// - internal static readonly Type PGLatestMigration = typeof(PGAddTopicPort); + internal static readonly Type PGLatestMigration = typeof(PGAddMinidumpsOption); /// /// Used by unit tests to remind us to setup the correct SQLite migration downgrades. /// - internal static readonly Type SLLatestMigration = typeof(SLAddTopicPort); + internal static readonly Type SLLatestMigration = typeof(SLAddMinidumpsOption); /// #pragma warning disable CA1502 // Cyclomatic complexity @@ -419,6 +419,16 @@ namespace Tgstation.Server.Host.Database string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); + if (targetVersion < new Version(6, 2, 0)) + targetMigration = currentDatabaseType switch + { + DatabaseType.MySql => nameof(MYAddTopicPort), + DatabaseType.PostgresSql => nameof(PGAddTopicPort), + DatabaseType.SqlServer => nameof(MSAddTopicPort), + DatabaseType.Sqlite => nameof(SLAddTopicPort), + _ => BadDatabaseType(), + }; + if (targetVersion < new Version(6, 0, 0)) targetMigration = currentDatabaseType switch { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.Designer.cs new file mode 100644 index 0000000000..dec415ded3 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.Designer.cs @@ -0,0 +1,1080 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20240202202038_MSAddMinidumpsOption")] + partial class MSAddMinidumpsOption + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .HasColumnType("int"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uniqueidentifier"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RepositoryOrigin") + .HasColumnType("nvarchar(max)"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("HealthCheckSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("bit"); + + b.Property("MapThreads") + .HasColumnType("bigint"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("bit"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .HasColumnType("int"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("int"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Online") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("SwarmIdentifer") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique() + .HasFilter("[SwarmIdentifer] IS NOT NULL"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("decimal(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("EngineRights") + .HasColumnType("decimal(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("decimal(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("decimal(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("decimal(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("decimal(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique() + .HasFilter("[GroupId] IS NOT NULL"); + + b.HasIndex("UserId") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("bit"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("bit"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("bit"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("MergedAt") + .HasColumnType("datetimeoffset"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique() + .HasFilter("[SystemIdentifier] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.cs new file mode 100644 index 0000000000..3439dbe5cb --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202038_MSAddMinidumpsOption.cs @@ -0,0 +1,37 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +using Tgstation.Server.Host.System; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MSAddMinidumpsOption : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + // This was originally minidumps on Linux and full dumps on Windows + var defaultValue = !new PlatformIdentifier().IsWindows; + migrationBuilder.AddColumn( + name: "Minidumps", + table: "DreamDaemonSettings", + type: "bit", + nullable: false, + defaultValue: defaultValue); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "Minidumps", + table: "DreamDaemonSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.Designer.cs new file mode 100644 index 0000000000..b579255da3 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.Designer.cs @@ -0,0 +1,1114 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20240202202051_MYAddMinidumpsOption")] + partial class MYAddMinidumpsOption + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ConnectionString"), "utf8mb4"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("IrcChannel"), "utf8mb4"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Tag"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("DmeName"), "utf8mb4"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("EngineVersion"), "utf8mb4"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Output"), "utf8mb4"); + + b.Property("RepositoryOrigin") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("RepositoryOrigin"), "utf8mb4"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AdditionalParameters"), "utf8mb4"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Port") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ProjectName"), "utf8mb4"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("Online") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Path"), "utf8mb4"); + + b.Property("SwarmIdentifer") + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SwarmIdentifer"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChatBotRights") + .HasColumnType("bigint unsigned"); + + b.Property("ConfigurationRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamDaemonRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamMakerRights") + .HasColumnType("bigint unsigned"); + + b.Property("EngineRights") + .HasColumnType("bigint unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("bigint unsigned"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("bigint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CancelRight") + .HasColumnType("bigint unsigned"); + + b.Property("CancelRightsType") + .HasColumnType("bigint unsigned"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Description"), "utf8mb4"); + + b.Property("ErrorCode") + .HasColumnType("int unsigned"); + + b.Property("ExceptionDetails") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExceptionDetails"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint unsigned"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExternalUserId"), "utf8mb4"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AdministrationRights") + .HasColumnType("bigint unsigned"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("bigint unsigned"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessIdentifier"), "utf8mb4"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("smallint unsigned"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("smallint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessToken"), "utf8mb4"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessUser"), "utf8mb4"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterEmail"), "utf8mb4"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterName"), "utf8mb4"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitSha"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("OriginCommitSha"), "utf8mb4"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Author"), "utf8mb4"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("BodyAtMerge"), "utf8mb4"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Comment"), "utf8mb4"); + + b.Property("MergedAt") + .HasColumnType("datetime(6)"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TargetCommitSha"), "utf8mb4"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TitleAtMerge"), "utf8mb4"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Url"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CanonicalName"), "utf8mb4"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("PasswordHash"), "utf8mb4"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SystemIdentifier"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.cs new file mode 100644 index 0000000000..05693e58f2 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202051_MYAddMinidumpsOption.cs @@ -0,0 +1,37 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +using Tgstation.Server.Host.System; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MYAddMinidumpsOption : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + // This was originally minidumps on Linux and full dumps on Windows + var defaultValue = !new PlatformIdentifier().IsWindows; + migrationBuilder.AddColumn( + name: "Minidumps", + table: "DreamDaemonSettings", + type: "tinyint(1)", + nullable: false, + defaultValue: defaultValue); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "Minidumps", + table: "DreamDaemonSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.Designer.cs new file mode 100644 index 0000000000..5a19f5dedf --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.Designer.cs @@ -0,0 +1,1074 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(PostgresSqlDatabaseContext))] + [Migration("20240202202106_PGAddMinidumpsOption")] + partial class PGAddMinidumpsOption + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .HasColumnType("integer"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("integer"); + + b.Property("DMApiMinorVersion") + .HasColumnType("integer"); + + b.Property("DMApiPatchVersion") + .HasColumnType("integer"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("GitHubDeploymentId") + .HasColumnType("integer"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("integer"); + + b.Property("Output") + .IsRequired() + .HasColumnType("text"); + + b.Property("RepositoryOrigin") + .HasColumnType("text"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("HealthCheckSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("MapThreads") + .HasColumnType("bigint"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("SecurityLevel") + .HasColumnType("integer"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .HasColumnType("integer"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("integer"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("interval"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("integer"); + + b.Property("ConfigurationType") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Online") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SwarmIdentifer") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("numeric(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("EngineRights") + .HasColumnType("numeric(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("numeric(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("numeric(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("numeric(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("text"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("smallint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("integer"); + + b.Property("LaunchVisibility") + .HasColumnType("integer"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ProcessId") + .HasColumnType("integer"); + + b.Property("RebootState") + .HasColumnType("integer"); + + b.Property("TopicPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("MergedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.cs new file mode 100644 index 0000000000..4f73ab4080 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202106_PGAddMinidumpsOption.cs @@ -0,0 +1,37 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +using Tgstation.Server.Host.System; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class PGAddMinidumpsOption : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + // This was originally minidumps on Linux and full dumps on Windows + var defaultValue = !new PlatformIdentifier().IsWindows; + migrationBuilder.AddColumn( + name: "Minidumps", + table: "DreamDaemonSettings", + type: "boolean", + nullable: false, + defaultValue: defaultValue); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "Minidumps", + table: "DreamDaemonSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.Designer.cs new file mode 100644 index 0000000000..db4c7542e2 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.Designer.cs @@ -0,0 +1,1046 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20240202202121_SLAddMinidumpsOption")] + partial class SLAddMinidumpsOption + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.1"); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatSettingsId") + .HasColumnType("INTEGER"); + + b.Property("DiscordChannelId") + .HasColumnType("INTEGER"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DMApiMajorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiMinorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiPatchVersion") + .HasColumnType("INTEGER"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GitHubDeploymentId") + .HasColumnType("INTEGER"); + + b.Property("GitHubRepoId") + .HasColumnType("INTEGER"); + + b.Property("JobId") + .HasColumnType("INTEGER"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("Output") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RepositoryOrigin") + .HasColumnType("TEXT"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Port") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("SecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Visibility") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConfigurationType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Online") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SwarmIdentifer") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatBotRights") + .HasColumnType("INTEGER"); + + b.Property("ConfigurationRights") + .HasColumnType("INTEGER"); + + b.Property("DreamDaemonRights") + .HasColumnType("INTEGER"); + + b.Property("DreamMakerRights") + .HasColumnType("INTEGER"); + + b.Property("EngineRights") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("INTEGER"); + + b.Property("PermissionSetId") + .HasColumnType("INTEGER"); + + b.Property("RepositoryRights") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CancelRight") + .HasColumnType("INTEGER"); + + b.Property("CancelRightsType") + .HasColumnType("INTEGER"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CancelledById") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ErrorCode") + .HasColumnType("INTEGER"); + + b.Property("ExceptionDetails") + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("JobCode") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedById") + .HasColumnType("INTEGER"); + + b.Property("StoppedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdministrationRights") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("InstanceManagerRights") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CompileJobId") + .HasColumnType("INTEGER"); + + b.Property("InitialCompileJobId") + .HasColumnType("INTEGER"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("LaunchVisibility") + .HasColumnType("INTEGER"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ProcessId") + .HasColumnType("INTEGER"); + + b.Property("RebootState") + .HasColumnType("INTEGER"); + + b.Property("TopicPort") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.Property("TestMergeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("MergedAt") + .HasColumnType("TEXT"); + + b.Property("MergedById") + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordUpdate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.cs b/src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.cs new file mode 100644 index 0000000000..7a4af46540 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240202202121_SLAddMinidumpsOption.cs @@ -0,0 +1,37 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +using Tgstation.Server.Host.System; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class SLAddMinidumpsOption : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + // This was originally minidumps on Linux and full dumps on Windows + var defaultValue = !new PlatformIdentifier().IsWindows; + migrationBuilder.AddColumn( + name: "Minidumps", + table: "DreamDaemonSettings", + type: "INTEGER", + nullable: false, + defaultValue: defaultValue); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "Minidumps", + table: "DreamDaemonSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index 1f42bd3856..e0449a4ac3 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("ProductVersion", "8.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => @@ -219,6 +219,10 @@ namespace Tgstation.Server.Host.Database.Migrations .IsRequired() .HasColumnType("int unsigned"); + b.Property("Minidumps") + .IsRequired() + .HasColumnType("tinyint(1)"); + b.Property("Port") .IsRequired() .HasColumnType("smallint unsigned"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs index 631cf53bc9..b27d3f8e50 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("ProductVersion", "8.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -209,6 +209,10 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("MapThreads") .HasColumnType("bigint"); + b.Property("Minidumps") + .IsRequired() + .HasColumnType("boolean"); + b.Property("Port") .HasColumnType("integer"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs index 1b1bb5276c..6477433e00 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("ProductVersion", "8.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -211,6 +211,10 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("MapThreads") .HasColumnType("bigint"); + b.Property("Minidumps") + .IsRequired() + .HasColumnType("bit"); + b.Property("Port") .HasColumnType("int"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs index 29d7469c65..ce2169101a 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.1"); modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => { @@ -201,6 +201,10 @@ namespace Tgstation.Server.Host.Database.Migrations .IsRequired() .HasColumnType("INTEGER"); + b.Property("Minidumps") + .IsRequired() + .HasColumnType("INTEGER"); + b.Property("Port") .IsRequired() .HasColumnType("INTEGER"); diff --git a/src/Tgstation.Server.Host/System/DotnetDumpService.cs b/src/Tgstation.Server.Host/System/DotnetDumpService.cs index f7ff800535..f813b44230 100644 --- a/src/Tgstation.Server.Host/System/DotnetDumpService.cs +++ b/src/Tgstation.Server.Host/System/DotnetDumpService.cs @@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.System } /// - public async ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken) + public async ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken) { // need to use an extra timeout here because if the process is truly deadlocked. A cooperative dump will hang forever using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); @@ -42,7 +42,13 @@ 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(DumpType.Full, outputFile, false, cts.Token); + await client.WriteDumpAsync( + minidump + ? DumpType.Normal + : DumpType.Full, + outputFile, + false, + cts.Token); } } } diff --git a/src/Tgstation.Server.Host/System/IDotnetDumpService.cs b/src/Tgstation.Server.Host/System/IDotnetDumpService.cs index f745e3c51a..43aea37806 100644 --- a/src/Tgstation.Server.Host/System/IDotnetDumpService.cs +++ b/src/Tgstation.Server.Host/System/IDotnetDumpService.cs @@ -13,8 +13,9 @@ namespace Tgstation.Server.Host.System /// /// The to dump. /// The path to the output dump file. + /// If a minidump should be taken as opposed to a full dump. /// The for the operation. /// A representing the running operation. - ValueTask Dump(IProcess process, string outputFile, CancellationToken cancellationToken); + ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index d7a20f43b7..92f6bdb159 100644 --- a/src/Tgstation.Server.Host/System/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -33,8 +33,9 @@ namespace Tgstation.Server.Host.System /// Create a dump file of the process. /// /// The full path to the output file. + /// If a minidump should be taken as opposed to a full dump. /// The for the operation. /// A representing the running operation. - ValueTask CreateDump(string outputFile, CancellationToken cancellationToken); + ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/System/IProcessFeatures.cs b/src/Tgstation.Server.Host/System/IProcessFeatures.cs index abfaca6b7b..927afd7e63 100644 --- a/src/Tgstation.Server.Host/System/IProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/IProcessFeatures.cs @@ -32,8 +32,9 @@ namespace Tgstation.Server.Host.System /// /// The to dump. /// The full path to the output file. + /// If a minidump should be taken as opposed to a full dump. /// The for the operation. /// A representing the running operation. - ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken); + ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs index 8077577aaf..7fd2ce8559 100644 --- a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.System => throw new NotSupportedException(); /// - public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken) + public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(process); ArgumentNullException.ThrowIfNull(outputFile); @@ -91,7 +91,7 @@ namespace Tgstation.Server.Host.System await using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess( GCorePath, Environment.CurrentDirectory, - $"-o {outputFile} {process.Id}", + $"{(!minidump ? "-a " : String.Empty)}-o {outputFile} {process.Id}", readStandardHandles: true, noShellExecute: true)) { @@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.System exitCode = (await gcoreProc.Lifetime).Value; output = await gcoreProc.GetCombinedOutput(cancellationToken); - logger.LogDebug("gcore output:{0}{1}", Environment.NewLine, output); + logger.LogDebug("gcore output:{newline}{output}", Environment.NewLine, output); } if (exitCode != 0) diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index 4362083247..896195944c 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -224,13 +224,13 @@ namespace Tgstation.Server.Host.System } /// - public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) + public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(outputFile); CheckDisposed(); logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile); - return processFeatures.CreateDump(handle, outputFile, cancellationToken); + return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken); } /// diff --git a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs index 41789aec8c..e842bd9db2 100644 --- a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs @@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.System } /// - public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken) + public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken) { try { @@ -137,15 +137,19 @@ namespace Tgstation.Server.Host.System await Task.Factory.StartNew( () => { + var flags = NativeMethods.MiniDumpType.WithHandleData + | NativeMethods.MiniDumpType.WithThreadInfo + | NativeMethods.MiniDumpType.WithUnloadedModules; + + if (!minidump) + flags |= NativeMethods.MiniDumpType.WithDataSegs + | NativeMethods.MiniDumpType.WithFullMemory; + if (!NativeMethods.MiniDumpWriteDump( process.Handle, (uint)process.Id, fileStream.SafeFileHandle, - NativeMethods.MiniDumpType.WithDataSegs - | NativeMethods.MiniDumpType.WithFullMemory - | NativeMethods.MiniDumpType.WithHandleData - | NativeMethods.MiniDumpType.WithThreadInfo - | NativeMethods.MiniDumpType.WithUnloadedModules, + flags, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 3ad12c9743..fd927a684a 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -1,4 +1,4 @@ -using Byond.TopicSender; +using Byond.TopicSender; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -351,13 +351,11 @@ namespace Tgstation.Server.Tests.Live.Instance var deleteJob = await deleteJobTask; - // And this freezes DD - await DumpTests(cancellationToken); + // And this freezes DD (also restarts it) + await DumpTests(false, cancellationToken); + await DumpTests(true, cancellationToken); - // Restart to unlock previous BYOND version - var restartJob = await instanceClient.DreamDaemon.Restart(cancellationToken); await WaitForJob(deleteJob, 15, false, null, cancellationToken); - await WaitForJob(restartJob, 15, false, null, cancellationToken); } async ValueTask RegressionTest1550(CancellationToken cancellationToken) @@ -519,9 +517,14 @@ namespace Tgstation.Server.Tests.Live.Instance Assert.AreEqual("sent", topicRequestResult.StringData); } - async Task DumpTests(CancellationToken cancellationToken) + async Task DumpTests(bool mini, CancellationToken cancellationToken) { System.Console.WriteLine("TEST: WATCHDOG DUMP TESTS"); + var updated = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest + { + Minidumps = mini, + }, cancellationToken); + Assert.AreEqual(mini, updated.Minidumps); var dumpJob = await instanceClient.DreamDaemon.CreateDump(cancellationToken); await WaitForJob(dumpJob, 30, false, null, cancellationToken); diff --git a/tests/Tgstation.Server.Tests/TestDatabase.cs b/tests/Tgstation.Server.Tests/TestDatabase.cs index 320c4e32b8..c5b4b13f50 100644 --- a/tests/Tgstation.Server.Tests/TestDatabase.cs +++ b/tests/Tgstation.Server.Tests/TestDatabase.cs @@ -122,6 +122,7 @@ namespace Tgstation.Server.Tests StartProfiler = false, LogOutput = true, MapThreads = 69, + Minidumps = true, }, DreamMakerSettings = new Host.Models.DreamMakerSettings { From 76d6395683b6df20ac58ef64dcd1473a58d9c302 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 2 Feb 2024 14:35:31 -0500 Subject: [PATCH 041/137] Cleanups to PR opening message --- .github/workflows/ci-pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index cb1fd2094b..a9c44ba687 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -62,7 +62,7 @@ jobs: if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared') uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 with: - message: Thank you for contributing to ${{ github.event.pull_request.base.repo.name }}! The workflow '${{ github.workflow }}' requires repository secrets amd will not run without approval. Maintainers can add the `CI Cleared` label to allow the CI suite to run. Please note that any changes to the workflow file will not be reflected in the CI run. + message: Thank you for contributing to ${{ github.event.pull_request.base.repo.name }}! The workflow '${{ github.workflow }}' requires repository secrets and will not run without approval. Maintainers can add the `CI Cleared` label to allow it to run. Please note that any changes to the workflow file will not be reflected in the run. - name: "Remove Stale 'CI Cleared' Label" if: github.event.action == 'synchronize' || github.event.action == 'reopened' From 313f3efa4fd777d900cc0cff6c7f3b9640a7e514 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 3 Feb 2024 12:05:20 -0500 Subject: [PATCH 042/137] Bump webpanel to v5.5.0 --- build/WebpanelVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/WebpanelVersion.props b/build/WebpanelVersion.props index 5b674b1734..abaf23b16d 100644 --- a/build/WebpanelVersion.props +++ b/build/WebpanelVersion.props @@ -1,6 +1,6 @@ - 5.4.2 + 5.5.0 From 8080475d9024baf147af2183369a6c1ada69420e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 3 Feb 2024 12:05:38 -0500 Subject: [PATCH 043/137] Version bump to v6.2.0 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index ced1da5478..b0347f6394 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.1.5 + 6.2.0 5.1.0 10.1.0 7.0.0 From 6f06f1e45cd95a51399a850b3bc5da74ba78a29b Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 15 Feb 2024 17:24:36 -0500 Subject: [PATCH 044/137] One less async function --- .../Components/Deployment/DmbFactory.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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) From 4500719a1cee44d7f42a16f9213a4ca182713571 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 15 Feb 2024 17:31:23 -0500 Subject: [PATCH 045/137] DMAPI 7.1.0: Add `TGS_FILE2TEXT_NATIVE` --- build/Version.props | 2 +- src/DMAPI/tgs.dm | 9 ++++++++- src/DMAPI/tgs/v5/bridge.dm | 2 +- tests/DMAPI/LongRunning/Test.dm | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/build/Version.props b/build/Version.props index b0347f6394..640f7a72fa 100644 --- a/build/Version.props +++ b/build/Version.props @@ -9,7 +9,7 @@ 7.0.0 13.1.0 15.1.0 - 7.0.2 + 7.1.0 5.8.0 1.4.1 1.2.1 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index fdfec5e8ca..1d7f7d02f8 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. diff --git a/src/DMAPI/tgs/v5/bridge.dm b/src/DMAPI/tgs/v5/bridge.dm index a0ab359876..d986ec7e73 100644 --- a/src/DMAPI/tgs/v5/bridge.dm +++ b/src/DMAPI/tgs/v5/bridge.dm @@ -88,7 +88,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/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm index 90164c7e80..f4468953a7 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!") From 07c7bd573c1dd3596ca77ef699b7a94baa14ec81 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 15 Feb 2024 18:44:20 -0500 Subject: [PATCH 046/137] Nuget package updates. Primarily patch Tuesday --- build/TestCommon.props | 6 ++--- .../Tgstation.Server.Api.csproj | 2 +- .../Tgstation.Server.Client.csproj | 4 ++-- .../.config/dotnet-tools.json | 2 +- .../Tgstation.Server.Host.csproj | 22 +++++++++---------- .../Tgstation.Server.Host.Tests.csproj | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) 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/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/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 2ea5dcd4ed..a944063eaa 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -78,23 +78,23 @@ - + - + - + - + - + - + runtime; build; native; contentfiles; analyzers; buildtransitive - + - + @@ -104,9 +104,9 @@ - + - + @@ -128,7 +128,7 @@ - + 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 @@ - + From b2b7afe5f2872ce75d74b6d9063f6471361c1a20 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 15 Feb 2024 18:45:12 -0500 Subject: [PATCH 047/137] Update dotnet redistributable --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 640f7a72fa..5f87b4c3ba 100644 --- a/build/Version.props +++ b/build/Version.props @@ -17,7 +17,7 @@ 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 From c17ecb84261910d57635ae0cc7a23fa60f4707e3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 16:00:14 -0500 Subject: [PATCH 048/137] Fix event scripts always running with low priority if `Session:LowPriorityDeploymentProcesses` was set --- .../Components/StaticFiles/Configuration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 8261afe918..c77fa7ab49 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -651,7 +651,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles noShellExecute: true)) using (cancellationToken.Register(() => script.Terminate())) { - if (sessionConfiguration.LowPriorityDeploymentProcesses) + if (sessionConfiguration.LowPriorityDeploymentProcesses && deploymentPipeline) script.AdjustPriority(false); var exitCode = await script.Lifetime; From 0bbf7cd46a1b0c483a1a0348bd32365c695b53c1 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 16:44:54 -0500 Subject: [PATCH 049/137] Add DMAPI triggerable custom events Closes #1746 --- build/Version.props | 2 +- src/DMAPI/tgs.dm | 10 ++ src/DMAPI/tgs/core/core.dm | 8 + src/DMAPI/tgs/core/datum.dm | 3 + src/DMAPI/tgs/v5/__interop_version.dm | 2 +- src/DMAPI/tgs/v5/_defines.dm | 9 + src/DMAPI/tgs/v5/api.dm | 37 ++++ src/DMAPI/tgs/v5/topic.dm | 12 ++ src/DMAPI/tgs/v5/undefs.dm | 9 + .../Components/Deployment/IDmbProvider.cs | 2 +- .../Components/Events/EventConsumer.cs | 4 + .../Components/Events/EventScriptAttribute.cs | 3 +- .../Components/Events/IEventConsumer.cs | 9 + .../Components/Events/NoopEventConsumer.cs | 4 + .../Interop/Bridge/BridgeCommandType.cs | 5 + .../Interop/Bridge/BridgeParameters.cs | 5 + .../Interop/Bridge/BridgeResponse.cs | 5 + .../Interop/Bridge/CustomEventInvocation.cs | 25 +++ .../Interop/Topic/EventNotification.cs | 4 +- .../Interop/Topic/TopicCommandType.cs | 5 + .../Interop/Topic/TopicParameters.cs | 16 ++ .../Components/Session/SessionController.cs | 107 +++++++++++- .../Session/SessionControllerFactory.cs | 2 + .../Components/StaticFiles/Configuration.cs | 158 +++++++++++------- .../Components/Watchdog/WatchdogBase.cs | 4 + tests/DMAPI/BasicOperation/Test.dm | 15 ++ .../DMAPI/BasicOperation/test_event-qwer.bat | 7 + tests/DMAPI/BasicOperation/test_event-qwer.sh | 13 ++ .../Live/Instance/ConfigurationTest.cs | 9 +- .../Live/Instance/WatchdogTest.cs | 2 +- tgstation-server.sln | 2 + 31 files changed, 420 insertions(+), 78 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Interop/Bridge/CustomEventInvocation.cs create mode 100644 tests/DMAPI/BasicOperation/test_event-qwer.bat create mode 100755 tests/DMAPI/BasicOperation/test_event-qwer.sh diff --git a/build/Version.props b/build/Version.props index 5f87b4c3ba..d5b05ce320 100644 --- a/build/Version.props +++ b/build/Version.props @@ -10,7 +10,7 @@ 13.1.0 15.1.0 7.1.0 - 5.8.0 + 5.9.0 1.4.1 1.2.1 2.0.0 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 1d7f7d02f8..dc49d2c6f0 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -496,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..fefca3af2f 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -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/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..32d09544ea 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() @@ -249,6 +251,41 @@ 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]") + pending_events[event_id] = TRUE + + do + sleep(1) + while(pending_events[event_id]) + + TGS_DEBUG_LOG("Completed wait on event ID: [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/topic.dm b/src/DMAPI/tgs/v5/topic.dm index 05e6c4e1b2..b13f83f82c 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 + 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.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/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 c77fa7ab49..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 && 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); - } - } + 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/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm index 022088fc67..aa0455b659 100644 --- a/tests/DMAPI/BasicOperation/Test.dm +++ b/tests/DMAPI/BasicOperation/Test.dm @@ -19,6 +19,21 @@ if(!("test" in world_params) || world_params["test"] != "bababooey") FailTest("Expected parameter test=bababooey but did not receive", "test_fail_reason.txt") + 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") + + fdel("test_event_output.txt") + world.TgsTriggerEvent("test_event", list("asdf"), FALSE) + if(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..ecbce0d0af --- /dev/null +++ b/tests/DMAPI/BasicOperation/test_event-qwer.bat @@ -0,0 +1,7 @@ +echo "Running test_event script" + +rem mingw has their own /usr/bin/timeout +C:\Windows\system32\timeout.exe /t 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/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}" From ae3fa1785ecedfa2b5ab33843567b407936046f5 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 16:47:12 -0500 Subject: [PATCH 050/137] Change all `sleep(1)`s into `sleep(world.tick_lag)`s --- src/DMAPI/tgs/core/datum.dm | 4 ++-- src/DMAPI/tgs/v4/api.dm | 6 +++--- src/DMAPI/tgs/v5/api.dm | 4 ++-- src/DMAPI/tgs/v5/bridge.dm | 2 +- tests/DMAPI/LongRunning/Test.dm | 4 ++-- tests/DMAPI/test_setup.dm | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index fefca3af2f..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,6 +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/api.dm b/src/DMAPI/tgs/v5/api.dm index 32d09544ea..ffdde1ae20 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -127,7 +127,7 @@ TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep") logged = TRUE - sleep(1) + sleep(world.tick_lag) TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed") @@ -279,7 +279,7 @@ pending_events[event_id] = TRUE do - sleep(1) + sleep(world.tick_lag) while(pending_events[event_id]) TGS_DEBUG_LOG("Completed wait on event ID: [event_id]") diff --git a/src/DMAPI/tgs/v5/bridge.dm b/src/DMAPI/tgs/v5/bridge.dm index d986ec7e73..763ab3e02b 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 diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm index f4468953a7..2735c8e0ab 100644 --- a/tests/DMAPI/LongRunning/Test.dm +++ b/tests/DMAPI/LongRunning/Test.dm @@ -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 From 36dff446469dfd69e9ed7e51b71eab0d4e48b9e3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 16:49:53 -0500 Subject: [PATCH 051/137] Update Postgres library --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index a944063eaa..7ef4d1c18c 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -100,7 +100,7 @@ - + From 5e0eb1aaf6a7bd734b196ccf6696a28f20d2d140 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 17:17:32 -0500 Subject: [PATCH 052/137] Address winget manifest 1.6 update --- build/package/winget/manifest/Tgstation.Server.installer.yaml | 2 +- .../winget/manifest/Tgstation.Server.locale.en-US.yaml | 2 +- build/package/winget/manifest/Tgstation.Server.yaml | 2 +- tools/Tgstation.Server.ReleaseNotes/Program.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) 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/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$)"; From e9463c192f46bb16fb749ed46a1c046edb343698 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 17:19:08 -0500 Subject: [PATCH 053/137] Update to Wix 4.0.4 --- build/package/winget/.config/dotnet-tools.json | 2 +- .../Tgstation.Server.Host.Service.Wix.Bundle.wixproj | 6 +++--- .../Tgstation.Server.Host.Service.Wix.Extensions.csproj | 2 +- .../Tgstation.Server.Host.Service.Wix.wixproj | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) 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 @@ - - + + From f13502f2acc2aee1458b693af15cc9890b934a70 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 17:28:49 -0500 Subject: [PATCH 054/137] Note about where to store instances on Linux --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9747be0b59c5311108adc0c32ea133eba0d6e035 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 17:56:04 -0500 Subject: [PATCH 055/137] Workaround for Debian stupidity See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=679746 --- build/package/deb/debian/postinst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9243f70203acfdc410ab32f741cd290dd1ae5c21 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 18:27:46 -0500 Subject: [PATCH 056/137] `trusted.txt` is only a Windows thing --- .../Components/Engine/ByondInstallerBase.cs | 88 ++++--------------- .../Components/Engine/PosixByondInstaller.cs | 10 +++ .../Engine/WindowsByondInstaller.cs | 66 ++++++++++++++ 3 files changed, 93 insertions(+), 71 deletions(-) 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..0723c96f5b 100644 --- a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs @@ -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. /// From e59e2ff337ae5ea903be0483c2d1be3738147db2 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 18:29:47 -0500 Subject: [PATCH 057/137] Fix path to BYOND $HOME folder on Linux --- .../Components/Engine/PosixByondInstaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs index 0723c96f5b..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")); } /// From ba41f41defa88acf9bec137ec98cd2e4282c7b45 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 18:32:08 -0500 Subject: [PATCH 058/137] Bump to latest webpanel version --- build/WebpanelVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 8bfccae678994613df1a0fa06288517a5b84c4ba Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Feb 2024 18:54:26 -0500 Subject: [PATCH 059/137] Version bump to 6.3.0 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index d5b05ce320..2f96d66f55 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.2.0 + 6.3.0 5.1.0 10.1.0 7.0.0 From 054c72cc1d1290e55a146c4ff85f09834efcc934 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 20 Feb 2024 08:43:41 -0500 Subject: [PATCH 060/137] Some debug logging --- src/DMAPI/tgs/v5/bridge.dm | 3 +++ tests/DMAPI/BasicOperation/Test.dm | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/DMAPI/tgs/v5/bridge.dm b/src/DMAPI/tgs/v5/bridge.dm index 763ab3e02b..0c5e701a32 100644 --- a/src/DMAPI/tgs/v5/bridge.dm +++ b/src/DMAPI/tgs/v5/bridge.dm @@ -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 diff --git a/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm index aa0455b659..e0cc3d2ba4 100644 --- a/tests/DMAPI/BasicOperation/Test.dm +++ b/tests/DMAPI/BasicOperation/Test.dm @@ -15,10 +15,12 @@ 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) @@ -29,6 +31,7 @@ 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") world.TgsTriggerEvent("test_event", list("asdf"), FALSE) if(fexists("test_event_output.txt")) From a43216210f1c7ecec78d25f062f52bfa6ffc02c9 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 20 Feb 2024 18:25:30 -0500 Subject: [PATCH 061/137] Fix race condition with DMAPI custom event creation/completion --- src/DMAPI/tgs/v5/api.dm | 5 ++--- src/DMAPI/tgs/v5/topic.dm | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm index ffdde1ae20..9b64931f8f 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -276,13 +276,12 @@ return TRUE TGS_DEBUG_LOG("Waiting for completion of event ID: [event_id]") - pending_events[event_id] = TRUE - do + while(!pending_events[event_id]) sleep(world.tick_lag) - while(pending_events[event_id]) TGS_DEBUG_LOG("Completed wait on event ID: [event_id]") + pending_events -= event_id return TRUE diff --git a/src/DMAPI/tgs/v5/topic.dm b/src/DMAPI/tgs/v5/topic.dm index b13f83f82c..e66edc2720 100644 --- a/src/DMAPI/tgs/v5/topic.dm +++ b/src/DMAPI/tgs/v5/topic.dm @@ -285,7 +285,7 @@ return TopicResponse("Invalid or missing [DMAPI5_EVENT_ID]") TGS_DEBUG_LOG("Completing event ID [event_id]...") - pending_events -= event_id + pending_events[event_id] = TRUE return TopicResponse() return TopicResponse("Unknown command: [command]") From 9d381df6323a04a0d83cf61db600667f9278715a Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 21 Feb 2024 08:38:28 -0500 Subject: [PATCH 062/137] Fix errors with dotnet dump. Add new error code --- build/Version.props | 6 ++--- src/Tgstation.Server.Api/Models/ErrorCode.cs | 6 +++++ .../System/DotnetDumpService.cs | 24 +++++++++++++------ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/build/Version.props b/build/Version.props index 2f96d66f55..c11adcf272 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,10 +5,10 @@ 6.3.0 5.1.0 - 10.1.0 + 10.2.0 7.0.0 - 13.1.0 - 15.1.0 + 13.2.0 + 15.2.0 7.1.0 5.9.0 1.4.1 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.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); + } } } } From a91cb620f42073713914da19e37c76ae8469e9ed Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 21 Feb 2024 08:48:06 -0500 Subject: [PATCH 063/137] Workaround for slow GitHub runners --- tests/DMAPI/BasicOperation/Test.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm index e0cc3d2ba4..6eecddfaa7 100644 --- a/tests/DMAPI/BasicOperation/Test.dm +++ b/tests/DMAPI/BasicOperation/Test.dm @@ -33,8 +33,11 @@ world.log << "file check 1" fdel("test_event_output.txt") + + var/start_time = world.timeofday world.TgsTriggerEvent("test_event", list("asdf"), FALSE) - if(fexists("test_event_output.txt")) + + 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" From bab8f93154ad394636511c1a7500af2a1136f50c Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 21 Feb 2024 17:49:16 -0500 Subject: [PATCH 064/137] Use a better sleep command on Windows Works in GitHub Actions --- tests/DMAPI/BasicOperation/test_event-qwer.bat | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/DMAPI/BasicOperation/test_event-qwer.bat b/tests/DMAPI/BasicOperation/test_event-qwer.bat index ecbce0d0af..7eb6a4dff9 100644 --- a/tests/DMAPI/BasicOperation/test_event-qwer.bat +++ b/tests/DMAPI/BasicOperation/test_event-qwer.bat @@ -1,7 +1,5 @@ echo "Running test_event script" - -rem mingw has their own /usr/bin/timeout -C:\Windows\system32\timeout.exe /t 5 +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 From 430b1f9777b6502634a9597bc3d44b61e815c052 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 22 Feb 2024 19:02:08 -0500 Subject: [PATCH 065/137] Workaround for world.Export potentially hanging forever I'm going to close #1681 with this because I haven't seen that particular issue in for every and it's the most closely related. --- build/Version.props | 2 +- src/DMAPI/tgs.dm | 4 ++-- src/DMAPI/tgs/v5/api.dm | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build/Version.props b/build/Version.props index c11adcf272..79032aba9b 100644 --- a/build/Version.props +++ b/build/Version.props @@ -9,7 +9,7 @@ 7.0.0 13.2.0 15.2.0 - 7.1.0 + 7.1.1 5.9.0 1.4.1 1.2.1 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index dc49d2c6f0..a4fb6d40be 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "7.1.0" +#define TGS_DMAPI_VERSION "7.1.1" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -496,7 +496,7 @@ /// 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! * diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm index 9b64931f8f..95b8edd3ee 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -48,6 +48,10 @@ var/datum/tgs_version/api_version = ApiVersion() version = null // we want this to be the TGS version, not the interop version + + // sleep once to prevent an issue where world.Export on the first tick can hang indefinitely + sleep(world.tick_lag) + var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort())) if(!istype(bridge_response)) TGS_ERROR_LOG("Failed initial bridge request!") From fd2e875d1b430e2446d583525d102639f6523cdd Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 22 Feb 2024 19:09:24 -0500 Subject: [PATCH 066/137] Bump webpanel version to 5.6.0 --- build/WebpanelVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/WebpanelVersion.props b/build/WebpanelVersion.props index 4fed7127de..1d18faa135 100644 --- a/build/WebpanelVersion.props +++ b/build/WebpanelVersion.props @@ -1,6 +1,6 @@ - 5.5.1 + 5.6.0 From 788d2bc035989234d07dd5009e52f5cb170cb9b7 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 23 Feb 2024 11:21:15 -0500 Subject: [PATCH 067/137] Necessary update to Octokit due to 32-bit overflow [TGSDeploy] --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 7ef4d1c18c..0a689676b5 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -102,7 +102,7 @@ - + From 888ce7cbd18e6f8f1c0d1bddf6a3ffb8c91b685d Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 23 Feb 2024 12:34:05 -0500 Subject: [PATCH 068/137] Better debug readout when .deb uninstallation fails [TGSDeploy] --- .github/workflows/ci-pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index a9c44ba687..e4f02108a3 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -1211,7 +1211,7 @@ jobs: sleep 10 sudo apt-get remove -y tgstation-server if [[ -d "/opt/tgstation-server" ]]; then - ls -al /opt/tgstation-server + find /opt/tgstation-server exit 2 fi From c37349107255921afde637c0432128147a056014 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 23 Feb 2024 20:16:03 -0500 Subject: [PATCH 069/137] Fix waiting on events never returning if TGS restarts --- src/DMAPI/tgs/v5/topic.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/v5/topic.dm b/src/DMAPI/tgs/v5/topic.dm index e66edc2720..e1f2cb6385 100644 --- a/src/DMAPI/tgs/v5/topic.dm +++ b/src/DMAPI/tgs/v5/topic.dm @@ -177,7 +177,8 @@ reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands() reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort() - pending_events.Cut() + for(var/eventId in pending_events) + pending_events[eventId] = TRUE return reattach_response From 77d8278ff2dbde352480e6f12788a7d483c0beeb Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 25 Feb 2024 12:57:06 -0500 Subject: [PATCH 070/137] Workaround for linux test race condition --- .../Live/Instance/WatchdogTest.cs | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 4a453d4f51..3bf26e3f1d 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -729,28 +729,41 @@ namespace Tgstation.Server.Tests.Live.Instance var foundLivePath = false; var allPaths = new List(); - Assert.IsFalse(proc.HasExited); - foreach (var fd in Directory.GetFiles($"/proc/{pid}/fd")) + var features = new PosixProcessFeatures( + new Lazy(Mock.Of()), + Mock.Of(), + Mock.Of>()); + + features.SuspendProcess(proc); + try { - var sb = new StringBuilder(UInt16.MaxValue); - if (Syscall.readlink(fd, sb) == -1) - throw new UnixIOException(Stdlib.GetLastError()); + Assert.IsFalse(proc.HasExited); + foreach (var fd in Directory.GetFiles($"/proc/{pid}/fd")) + { + var sb = new StringBuilder(UInt16.MaxValue); + if (Syscall.readlink(fd, sb) == -1) + throw new UnixIOException(Stdlib.GetLastError()); - var path = sb.ToString(); + var path = sb.ToString(); - allPaths.Add($"Path: {path}"); - if (path.Contains($"Game/{previousStatus.DirectoryName}")) - failingLinks.Add($"Found fd {fd} resolving to previous absolute path game dir path: {path}"); + allPaths.Add($"Path: {path}"); + if (path.Contains($"Game/{previousStatus.DirectoryName}")) + failingLinks.Add($"Found fd {fd} resolving to previous absolute path game dir path: {path}"); - if (path.Contains($"Game/{currentStatus.ActiveCompileJob.DirectoryName}")) - failingLinks.Add($"Found fd {fd} resolving to current absolute path game dir path: {path}"); + if (path.Contains($"Game/{currentStatus.ActiveCompileJob.DirectoryName}")) + failingLinks.Add($"Found fd {fd} resolving to current absolute path game dir path: {path}"); - if (path.Contains($"Game/Live")) - foundLivePath = true; + if (path.Contains($"Game/Live")) + foundLivePath = true; + } + + if (!foundLivePath) + failingLinks.Add($"Failed to find a path containing the 'Live' directory!"); + } + finally + { + features.ResumeProcess(proc); } - - if (!foundLivePath) - failingLinks.Add($"Failed to find a path containing the 'Live' directory!"); Assert.IsTrue(failingLinks.Count == 0, String.Join(Environment.NewLine, failingLinks.Concat(allPaths))); } From 690662c5afe6a832a3f2fbd525af57211f3aef9e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 25 Feb 2024 13:04:44 -0500 Subject: [PATCH 071/137] Fix race condition in Windows suspend process --- .../System/WindowsProcessFeatures.cs | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs index e842bd9db2..f7ad2096a1 100644 --- a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -68,28 +69,40 @@ namespace Tgstation.Server.Host.System { ArgumentNullException.ThrowIfNull(process); - process.Refresh(); - foreach (ProcessThread thread in process.Threads) + var suspendedThreadIds = new HashSet(); + bool suspendedNewThreads; + do { - var threadId = (uint)thread.Id; - logger.LogTrace("Suspending thread {threadId}...", threadId); - var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, threadId); - if (pOpenThread == IntPtr.Zero) + suspendedNewThreads = false; + process.Refresh(); + foreach (ProcessThread thread in process.Threads) { - logger.LogDebug(new Win32Exception(), "Failed to open thread {threadId}!", threadId); - continue; - } + var threadId = (uint)thread.Id; - try - { - if (NativeMethods.SuspendThread(pOpenThread) == UInt32.MaxValue) - throw new Win32Exception(); - } - finally - { - NativeMethods.CloseHandle(pOpenThread); + if (!suspendedThreadIds.Add(threadId)) + continue; + + suspendedNewThreads = true; + logger.LogTrace("Suspending thread {threadId}...", threadId); + var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, threadId); + if (pOpenThread == IntPtr.Zero) + { + logger.LogDebug(new Win32Exception(), "Failed to open thread {threadId}!", threadId); + continue; + } + + try + { + if (NativeMethods.SuspendThread(pOpenThread) == UInt32.MaxValue) + throw new Win32Exception(); + } + finally + { + NativeMethods.CloseHandle(pOpenThread); + } } } + while (suspendedNewThreads); } /// From 575d811843fe33f12f2d4641cbf114782e8d19f7 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 26 Feb 2024 09:50:03 -0500 Subject: [PATCH 072/137] Increase timeout for dump tests --- tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 3bf26e3f1d..6a59da10bb 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -526,7 +526,7 @@ namespace Tgstation.Server.Tests.Live.Instance }, cancellationToken); Assert.AreEqual(mini, updated.Minidumps); var dumpJob = await instanceClient.DreamDaemon.CreateDump(cancellationToken); - await WaitForJob(dumpJob, 30, false, null, cancellationToken); + await WaitForJob(dumpJob, 60, false, null, cancellationToken); var dumpFiles = Directory.GetFiles(Path.Combine( instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), testVersion.Engine == EngineType.OpenDream ? "*.net.dmp" : "*.dmp"); From 786f98d2869003c92cc0885e41b4e32eb3d1fb53 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 26 Feb 2024 20:46:04 -0500 Subject: [PATCH 073/137] Add a debug message --- tests/Tgstation.Server.Tests/Live/Instance/ChatTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Live/Instance/ChatTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/ChatTest.cs index abb699815e..1dcfd368da 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/ChatTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/ChatTest.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; @@ -196,7 +198,7 @@ namespace Tgstation.Server.Tests.Live.Instance .OrderBy(x => x.StartedAt) .FirstOrDefault(); - Assert.IsNotNull(reconnectJob); + Assert.IsNotNull(reconnectJob, $"Jobs: {JsonConvert.SerializeObject(jobs)}"); await WaitForJob(reconnectJob, 60, false, null, cancellationToken); var channelIdStr = Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_CHANNEL"); From 8a608968bbda5fb97cb4fa405d3e98e1ea9ddf70 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 27 Feb 2024 08:46:34 -0500 Subject: [PATCH 074/137] Fix potential `NullReferenceException` when disabling a user --- .../Utils/SignalR/ComprehensiveHubContext.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs b/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs index d9c32143ce..b47f17d57b 100644 --- a/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs +++ b/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs @@ -147,8 +147,9 @@ namespace Tgstation.Server.Host.Utils.SignalR return old; }); - foreach (var context in connections!) - context.Abort(); + if (connections != null) + foreach (var context in connections) + context.Abort(); } } } From 436938e6bb97a27d0760498d5da6375bbfbe5268 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 27 Feb 2024 08:47:12 -0500 Subject: [PATCH 075/137] Version bump to 6.3.1 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 79032aba9b..34d28b5b29 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.3.0 + 6.3.1 5.1.0 10.2.0 7.0.0 From 59ffa0838971ddb2266cd4c484864f3fc680f79f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:48:04 -0500 Subject: [PATCH 076/137] Better FromCompileJob lock logging --- .../Components/Deployment/DmbFactory.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index d61b2ffc68..92000a1e53 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -325,14 +325,17 @@ namespace Tgstation.Server.Host.Components.Deployment if (!jobLockCounts.TryGetValue(compileJobId, out int value)) { value = 1; + logger.LogTrace("Initializing lock count for compile job {id}", compileJobId); jobLockCounts.Add(compileJobId, 1); } else + { + logger.LogTrace("FromCompileJob already had a jobLockCounts entry for {id}. Incrementing lock count to {value}.", compileJobId, value); jobLockCounts[compileJobId] = ++value; + } providerSubmitted = true; - logger.LogTrace("Compile job {id} lock count now: {lockCount}", compileJobId, value); return newProvider; } } From f29ad29bc449373bc96e49243fab061801e4e233 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:48:47 -0500 Subject: [PATCH 077/137] Cleanup an unnecessary `catch` --- .../Components/Deployment/DmbFactory.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index 92000a1e53..a4ca0c26ca 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -399,11 +399,7 @@ namespace Tgstation.Server.Host.Components.Deployment ++deleting; await DeleteCompileJobContent(x, cancellationToken); } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) + catch (Exception e) when (e is not OperationCanceledException) { logger.LogWarning(e, "Error deleting directory {dirName}!", x); } From cfc551c547675a19007130bc3b6cf44386cdec5d Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:49:06 -0500 Subject: [PATCH 078/137] Switch a lambda to `ValueTask`s --- src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index a4ca0c26ca..ccf40188f8 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -388,7 +388,7 @@ namespace Tgstation.Server.Host.Components.Deployment await ioManager.CreateDirectory(gameDirectory, cancellationToken); var directories = await ioManager.GetDirectories(gameDirectory, cancellationToken); int deleting = 0; - var tasks = directories.Select(async x => + var tasks = directories.Select(async x => { var nameOnly = ioManager.GetFileName(x); if (jobUidsToNotErase.Contains(nameOnly)) @@ -405,7 +405,7 @@ namespace Tgstation.Server.Host.Components.Deployment } }).ToList(); if (deleting > 0) - await Task.WhenAll(tasks); + await ValueTaskExtensions.WhenAll(tasks); } #pragma warning restore CA1506 From 94106a3acf5a514ff142239949efbed94aba69b9 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:51:43 -0500 Subject: [PATCH 079/137] Better `CompileJob` lock logging --- .../Components/Deployment/DmbFactory.cs | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index ccf40188f8..fbeb7d3f92 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -178,7 +179,8 @@ namespace Tgstation.Server.Host.Components.Deployment { var jobId = nextDmbProvider.CompileJob.Require(x => x.Id); var incremented = jobLockCounts[jobId] += lockCount; - logger.LogTrace("Compile job {jobId} lock count now: {lockCount}", jobId, incremented); + logger.LogTrace("Compile job {jobId} lock increased by: {increment}", jobId, lockCount); + LogLockCounts(); return nextDmbProvider; } } @@ -336,6 +338,7 @@ namespace Tgstation.Server.Host.Components.Deployment providerSubmitted = true; + LogLockCounts(); return newProvider; } } @@ -434,14 +437,14 @@ namespace Tgstation.Server.Host.Components.Deployment // First kill the GitHub deployment var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager(metadata, job); - // DCT: None available - var deploymentJob = remoteDeploymentManager.MarkInactive(job, CancellationToken.None); + var cancellationToken = cleanupCts.Token; + var deploymentJob = remoteDeploymentManager.MarkInactive(job, cancellationToken); - var deleteTask = DeleteCompileJobContent(job.DirectoryName!.Value.ToString(), cleanupCts.Token); + var deleteTask = DeleteCompileJobContent(job.DirectoryName!.Value.ToString(), cancellationToken); await ValueTaskExtensions.WhenAll(deleteTask, deploymentJob); } - catch (Exception ex) + catch (Exception ex) when (ex is not OperationCanceledException) { logger.LogWarning(ex, "Error cleaning up compile job {jobGuid}!", job.DirectoryName); } @@ -467,6 +470,8 @@ namespace Tgstation.Server.Host.Components.Deployment } else logger.LogError("Extra Dispose of DmbProvider for CompileJob {compileJobId}!", jobId); + + LogLockCounts(); } } @@ -482,5 +487,30 @@ namespace Tgstation.Server.Host.Components.Deployment await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List { ioManager.ResolvePath(directory) }, true, cancellationToken); await ioManager.DeleteDirectory(directory, cancellationToken); } + + /// + /// Log out the current lock counts to Trace. + /// + /// must be locked before calling this function. + void LogLockCounts() + { + if (jobLockCounts.Count == 0) + { + logger.LogWarning("No compile jobs registered!"); + return; + } + + var builder = new StringBuilder(); + foreach (var jobId in jobLockCounts.Keys) + { + builder.AppendLine(); + builder.Append("\t- "); + builder.Append(jobId); + builder.Append(": "); + builder.Append(jobLockCounts[jobId]); + } + + logger.LogTrace("Compile Job Lock Counts:{details}", builder.ToString()); + } } } From c9c6e50284db719805e850e0fab3ede0309b65a8 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:52:25 -0500 Subject: [PATCH 080/137] Remove a redundant `await` --- .../Components/Watchdog/AdvancedWatchdog.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs index 884541e9fb..aad9bcf64e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs @@ -127,8 +127,12 @@ namespace Tgstation.Server.Host.Components.Watchdog // If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again. ActiveSwappable = null; - await (pendingSwappable?.DisposeAsync() ?? ValueTask.CompletedTask); - pendingSwappable = null; + + if (pendingSwappable != null) + { + await pendingSwappable.DisposeAsync(); + pendingSwappable = null; + } await DrainDeploymentCleanupTasks(true); } From 1beb53fe704571971870a7d91a14e5a29d66c71f Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:57:47 -0500 Subject: [PATCH 081/137] Fix double call to `BeforeApplyDmb` --- .../Components/Watchdog/AdvancedWatchdog.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs index aad9bcf64e..0ff64a54e4 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs @@ -142,8 +142,6 @@ namespace Tgstation.Server.Host.Components.Watchdog { if (pendingSwappable != null) { - ValueTask RunPrequel() => BeforeApplyDmb(pendingSwappable.CompileJob, cancellationToken); - var needToSwap = !pendingSwappable.Swapped; var controller = Server!; if (needToSwap) @@ -155,7 +153,6 @@ namespace Tgstation.Server.Host.Components.Watchdog // integration test logging will catch this Logger.LogError( "The reboot bridge request completed before the watchdog could suspend the server! This can lead to buggy DreamDaemon behaviour and should be reported! To ensure stability, we will need to hard reboot the server"); - await RunPrequel(); return MonitorAction.Restart; } @@ -169,7 +166,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } } - var updateTask = RunPrequel(); + var updateTask = BeforeApplyDmb(pendingSwappable.CompileJob, cancellationToken); if (needToSwap) await PerformDmbSwap(pendingSwappable, cancellationToken); From 81dd33c1d1d6fec6b0df2707af74737f9f008ea4 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 13:58:43 -0500 Subject: [PATCH 082/137] Documentation comment cleanup --- .../Components/Deployment/SwappableDmbProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs index 75c5a1b6bb..7ad999856c 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/SwappableDmbProvider.cs @@ -87,7 +87,7 @@ namespace Tgstation.Server.Host.Components.Deployment } /// - /// Should be . before calling to ensure the is ready to instantly swap. Can be called multiple times. + /// Should be ed. before calling to ensure the is ready to instantly swap. Can be called multiple times. /// /// The for the operation. /// A representing the preparation process. From 8b9f5535ca72c63c3e62c441b66911e0a176452d Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 14:17:49 -0500 Subject: [PATCH 083/137] Fix potential for a deployment to leak when stopping or restarting the server I'll say this fixes #1779, but we'll have to see --- .../Components/Watchdog/AdvancedWatchdog.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs index 0ff64a54e4..610fe25bba 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs @@ -184,7 +184,8 @@ namespace Tgstation.Server.Host.Components.Watchdog currentCompileJobId, lingeringDeploymentExpirySeconds); - var timeout = AsyncDelayer.Delay(TimeSpan.FromSeconds(lingeringDeploymentExpirySeconds), cancellationToken); + // DCT: A cancel firing here can result in us leaving a dmbprovider undisposed, localDeploymentCleanupGate will always fire in that case + var timeout = AsyncDelayer.Delay(TimeSpan.FromSeconds(lingeringDeploymentExpirySeconds), CancellationToken.None); var completedTask = await Task.WhenAny( localDeploymentCleanupGate.Task, From 5481ebbb7ebc3a7da82466214f52176a35edca22 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 14:18:06 -0500 Subject: [PATCH 084/137] Version bump to 6.3.2 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 34d28b5b29..bd546a1cd3 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.3.1 + 6.3.2 5.1.0 10.2.0 7.0.0 From 4c59fadcfff1b2bb9c43fee44fe75b41e3cf8829 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 14:20:24 -0500 Subject: [PATCH 085/137] Nuget patches --- build/TestCommon.props | 6 +++--- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/TestCommon.props b/build/TestCommon.props index af63625813..add8e10ac6 100644 --- a/build/TestCommon.props +++ b/build/TestCommon.props @@ -3,7 +3,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,9 +18,9 @@ - + - + diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 0a689676b5..04df231d71 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -104,7 +104,7 @@ - + @@ -128,7 +128,7 @@ - + From e684b76a3c0a7c564f6fef459959d6c4cef7907e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 14:47:04 -0500 Subject: [PATCH 086/137] Nuget package update --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 0b93c41fec..b7e14a249b 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -27,7 +27,7 @@ - + From 660b5490cf93b259b423390a5ed7b72a5321ac67 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 17:38:06 -0500 Subject: [PATCH 087/137] Use our own app to create the `CI Completion` check --- .github/workflows/ci-pipeline.yml | 33 +++++++--- .../Tgstation.Server.ReleaseNotes/Program.cs | 64 ++++++++++++++++++- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index e4f02108a3..d136649c08 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -1396,19 +1396,32 @@ jobs: name: CI Completion Gate needs: [ pages-build, docker-build, build-deb, build-msi, validate-openapi-spec, upload-code-coverage, check-winget-pr-template, code-scanning ] runs-on: ubuntu-latest - permissions: - checks: write - contents: read if: (!(cancelled() || failure()) && needs.pages-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.build-msi.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && needs.check-winget-pr-template.result == 'success' && needs.code-scanning.result == 'success') steps: - - name: Create Completion Check - uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} - name: CI Completion - conclusion: success - output: | - {"summary":"The CI Pipeline completed successfully"} + dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' + dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} + + - name: Checkout (Branch) + uses: actions/checkout@v4 + if: github.event_name == 'push' || github.event_name == 'schedule' + + - name: Checkout (PR Merge) + uses: actions/checkout@v4 + if: github.event_name != 'push' && github.event_name != 'schedule' + with: + ref: "refs/pull/${{ github.event.number }}/merge" + + - name: Restore + run: dotnet restore + + - name: Build ReleaseNotes + run: dotnet build -c Release -p:TGS_HOST_NO_WEBPANEL=true tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj + + - name: Run ReleaseNotes Create CI Completion Check + run: dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --ci-completion-check ${{ github.sha }} ${{ secrets.TGS_CI_GITHUB_APP_TOKEN_BASE64 }} deployment-gate: name: Deployment Start Gate diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs index 6130bbc17a..d5ac69e564 100644 --- a/tools/Tgstation.Server.ReleaseNotes/Program.cs +++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs @@ -1,20 +1,25 @@ // This program is minimal effort and should be sent to remedial school using System; +using System.Buffers.Text; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Security; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; +using Microsoft.IdentityModel.Tokens; + using Newtonsoft.Json; using Octokit; @@ -32,8 +37,11 @@ namespace Tgstation.Server.ReleaseNotes static class Program { const string OutputPath = "release_notes.md"; + + // some stuff that should be abstracted for different repos const string RepoOwner = "tgstation"; const string RepoName = "tgstation-server"; + const int AppId = 847638; /// /// The entrypoint for the @@ -52,13 +60,15 @@ namespace Tgstation.Server.ReleaseNotes var shaCheck = versionString.Equals("--winget-template-check", StringComparison.OrdinalIgnoreCase); var fullNotes = versionString.Equals("--generate-full-notes", StringComparison.OrdinalIgnoreCase); var nuget = versionString.Equals("--nuget", StringComparison.OrdinalIgnoreCase); + var ciCompletionCheck = versionString.Equals("--ci-completion-check", StringComparison.OrdinalIgnoreCase); if ((!Version.TryParse(versionString, out var version) || version.Revision != -1) && !ensureRelease && !linkWinget && !shaCheck && !fullNotes - && !nuget) + && !nuget + && !ciCompletionCheck) { Console.WriteLine("Invalid version: " + versionString); return 2; @@ -129,6 +139,17 @@ namespace Tgstation.Server.ReleaseNotes return await Winget(client, actionsUrl, null); } + if (ciCompletionCheck) + { + if (args.Length < 3) + { + Console.WriteLine("Missing SHA or PEM Base64 for creating check run!"); + return 4543; + } + + return await CICompletionCheck(client, args[1], args[2]); + } + if (shaCheck) { if(args.Length < 2) @@ -1583,6 +1604,47 @@ package (version) distribution(s); urgency=urgency return 0; } + static async ValueTask CICompletionCheck(GitHubClient gitHubClient, string currentSha, string pemBase64) + { + var pemBytes = Convert.FromBase64String(pemBase64); + var pem = Encoding.UTF8.GetString(pemBytes); + + var rsa = RSA.Create(); + rsa.ImportFromPem(pem); + + var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256); + var jwtSecurityTokenHandler = new JwtSecurityTokenHandler { SetDefaultTimesOnTokenCreation = false }; + + var now = DateTime.UtcNow; + + var jwt = jwtSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor + { + Issuer = AppId.ToString(), + Expires = now.AddMinutes(10), + IssuedAt = now, + SigningCredentials = signingCredentials + }); + + var jwtStr = jwtSecurityTokenHandler.WriteToken(jwt); + + gitHubClient.Credentials = new Credentials(jwtStr, AuthenticationType.Bearer); + + var installation = await gitHubClient.GitHubApps.GetRepositoryInstallationForCurrent(RepoOwner, RepoName); + var installToken = await gitHubClient.GitHubApps.CreateInstallationToken(installation.Id); + + gitHubClient.Credentials = new Credentials(installToken.Token); + + await gitHubClient.Check.Run.Create(RepoOwner, RepoName, new NewCheckRun("CI Completion", currentSha) + { + CompletedAt = now, + Conclusion = CheckConclusion.Success, + Output = new NewCheckRunOutput("CI Completion", "The CI Pipeline completed successfully"), + Status = CheckStatus.Completed, + }); + + return 0; + } + static void DebugAssert(bool condition, string message = null) { // This exists because one of the fucking asserts evaluates an enumerable or something and it was getting optimized out in release From bfc2e64e663869105ce6680b6538f9356bbd3b85 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 2 Mar 2024 15:35:58 -0500 Subject: [PATCH 088/137] Set `oom_score_adj` appropriately on Linux Closes #1792 --- README.md | 1 + .../Components/Deployment/DreamMaker.cs | 3 +- .../Components/Engine/OpenDreamInstaller.cs | 3 +- .../Engine/WindowsByondInstaller.cs | 3 +- .../Session/SessionControllerFactory.cs | 3 +- .../Components/StaticFiles/Configuration.cs | 3 +- src/Tgstation.Server.Host/Core/Application.cs | 1 + .../IO/DefaultIOManager.cs | 22 ++-- src/Tgstation.Server.Host/IO/IIOManager.cs | 7 ++ .../System/IProcessExecutor.cs | 8 +- .../System/IProcessFeatures.cs | 14 ++- .../System/PosixProcessFeatures.cs | 104 +++++++++++++++++- .../System/ProcessExecutor.cs | 14 ++- .../System/WindowsFirewallHelper.cs | 3 +- .../System/WindowsProcessFeatures.cs | 4 + .../System/TestPosixSignalHandler.cs | 3 +- .../Live/Instance/WatchdogTest.cs | 4 +- .../Live/TestLiveServer.cs | 3 +- .../TestSystemInteraction.cs | 4 +- tests/Tgstation.Server.Tests/TestVersions.cs | 5 +- 20 files changed, 181 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 085f8c05db..f535174118 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ docker run \ --network="host" \ # Not recommended, eases networking setup if your sql server is on the same machine --name="tgs" \ # Name for the container --cap-add=sys_nice \ # Recommended, allows TGS to lower the niceness of child processes if it sees fit + --cap-add=sys_resource \ # Recommended, allows TGS to not be killed by the OOM killer before its child processes --init \ #Highly recommended, reaps potential zombie processes -p 5000:5000 \ # Port bridge for accessing TGS, you can change this if you need -p 0.0.0.0:: \ # Port bridge for accessing DreamDaemon diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 408cc07bfb..27107f7ff8 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -855,11 +855,12 @@ namespace Tgstation.Server.Host.Components.Deployment var environment = await engineLock.LoadEnv(logger, true, cancellationToken); var arguments = engineLock.FormatCompilerArguments($"{job.DmeName}.{DmeExtension}"); - await using var dm = processExecutor.LaunchProcess( + await using var dm = await processExecutor.LaunchProcess( engineLock.CompilerExePath, ioManager.ResolvePath( job.DirectoryName!.Value.ToString()), arguments, + cancellationToken, environment, readStandardHandles: true, noShellExecute: true); diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs index eb0bca9450..e552d16b40 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs @@ -241,10 +241,11 @@ namespace Tgstation.Server.Host.Components.Engine async shortenedPath => { var shortenedDeployPath = IOManager.ConcatPath(shortenedPath, DeployDir); - await using var buildProcess = ProcessExecutor.LaunchProcess( + await using var buildProcess = await ProcessExecutor.LaunchProcess( dotnetPath, shortenedPath, $"run -c Release --project OpenDreamPackageTool -- --tgs -o {shortenedDeployPath}", + cancellationToken, null, null, !GeneralConfiguration.OpenDreamSuppressInstallOutput, diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs index 1534bfc500..35bcbf63dc 100644 --- a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs @@ -283,10 +283,11 @@ namespace Tgstation.Server.Host.Components.Engine try { // noShellExecute because we aren't doing runas shennanigans - await using var directXInstaller = processExecutor.LaunchProcess( + await using var directXInstaller = await processExecutor.LaunchProcess( IOManager.ConcatPath(rbdx, "DXSETUP.exe"), rbdx, "/silent", + cancellationToken, noShellExecute: true); int exitCode; diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 5e89fe8cb2..1573d4c48a 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -516,10 +516,11 @@ namespace Tgstation.Server.Host.Components.Session ? logFilePath : null); - var process = processExecutor.LaunchProcess( + var process = await processExecutor.LaunchProcess( engineLock.ServerExePath, dmbProvider.Directory, arguments, + cancellationToken, environment, logFilePath, engineLock.HasStandardOutput, diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index f25f515a60..86ae2c28ca 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -761,7 +761,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles foreach (var scriptFile in scriptFiles) { logger.LogTrace("Running event script {scriptFile}...", scriptFile); - await using (var script = processExecutor.LaunchProcess( + await using (var script = await processExecutor.LaunchProcess( ioManager.ConcatPath(resolvedScriptsDir, scriptFile), resolvedScriptsDir, String.Join( @@ -778,6 +778,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles return $"\"{arg}\""; })), + cancellationToken, readStandardHandles: true, noShellExecute: true)) using (cancellationToken.Register(() => script.Terminate())) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index bad47cde0b..9569d2510b 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -356,6 +356,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); + services.AddHostedService(); // PosixProcessFeatures also needs a IProcessExecutor for gcore services.AddSingleton(x => new Lazy(() => x.GetRequiredService(), true)); diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index cf7b616587..0ce1d50f45 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -221,14 +221,7 @@ namespace Tgstation.Server.Host.IO /// public async ValueTask ReadAllBytes(string path, CancellationToken cancellationToken) { - path = ResolvePath(path); - await using var file = new FileStream( - path, - FileMode.Open, - FileAccess.Read, - FileShare.ReadWrite | FileShare.Delete, - DefaultBufferSize, - FileOptions.Asynchronous | FileOptions.SequentialScan); + await using var file = CreateAsyncSequentialReadStream(path); byte[] buf; buf = new byte[file.Length]; await file.ReadAsync(buf, cancellationToken); @@ -261,6 +254,19 @@ namespace Tgstation.Server.Host.IO FileOptions.Asynchronous | FileOptions.SequentialScan); } + /// + public FileStream CreateAsyncSequentialReadStream(string path) + { + path = ResolvePath(path); + return new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite | FileShare.Delete, + DefaultBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); + } + /// public Task> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew( () => diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index d88ec3ec17..8b8b6f0ce7 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -110,6 +110,13 @@ namespace Tgstation.Server.Host.IO /// The open . FileStream CreateAsyncSequentialWriteStream(string path); + /// + /// Creates an asynchronous for sequential reading. + /// + /// The path of the file to write, will be truncated. + /// The open . + FileStream CreateAsyncSequentialReadStream(string path); + /// /// Writes some to a file at overwriting previous content. /// diff --git a/src/Tgstation.Server.Host/System/IProcessExecutor.cs b/src/Tgstation.Server.Host/System/IProcessExecutor.cs index 34962811e1..0ce0c98a8c 100644 --- a/src/Tgstation.Server.Host/System/IProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/IProcessExecutor.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Tgstation.Server.Host.System { @@ -13,15 +15,17 @@ namespace Tgstation.Server.Host.System /// The full path to the executable file. /// The working directory for the . /// The arguments for the . + /// The for the operation. /// A of environment variables to set. /// File to write process output and error streams to. Requires to be . /// If the process output and error streams should be read. /// If shell execute should not be used. Must be set if is set. - /// The new . - IProcess LaunchProcess( + /// A resulting in the new . + ValueTask LaunchProcess( string fileName, string workingDirectory, string arguments, + CancellationToken cancellationToken, IReadOnlyDictionary? environment = null, string? fileRedirect = null, bool readStandardHandles = false, diff --git a/src/Tgstation.Server.Host/System/IProcessFeatures.cs b/src/Tgstation.Server.Host/System/IProcessFeatures.cs index 927afd7e63..b00c5a4934 100644 --- a/src/Tgstation.Server.Host/System/IProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/IProcessFeatures.cs @@ -18,11 +18,11 @@ namespace Tgstation.Server.Host.System /// /// Suspend a given . /// - /// The to suspend. + /// The to suspend. void SuspendProcess(global::System.Diagnostics.Process process); /// - /// Resume a given suspended . + /// Resume a given suspended . /// /// The to suspended. void ResumeProcess(global::System.Diagnostics.Process process); @@ -30,11 +30,19 @@ namespace Tgstation.Server.Host.System /// /// Create a dump file for a given . /// - /// The to dump. + /// The to dump. /// The full path to the output file. /// If a minidump should be taken as opposed to a full dump. /// The for the operation. /// A representing the running operation. ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken); + + /// + /// Run events on starting a process. + /// + /// The that was started. + /// The for the operation. + /// A resulting in the ID. + ValueTask HandleProcessStart(global::System.Diagnostics.Process process, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs index 7fd2ce8559..9d97d20be5 100644 --- a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -1,7 +1,11 @@ using System; +using System.Globalization; +using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Mono.Unix; using Mono.Unix.Native; @@ -13,8 +17,18 @@ using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.System { /// - sealed class PosixProcessFeatures : IProcessFeatures + sealed class PosixProcessFeatures : IProcessFeatures, IHostedService { + /// + /// Difference from to set our own oom_score_adj to. 1 higher host watchdog. + /// + const short SelfOomAdjust = 1; + + /// + /// Difference from to set the oom_score_adj of child processes to. 1 higher than ourselves. + /// + const short ChildProcessOomAdjust = SelfOomAdjust + 1; + /// /// loaded . /// @@ -30,6 +44,11 @@ namespace Tgstation.Server.Host.System /// readonly ILogger logger; + /// + /// The original value of oom_score_adj as read from the /proc/ filesystem. Inherited from parent process. + /// + short baselineOomAdjust; + /// /// Initializes a new instance of the class. /// @@ -88,10 +107,11 @@ namespace Tgstation.Server.Host.System string? output; int exitCode; - await using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess( + await using (var gcoreProc = await lazyLoadedProcessExecutor.Value.LaunchProcess( GCorePath, Environment.CurrentDirectory, $"{(!minidump ? "-a " : String.Empty)}-o {outputFile} {process.Id}", + cancellationToken, readStandardHandles: true, noShellExecute: true)) { @@ -112,5 +132,85 @@ namespace Tgstation.Server.Host.System var generatedGCoreFile = $"{outputFile}.{pid}"; await ioManager.MoveFile(generatedGCoreFile, outputFile, cancellationToken); } + + /// + public async ValueTask HandleProcessStart(global::System.Diagnostics.Process process, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(process); + var pid = process.Id; + try + { + // make sure all processes we spawn are killed _before_ us + await AdjustOutOfMemoryScore(pid, ChildProcessOomAdjust, cancellationToken); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.LogWarning(ex, "Failed to adjust OOM killer score for pid {pid}!", pid); + } + + return pid; + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + // let this all throw + string originalString; + { + // can't use ReadAllBytes here, /proc files have 0 length so the buffer is initialized to empty + // https://stackoverflow.com/questions/12237712/how-can-i-show-the-size-of-files-in-proc-it-should-not-be-size-zero + await using var fileStream = ioManager.CreateAsyncSequentialReadStream( + "/proc/self/oom_score_adj"); + using var reader = new StreamReader(fileStream, Encoding.UTF8, leaveOpen: true); + originalString = await reader.ReadToEndAsync(cancellationToken); + } + + var trimmedString = originalString.Trim(); + + logger.LogTrace("Original oom_score_adj is \"{original}\"", trimmedString); + + var originalOomAdjust = Int16.Parse(trimmedString, CultureInfo.InvariantCulture); + baselineOomAdjust = Math.Clamp(originalOomAdjust, (short)-1000, (short)1000); + + if (originalOomAdjust != baselineOomAdjust) + logger.LogWarning("oom_score_adj is at it's limit of 1000 (Clamped from {original}). TGS cannot guarantee the kill order of its parent/child processes!", originalOomAdjust); + else + logger.LogWarning("oom_score_adj is at it's limit of 1000. TGS cannot guarantee the kill order of its parent/child processes!"); + + try + { + // we do not want to be killed before the host watchdog + await AdjustOutOfMemoryScore(null, SelfOomAdjust, cancellationToken); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.LogWarning(ex, "Could not increase oom_score_adj!"); + } + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + /// Set oom_score_adj for a given . + /// + /// The or to self adjust. + /// The value being written to the adjustment file. + /// The for the operation. + /// A representing the running operation. + ValueTask AdjustOutOfMemoryScore(int? pid, short adjustment, CancellationToken cancellationToken) + { + var adjustedValue = Math.Clamp(baselineOomAdjust + adjustment, -1000, 1000); + + var pidStr = pid.HasValue + ? pid.Value.ToString(CultureInfo.InvariantCulture) + : "self"; + logger.LogTrace( + "Setting oom_score_adj of {pid} to {adjustment}...", pidStr, adjustedValue); + return ioManager.WriteAllBytes( + $"/proc/{pidStr}/oom_score_adj", + Encoding.UTF8.GetBytes(adjustedValue.ToString(CultureInfo.InvariantCulture)), + cancellationToken); + } } } diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 445a333ffd..01fe2c00a4 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -105,10 +105,11 @@ namespace Tgstation.Server.Host.System } /// - public IProcess LaunchProcess( + public async ValueTask LaunchProcess( string fileName, string workingDirectory, string arguments, + CancellationToken cancellationToken, IReadOnlyDictionary? environment, string? fileRedirect, bool readStandardHandles, @@ -174,7 +175,16 @@ namespace Tgstation.Server.Host.System ExclusiveProcessLaunchLock.ExitReadLock(); } - pid = handle.Id; + try + { + pid = await processFeatures.HandleProcessStart(handle, cancellationToken); + } + catch + { + handle.Kill(); + throw; + } + processStartTcs?.SetResult(pid); } catch (Exception ex) diff --git a/src/Tgstation.Server.Host/System/WindowsFirewallHelper.cs b/src/Tgstation.Server.Host/System/WindowsFirewallHelper.cs index fff1521092..13e1171053 100644 --- a/src/Tgstation.Server.Host/System/WindowsFirewallHelper.cs +++ b/src/Tgstation.Server.Host/System/WindowsFirewallHelper.cs @@ -31,10 +31,11 @@ namespace Tgstation.Server.Host.System { logger.LogInformation("Adding Windows Firewall exception for {path}...", exePath); var arguments = $"advfirewall firewall add rule name=\"{exceptionName}\" program=\"{exePath}\" protocol=tcp dir=in enable=yes action=allow"; - await using var netshProcess = processExecutor.LaunchProcess( + await using var netshProcess = await processExecutor.LaunchProcess( "netsh.exe", Environment.CurrentDirectory, arguments, + cancellationToken, readStandardHandles: true, noShellExecute: true); diff --git a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs index f7ad2096a1..6b71b2b253 100644 --- a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs @@ -172,5 +172,9 @@ namespace Tgstation.Server.Host.System DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } + + /// + public ValueTask HandleProcessStart(global::System.Diagnostics.Process process, CancellationToken cancellationToken) + => ValueTask.FromResult((process ?? throw new ArgumentNullException(nameof(process))).Id); } } diff --git a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs index 121df115e3..dd0f5fd5fb 100644 --- a/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs +++ b/tests/Tgstation.Server.Host.Tests/System/TestPosixSignalHandler.cs @@ -63,11 +63,12 @@ namespace Tgstation.Server.Host.System.Tests Mock.Of(), loggerFactory.CreateLogger(), loggerFactory); - await using var subProc = processExecutor + await using var subProc = await processExecutor .LaunchProcess( "dotnet", pathToSignalTestApp, $"run -c {CurrentConfig} --no-build", + CancellationToken.None, null, null, true, diff --git a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs index 6a59da10bb..48d8a5faee 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs @@ -731,7 +731,7 @@ namespace Tgstation.Server.Tests.Live.Instance var features = new PosixProcessFeatures( new Lazy(Mock.Of()), - Mock.Of(), + new DefaultIOManager(), Mock.Of>()); features.SuspendProcess(proc); @@ -798,7 +798,7 @@ namespace Tgstation.Server.Tests.Live.Instance executor = new ProcessExecutor( RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new WindowsProcessFeatures(Mock.Of>()) - : new PosixProcessFeatures(new Lazy(() => executor), Mock.Of(), Mock.Of>()), + : new PosixProcessFeatures(new Lazy(() => executor), new DefaultIOManager(), Mock.Of>()), Mock.Of(), Mock.Of>(), LoggerFactory.Create(x => { })); diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index 5e058d24eb..b218b14680 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -1100,10 +1100,11 @@ namespace Tgstation.Server.Tests.Live async ValueTask RunGitCommand(string args) { - await using var gitRemoteOriginFixProc = processExecutor.LaunchProcess( + await using var gitRemoteOriginFixProc = await processExecutor.LaunchProcess( "git", repoPath, args, + cancellationToken, null, null, true, diff --git a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs index a2b43af6fd..e7c8e28962 100644 --- a/tests/Tgstation.Server.Tests/TestSystemInteraction.cs +++ b/tests/Tgstation.Server.Tests/TestSystemInteraction.cs @@ -28,7 +28,7 @@ namespace Tgstation.Server.Tests Mock.Of>(), loggerFactory); - await using var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, null, true, true); + await using var process = await processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, CancellationToken.None, null, null, true, true); using var cts = new CancellationTokenSource(); cts.CancelAfter(3000); var exitCode = await process.Lifetime.WaitAsync(cts.Token); @@ -63,7 +63,7 @@ namespace Tgstation.Server.Tests File.Delete(tempFile); try { - await using (var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, tempFile, true, true)) + await using (var process = await processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, CancellationToken.None, null, tempFile, true, true)) { using var cts = new CancellationTokenSource(); cts.CancelAfter(3000); diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 0b4cc591bb..adadd4bfcb 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -208,7 +208,7 @@ namespace Tgstation.Server.Tests ? new WindowsProcessFeatures(Mock.Of>()) : new PosixProcessFeatures( new Lazy(() => null), - Mock.Of(), + new DefaultIOManager(), loggerFactory.CreateLogger()), Mock.Of(), loggerFactory.CreateLogger(), @@ -498,10 +498,11 @@ namespace Tgstation.Server.Tests try { - await using var process = processExecutor.LaunchProcess( + await using var process = await processExecutor.LaunchProcess( ddPath, Environment.CurrentDirectory, "fake.dmb -map-threads 3 -close", + CancellationToken.None, null, null, true, From 2b44a592f6e56ad3dc354482e23a2ad8db22a669 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 3 Mar 2024 10:20:50 -0500 Subject: [PATCH 089/137] Attempt to deduce GitHub and GitLab test merge URLs if API calls fail Closes #1795 --- .../Components/Repository/GitHubRemoteFeatures.cs | 2 +- .../Components/Repository/GitLabRemoteFeatures.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs index 7a21ebde2b..6109f490bb 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs @@ -93,7 +93,7 @@ namespace Tgstation.Server.Host.Components.Repository Comment = parameters.Comment, Number = parameters.Number, TargetCommitSha = revisionToUse, - Url = pr?.HtmlUrl ?? errorMessage, + Url = pr?.HtmlUrl ?? $"https://github.com/{RemoteRepositoryOwner}/{RemoteRepositoryName}/pull/{parameters.Number}", }; return testMerge; diff --git a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs index 9e468b06e6..9cf32f3f7b 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs @@ -85,7 +85,7 @@ namespace Tgstation.Server.Host.Components.Repository Comment = parameters.Comment, Number = parameters.Number, TargetCommitSha = parameters.TargetCommitSha, - Url = ex.Message, + Url = $"https://gitlab.com/{RemoteRepositoryOwner}/{RemoteRepositoryName}/-/merge_requests/{parameters.Number}", }; } } From ffe273b0c9d00a8792b01567d2eaaa444a9d0df1 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 3 Mar 2024 13:36:18 -0500 Subject: [PATCH 090/137] A note about `IIOManager.ReadAllBytes` and `/proc` --- src/Tgstation.Server.Host/IO/IIOManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index 8b8b6f0ce7..e14f15cf29 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -85,6 +85,7 @@ namespace Tgstation.Server.Host.IO /// The path of the file to read. /// A for the operation. /// A that results in the contents of a file at . + /// This function will fail to read files from the /proc filesystem on Linux. ValueTask ReadAllBytes(string path, CancellationToken cancellationToken); /// From c4f194a61ed086b1e83398f7a0e5a7795e9355ed Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 3 Mar 2024 16:29:10 -0500 Subject: [PATCH 091/137] Update Master Merge workflow --- .github/workflows/stable-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stable-merge.yml b/.github/workflows/stable-merge.yml index 9135dc5f7a..bd4d64d09f 100644 --- a/.github/workflows/stable-merge.yml +++ b/.github/workflows/stable-merge.yml @@ -17,7 +17,7 @@ jobs: fetch-depth: 0 - name: Merge master into dev - uses: robotology/gh-action-nightly-merge@22f5e45d028f22837d617fa07512925457eec184 #v1.3.3 + uses: robotology/gh-action-nightly-merge@14b4a4cf358f7479aa708bee05cf8a794d6a2516 #v1.5.0 with: stable_branch: 'master' development_branch: 'dev' From e3fe9b5c42fb17429f7cbac2d50cdf08d6ff5c17 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 3 Mar 2024 21:31:05 -0500 Subject: [PATCH 092/137] Version bump to 6.4.0 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index bd546a1cd3..391a9e4ec8 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.3.2 + 6.4.0 5.1.0 10.2.0 7.0.0 From 9b54ea7c4d75a0c7d7930991c6ed41e0362fad71 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 3 Mar 2024 23:53:11 -0500 Subject: [PATCH 093/137] Fix `CI Completion` check not being created for the right SHA --- .github/workflows/ci-pipeline.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index d136649c08..61a7d50a23 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -1420,7 +1420,12 @@ jobs: - name: Build ReleaseNotes run: dotnet build -c Release -p:TGS_HOST_NO_WEBPANEL=true tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj - - name: Run ReleaseNotes Create CI Completion Check + - name: Run ReleaseNotes Create CI Completion Check (PR HEAD) + if: github.event_name != 'push' && github.event_name != 'schedule' + run: dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --ci-completion-check ${{ github.event.pull_request.head.sha }} ${{ secrets.TGS_CI_GITHUB_APP_TOKEN_BASE64 }} + + - name: Run ReleaseNotes Create CI Completion Check (Branch) + if: github.event_name == 'push' || github.event_name == 'schedule' run: dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --ci-completion-check ${{ github.sha }} ${{ secrets.TGS_CI_GITHUB_APP_TOKEN_BASE64 }} deployment-gate: From 4a5610ce14195c532d02471de0b6e7461f43f9d8 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 4 Mar 2024 14:14:16 -0500 Subject: [PATCH 094/137] Fix incorrect warning on Linux --- src/Tgstation.Server.Host/System/PosixProcessFeatures.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs index 9d97d20be5..f9894e9ac9 100644 --- a/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/PosixProcessFeatures.cs @@ -172,10 +172,11 @@ namespace Tgstation.Server.Host.System var originalOomAdjust = Int16.Parse(trimmedString, CultureInfo.InvariantCulture); baselineOomAdjust = Math.Clamp(originalOomAdjust, (short)-1000, (short)1000); - if (originalOomAdjust != baselineOomAdjust) - logger.LogWarning("oom_score_adj is at it's limit of 1000 (Clamped from {original}). TGS cannot guarantee the kill order of its parent/child processes!", originalOomAdjust); - else - logger.LogWarning("oom_score_adj is at it's limit of 1000. TGS cannot guarantee the kill order of its parent/child processes!"); + if (baselineOomAdjust == 1000) + if (originalOomAdjust != baselineOomAdjust) + logger.LogWarning("oom_score_adj is at it's limit of 1000 (Clamped from {original}). TGS cannot guarantee the kill order of its parent/child processes!", originalOomAdjust); + else + logger.LogWarning("oom_score_adj is at it's limit of 1000. TGS cannot guarantee the kill order of its parent/child processes!"); try { From dc193f39d0b48986e373208e76f53f5dd7b8f01a Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 4 Mar 2024 14:18:14 -0500 Subject: [PATCH 095/137] Document why this sleep is here --- .github/workflows/ci-pipeline.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index d136649c08..5ac90a2d70 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -1970,8 +1970,10 @@ jobs: shell: powershell run: build/package/winget/push_manifest.ps1 + - name: Delay 10m to allow MS bot to update PR + shell: powershell + run: Sleep 600 + - name: Run ReleaseNotes with --link-winget shell: powershell - run: | - Sleep 600 - dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --link-winget ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --link-winget ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} From f1a42092822cf46e1cba8ee9814c2d33126f6053 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 11 Mar 2024 13:23:45 -0400 Subject: [PATCH 096/137] Update README.md with new IIS steps --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f535174118..34277f44a1 100644 --- a/README.md +++ b/README.md @@ -408,15 +408,15 @@ _NOTE: Your reverse proxy setup may interfere with SSE (Server-Sent Events) whic #### IIS (Reccommended for Windows) 1. Acquire an HTTPS certificate. The easiet free way for Windows is [win-acme](https://github.com/PKISharp/win-acme) (requires you to set up the website first) -2. Install the [Web Platform Installer](https://www.microsoft.com/web/downloads/platform.aspx) -3. Open the web platform installer in the IIS Manager and install the Application Request Routing 3.0 module -4. Create a new website, bind it to HTTPS only with your chosen certificate and exposed port. The physical path won't matter since it won't be used. Use `Require Server Name Indication` if you want to limit requests to a specific URL prefix. Do not use the same port as the one TGS is running on. -5. Close and reopen the IIS Manager -5. Open the site and navigate to the `URL Rewrite` module -6. In the `Actions` Pane on the right click `Add Rule(s)...` -7. For the rule template, select `Reverse Proxy` under `Inbound and Outbound Rules` and click `OK` -8. You may get a prompt about enabling proxy functionality. Click `OK` -9. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK` +1. Install the [URL Rewrite Module](https://www.iis.net/downloads/microsoft/url-rewrite) +1. Install the [Application Request Routing Module](https://www.iis.net/downloads/microsoft/application-request-routing) +1. Create a new website, bind it to HTTPS only with your chosen certificate and exposed port. The physical path won't matter since it won't be used. Use `Require Server Name Indication` if you want to limit requests to a specific URL prefix. Do not use the same port as the one TGS is running on. +1. Close and reopen the IIS Manager +1. Open the site and navigate to the `URL Rewrite` module +1. In the `Actions` Pane on the right click `Add Rule(s)...` +1. For the rule template, select `Reverse Proxy` under `Inbound and Outbound Rules` and click `OK` +1. You may get a prompt about enabling proxy functionality. Click `OK` +1. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK` #### Caddy (Reccommended for Linux, or those unfamilar with configuring NGINX or Apache) From 597f7d3263d50172b06a7685bf3ed8a3a4e5d15d Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 11 Mar 2024 13:31:45 -0400 Subject: [PATCH 097/137] `http://` is incorrect here apparently --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34277f44a1..ac54b0fde5 100644 --- a/README.md +++ b/README.md @@ -416,7 +416,7 @@ _NOTE: Your reverse proxy setup may interfere with SSE (Server-Sent Events) whic 1. In the `Actions` Pane on the right click `Add Rule(s)...` 1. For the rule template, select `Reverse Proxy` under `Inbound and Outbound Rules` and click `OK` 1. You may get a prompt about enabling proxy functionality. Click `OK` -1. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK` +1. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK` #### Caddy (Reccommended for Linux, or those unfamilar with configuring NGINX or Apache) From 431a886d971bb82baed938237ac2fc8a04ba3c10 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 13 Mar 2024 18:52:16 -0400 Subject: [PATCH 098/137] Dependency updates --- build/TestCommon.props | 2 +- .../Tgstation.Server.Client.csproj | 4 ++-- .../Tgstation.Server.Host.Service.csproj | 2 +- .../.config/dotnet-tools.json | 2 +- .../Tgstation.Server.Host.csproj | 18 +++++++++--------- .../Tgstation.Server.Host.Tests.csproj | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build/TestCommon.props b/build/TestCommon.props index add8e10ac6..30fef503c8 100644 --- a/build/TestCommon.props +++ b/build/TestCommon.props @@ -3,7 +3,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 5cff1a09a8..8b98ec2894 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.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 54710f6b47..5dd0bd9553 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Tgstation.Server.Host/.config/dotnet-tools.json b/src/Tgstation.Server.Host/.config/dotnet-tools.json index d9b689bb64..c6670e9f58 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.2", + "version": "8.0.3", "commands": [ "dotnet-ef" ] diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 04df231d71..f503f5894e 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -78,23 +78,23 @@ - + - + - + - + - + runtime; build; native; contentfiles; analyzers; buildtransitive - + - + @@ -114,7 +114,7 @@ - + @@ -128,7 +128,7 @@ - + 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 87023f4cfd..bb8eadbb25 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 @@ - + From 87f8777a433ef2eed1e7981ad0a0c0c93a4b4112 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 20 Mar 2024 17:29:07 -0400 Subject: [PATCH 099/137] More Nuget updates --- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index b7e14a249b..67843b79d0 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.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index f503f5894e..ddd44ec835 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -76,7 +76,7 @@ - + @@ -104,7 +104,7 @@ - + From 3dcf3b071accc513487f09e21834018c7ea6de2e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 20 Mar 2024 17:31:42 -0400 Subject: [PATCH 100/137] Update winget deployment template --- tools/Tgstation.Server.ReleaseNotes/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs index d5ac69e564..dff25a27c6 100644 --- a/tools/Tgstation.Server.ReleaseNotes/Program.cs +++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs @@ -809,7 +809,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 = "bec143988b4b8ddeb586ed97aaf0647803110d98"; + const string BodyForPRSha = "b64a9a24ec6b13c819b47304625a88864c3872e0"; 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. @@ -817,10 +817,12 @@ This pull request was generated by our [deployment pipeline]({actionUrl}) as a r The user account that created this pull request is available to correct any issues. - [x] Have you signed the [Contributor License Agreement](https://cla.opensource.microsoft.com/microsoft/winget-pkgs)? +- [x] Is there a linked Issue? + - Shouldn't be possible as this release was just created. - [x] Have you checked that there aren't other open [pull requests](https://github.com/microsoft/winget-pkgs/pulls) for the same manifest update/change? - This PR is generated as a direct result of a new release of `tgstation-server` this should be impossible - [x] This PR only modifies one (1) manifest -- [x] Have you [validated](https://github.com/microsoft/winget-pkgs/blob/master/AUTHORING_MANIFESTS.md#validation) your manifest locally with `winget validate --manifest `? +- [x] Have you [validated](https://github.com/microsoft/winget-pkgs/blob/master/doc/Authoring.md#validation) your manifest locally with `winget validate --manifest `? - 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. From 35f9051d5d5da8e2d855ccc26ede4b66622d1af6 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 20 Mar 2024 18:23:13 -0400 Subject: [PATCH 101/137] Generate logos in C# Fuck fucking JS, node, and yarn --- .../Tgstation.Server.Common.csproj | 11 +---- src/Tgstation.Server.Common/build_logo.js | 48 ------------------- tgstation-server.sln | 20 +++++++- .../Tgstation.Server.LogoGenerator/Program.cs | 34 +++++++++++++ .../Tgstation.Server.LogoGenerator.csproj | 14 ++++++ 5 files changed, 69 insertions(+), 58 deletions(-) delete mode 100644 src/Tgstation.Server.Common/build_logo.js create mode 100644 tools/Tgstation.Server.LogoGenerator/Program.cs create mode 100644 tools/Tgstation.Server.LogoGenerator/Tgstation.Server.LogoGenerator.csproj diff --git a/src/Tgstation.Server.Common/Tgstation.Server.Common.csproj b/src/Tgstation.Server.Common/Tgstation.Server.Common.csproj index b77bdc4dc1..631b5f35d4 100644 --- a/src/Tgstation.Server.Common/Tgstation.Server.Common.csproj +++ b/src/Tgstation.Server.Common/Tgstation.Server.Common.csproj @@ -16,20 +16,13 @@ - - - + - + - - - - - diff --git a/src/Tgstation.Server.Common/build_logo.js b/src/Tgstation.Server.Common/build_logo.js deleted file mode 100644 index c79a46903c..0000000000 --- a/src/Tgstation.Server.Common/build_logo.js +++ /dev/null @@ -1,48 +0,0 @@ -// Prereq packages: svg-to-ico@1.0.14 svg2img@1.0.0-beta.2 -// Usage: node ./build_logo.js -// Generates ../../artifacts/tgs.ico and ../../artifacts/tgs.ico - -const svg_to_img = require("svg-to-ico"); -const svg2img = require('svg2img'); -const fs = require('fs'); -const { exit } = require("process"); -if (!fs.existsSync("../../artifacts")) { - fs.mkdirSync("../../artifacts",'0777', true); -} - -const svg_bytes = fs.readFileSync("../../build/logo.svg"); -const svg = svg_bytes.toString(); -const white_bg_svg = svg - .replace(" - + From cbc9353fcbe4af842c2b509825af74c2f4efc60c Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 22 Mar 2024 10:07:13 -0400 Subject: [PATCH 106/137] Add missing libgdiplus dependency --- .github/CONTRIBUTING.md | 2 +- .github/workflows/ci-pipeline.yml | 6 +++--- build/Dockerfile | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 48b19a8566..2935d961a7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -34,7 +34,7 @@ You can of course, as always, ask for help at [#coderbus](irc://irc.rizon.net/co ### Development Environment -You need the .NET 8.0 SDK, node>=v20, and npm>=v5.7 (in your PATH) to compile the server. +You need the .NET 8.0 SDK, node>=v20, and npm>=v5.7 (in your PATH) to compile the server. On Linux, you also need the `libgdiplus` package installed to generate icons. The recommended IDE is Visual Studio 2022 or VSCode. diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 5db44392bb..cd3a842591 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -385,7 +385,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386 + sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386 libgdiplus - name: Setup dotnet uses: actions/setup-dotnet@v4 @@ -726,7 +726,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 + sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 libgdiplus - name: Setup dotnet uses: actions/setup-dotnet@v4 @@ -1130,7 +1130,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y -o APT::Immediate-Configure=0 libstdc++6:i386 libgcc-s1:i386 gnupg2 xmlstarlet + sudo apt-get install -y -o APT::Immediate-Configure=0 libstdc++6:i386 libgcc-s1:i386 gnupg2 xmlstarlet libgdiplus - name: Import GPG Key if: (github.event_name == 'push' && contains(github.event.head_commit.message, '[TGSDeploy]') && (github.event.ref == 'refs/heads/master' || github.event.ref == 'refs/heads/dev')) diff --git a/build/Dockerfile b/build/Dockerfile index 383df77bfb..cba330156e 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -16,6 +16,7 @@ RUN . $NVM_DIR/nvm.sh \ && apt-get update \ && apt-get install -y \ dos2unix \ + libgdiplus \ && rm -rf /var/lib/apt/lists/* # Build web control panel From 289fae897e0f047db4d3b41359b9ec2ebb6547c4 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 23 Mar 2024 19:59:08 -0400 Subject: [PATCH 107/137] Update DMAPI License year --- src/DMAPI/tgs.dm | 2 +- src/DMAPI/tgs/LICENSE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index a4fb6d40be..df856f6ba0 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -510,7 +510,7 @@ /* The MIT License -Copyright (c) 2017-2023 Jordan Brown +Copyright (c) 2017-2024 Jordan Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and diff --git a/src/DMAPI/tgs/LICENSE b/src/DMAPI/tgs/LICENSE index 2bedf9a63a..324c48e993 100644 --- a/src/DMAPI/tgs/LICENSE +++ b/src/DMAPI/tgs/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2017-2023 Jordan Brown +Copyright (c) 2017-2024 Jordan Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and From 99718467da14e589f3d2f0528cd5225d34f5ede3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 24 Mar 2024 12:37:27 -0400 Subject: [PATCH 108/137] Fix proc must call parent linter errors on goon --- build/Version.props | 2 +- src/DMAPI/tgs.dm | 7 ++++++- src/DMAPI/tgs/core/datum.dm | 2 +- src/DMAPI/tgs/core/tgs_version.dm | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/build/Version.props b/build/Version.props index 391a9e4ec8..0c4150c5d9 100644 --- a/build/Version.props +++ b/build/Version.props @@ -9,7 +9,7 @@ 7.0.0 13.2.0 15.2.0 - 7.1.1 + 7.1.2 5.9.0 1.4.1 1.2.1 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index df856f6ba0..e2c89df90e 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "7.1.1" +#define TGS_DMAPI_VERSION "7.1.2" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -312,6 +312,7 @@ var/datum/tgs_chat_embed/structure/embed /datum/tgs_message_content/New(text) + ..() if(!istext(text)) TGS_ERROR_LOG("[/datum/tgs_message_content] created with no text!") text = null @@ -354,6 +355,7 @@ var/proxy_url /datum/tgs_chat_embed/media/New(url) + ..() if(!istext(url)) CRASH("[/datum/tgs_chat_embed/media] created with no url!") @@ -367,6 +369,7 @@ var/proxy_icon_url /datum/tgs_chat_embed/footer/New(text) + ..() if(!istext(text)) CRASH("[/datum/tgs_chat_embed/footer] created with no text!") @@ -383,6 +386,7 @@ var/proxy_icon_url /datum/tgs_chat_embed/provider/author/New(name) + ..() if(!istext(name)) CRASH("[/datum/tgs_chat_embed/provider/author] created with no name!") @@ -395,6 +399,7 @@ var/is_inline /datum/tgs_chat_embed/field/New(name, value) + ..() if(!istext(name)) CRASH("[/datum/tgs_chat_embed/field] created with no name!") diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index 898516f124..f734fd0527 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -7,7 +7,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) var/list/warned_deprecated_command_runs /datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version) - . = ..() + ..() src.event_handler = event_handler src.version = version diff --git a/src/DMAPI/tgs/core/tgs_version.dm b/src/DMAPI/tgs/core/tgs_version.dm index a5dae1241a..bc561e6748 100644 --- a/src/DMAPI/tgs/core/tgs_version.dm +++ b/src/DMAPI/tgs/core/tgs_version.dm @@ -1,4 +1,5 @@ /datum/tgs_version/New(raw_parameter) + ..() src.raw_parameter = raw_parameter deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "") var/list/version_bits = splittext(deprefixed_parameter, ".") From 22765614ada35784c1e0feabd10db51292158d4e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 25 Mar 2024 17:00:24 -0400 Subject: [PATCH 109/137] Steal the rerun flaky tests script From https://github.com/tgstation/tgstation/blob/4f41277de989e573af881566620fc87edd7394d9 --- .github/workflows/rerunFlakyTests.yml | 20 ++++++++ .github/workflows/scripts/rerunFlakyTests.js | 49 ++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 .github/workflows/rerunFlakyTests.yml create mode 100644 .github/workflows/scripts/rerunFlakyTests.js diff --git a/.github/workflows/rerunFlakyTests.yml b/.github/workflows/rerunFlakyTests.yml new file mode 100644 index 0000000000..2a18960a1c --- /dev/null +++ b/.github/workflows/rerunFlakyTests.yml @@ -0,0 +1,20 @@ +name: Rerun Flaky Live Tests +on: + workflow_run: + workflows: [CI Pipeline] + types: + - completed +jobs: + rerun_flaky_tests: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.run_attempt == 1 }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Rerun flaky tests + uses: actions/github-script@v6 + with: + script: | + const { rerunFlakyTests } = await import('${{ github.workspace }}/.github/workflows/scripts/rerunFlakyTests.js') + await rerunFlakyTests({ github, context }) diff --git a/.github/workflows/scripts/rerunFlakyTests.js b/.github/workflows/scripts/rerunFlakyTests.js new file mode 100644 index 0000000000..3e7630f8f6 --- /dev/null +++ b/.github/workflows/scripts/rerunFlakyTests.js @@ -0,0 +1,49 @@ +// Only check jobs that start with these. +// Helps make sure we don't restart something like which is not known to be flaky. +const CONSIDERED_JOBS = [ + "Windows Live Tests", + "Linux Live Tests", +]; + +async function getFailedJobsForRun(github, context, workflowRunId, runAttempt) { + const { + data: { jobs }, + } = await github.rest.actions.listJobsForWorkflowRunAttempt({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: workflowRunId, + attempt_number: runAttempt, + }); + + return jobs + .filter((job) => job.conclusion === "failure") + .filter((job) => + CONSIDERED_JOBS.some((title) => job.name.startsWith(title)) + ); +} + +export async function rerunFlakyTests({ github, context }) { + const failingJobs = await getFailedJobsForRun( + github, + context, + context.payload.workflow_run.id, + context.payload.workflow_run.run_attempt + ); + + if (failingJobs.length > 1) { + console.log("Multiple jobs failing. PROBABLY not flaky, not rerunning."); + return; + } + + if (failingJobs.length === 0) { + throw new Error( + "rerunFlakyTests should not have run on a run with no failing jobs" + ); + } + + github.rest.actions.reRunWorkflowFailedJobs({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); +} From 6745c2f79255028c84c49986afac05138d1cce76 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 25 Mar 2024 17:04:21 -0400 Subject: [PATCH 110/137] Make master merge depend on CI Pipeline This should fix the auto-integration issues --- .github/workflows/stable-merge.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stable-merge.yml b/.github/workflows/stable-merge.yml index bd4d64d09f..384b8791e2 100644 --- a/.github/workflows/stable-merge.yml +++ b/.github/workflows/stable-merge.yml @@ -1,9 +1,10 @@ name: 'Master Merge' on: - push: - branches: - - master + workflow_run: + workflows: [CI Pipeline] + types: + - completed jobs: master-merge: From 979e4d81f24b1da5f63d4f459ea01b6722b3e282 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 25 Mar 2024 19:56:02 -0400 Subject: [PATCH 111/137] Rename rerun yml --- .github/workflows/{rerunFlakyTests.yml => rerun-flaky-tests.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{rerunFlakyTests.yml => rerun-flaky-tests.yml} (100%) diff --git a/.github/workflows/rerunFlakyTests.yml b/.github/workflows/rerun-flaky-tests.yml similarity index 100% rename from .github/workflows/rerunFlakyTests.yml rename to .github/workflows/rerun-flaky-tests.yml From ea2c5dc05861b012a4d44b11cf01d7bdd932fbc2 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 25 Mar 2024 19:57:14 -0400 Subject: [PATCH 112/137] Only trigger master merge on successful CI --- .github/workflows/stable-merge.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/stable-merge.yml b/.github/workflows/stable-merge.yml index 384b8791e2..54ce84245b 100644 --- a/.github/workflows/stable-merge.yml +++ b/.github/workflows/stable-merge.yml @@ -8,9 +8,8 @@ on: jobs: master-merge: - runs-on: ubuntu-latest - + if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Checkout uses: actions/checkout@v4 From d4886eb5e4744d546edc4f511789704b716a4b8d Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 26 Mar 2024 20:35:02 -0400 Subject: [PATCH 113/137] Fix chat help commands not working --- src/Tgstation.Server.Host/Components/Chat/ChatManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index d53e997b25..049c217efb 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -839,7 +839,7 @@ namespace Tgstation.Server.Host.Components.Chat splits.RemoveAt(0); var arguments = String.Join(" ", splits); - Tuple? GetCommand() + Tuple? GetCommand(string command) { if (!builtinCommands.TryGetValue(command, out var handler)) return trackingContexts @@ -867,7 +867,7 @@ namespace Tgstation.Server.Host.Components.Chat } else { - var helpTuple = GetCommand(); + var helpTuple = GetCommand(splits[0]); if (helpTuple != default) { var (helpHandler, _) = helpTuple; @@ -881,7 +881,7 @@ namespace Tgstation.Server.Host.Components.Chat return; } - var tuple = GetCommand(); + var tuple = GetCommand(command); if (tuple == default) { From 1f89791ff150cf1e18a830e00ded1cc842bd8bc3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Tue, 26 Mar 2024 20:35:47 -0400 Subject: [PATCH 114/137] Version bump to 6.4.1 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 391a9e4ec8..56b991f7d3 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.4.0 + 6.4.1 5.1.0 10.2.0 7.0.0 From 19c856b2a53fdceb1fee562e78c8985b473cf996 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 28 Mar 2024 09:22:35 -0400 Subject: [PATCH 115/137] Add name to rerun-flaky-tests job --- .github/workflows/rerun-flaky-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rerun-flaky-tests.yml b/.github/workflows/rerun-flaky-tests.yml index 2a18960a1c..522e1120cc 100644 --- a/.github/workflows/rerun-flaky-tests.yml +++ b/.github/workflows/rerun-flaky-tests.yml @@ -6,6 +6,7 @@ on: - completed jobs: rerun_flaky_tests: + name: Rerun Flaky Tests runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.run_attempt == 1 }} steps: From 27b5499fbb2bb9c1a8a5de3b45c8e3c82c7df7d5 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Thu, 28 Mar 2024 09:23:24 -0400 Subject: [PATCH 116/137] Add missing package.json --- .github/workflows/scripts/package.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/workflows/scripts/package.json diff --git a/.github/workflows/scripts/package.json b/.github/workflows/scripts/package.json new file mode 100644 index 0000000000..bedb411a91 --- /dev/null +++ b/.github/workflows/scripts/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} From 69c83582cbdc50f0115f2433b784dfbc7c3baca7 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 10:09:51 -0400 Subject: [PATCH 117/137] Package updates --- build/TestCommon.props | 4 ++-- .../Tgstation.Server.Api.csproj | 2 +- .../Tgstation.Server.Client.csproj | 4 ++-- .../.config/dotnet-tools.json | 2 +- .../Tgstation.Server.Host.csproj | 20 +++++++++---------- .../Tgstation.Server.Host.Tests.csproj | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/build/TestCommon.props b/build/TestCommon.props index 30fef503c8..c932f8919d 100644 --- a/build/TestCommon.props +++ b/build/TestCommon.props @@ -18,9 +18,9 @@ - + - + diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 67843b79d0..502f74b255 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 8b98ec2894..f085719f84 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 c6670e9f58..8e82e30018 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.3", + "version": "8.0.4", "commands": [ "dotnet-ef" ] diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index ddd44ec835..a2afbf98de 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -72,29 +72,29 @@ - + - + - + - + - + - + runtime; build; native; contentfiles; analyzers; buildtransitive - + - + @@ -102,7 +102,7 @@ - + @@ -128,7 +128,7 @@ - + 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 bb8eadbb25..a179871b32 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 @@ - + From 80b50a442651fa328e44147cbbb25817d15ef092 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 10:34:36 -0400 Subject: [PATCH 118/137] Make `IdentityCacheObject` `IAsyncDisposable` Closes #1733 --- .../Controllers/ApiRootController.cs | 2 +- .../Security/IIdentityCache.cs | 4 +- .../Security/IdentityCache.cs | 53 +++++++++++-------- .../Security/IdentityCacheObject.cs | 16 +++--- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs index fa6b3e0c4b..9bf56a0e4d 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs @@ -357,7 +357,7 @@ namespace Tgstation.Server.Host.Controllers var identExpiry = token.ParseJwt().ValidTo; identExpiry += tokenFactory.ValidationParameters.ClockSkew; identExpiry += TimeSpan.FromSeconds(15); - identityCache.CacheSystemIdentity(user, systemIdentity!, identExpiry); + await identityCache.CacheSystemIdentity(user, systemIdentity!, identExpiry); } Logger.LogDebug("Successfully logged in user {userId}!", user.Id); diff --git a/src/Tgstation.Server.Host/Security/IIdentityCache.cs b/src/Tgstation.Server.Host/Security/IIdentityCache.cs index 75010f2c64..4a74ed15ac 100644 --- a/src/Tgstation.Server.Host/Security/IIdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IIdentityCache.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Tgstation.Server.Host.Models; @@ -15,7 +16,8 @@ namespace Tgstation.Server.Host.Security /// The the belongs to. /// The to cache. /// When the should expire. - void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry); + /// A representing the running operation. + ValueTask CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry); /// /// Attempt to load a cached . diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs index c42ffab3f6..1b40269101 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -1,16 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.Security { /// - sealed class IdentityCache : IIdentityCache, IDisposable + sealed class IdentityCache : IIdentityCache, IAsyncDisposable { /// /// The for the . @@ -41,15 +43,14 @@ namespace Tgstation.Server.Host.Security } /// - public void Dispose() + public ValueTask DisposeAsync() { logger.LogTrace("Disposing..."); - foreach (var cachedIdentity in cachedIdentities.Select(x => x.Value).ToList()) - cachedIdentity.Dispose(); + return ValueTaskExtensions.WhenAll(cachedIdentities.Select(x => x.Value.DisposeAsync())); } /// - public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry) + public async ValueTask CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry) { ArgumentNullException.ThrowIfNull(user); ArgumentNullException.ThrowIfNull(systemIdentity); @@ -57,27 +58,35 @@ namespace Tgstation.Server.Host.Security var uid = user.Require(x => x.Id); var sysId = systemIdentity.Uid; - lock (cachedIdentities) + ValueTask oldIdentityDisposal = ValueTask.CompletedTask; + try { - logger.LogDebug("Caching system identity {sysId} of user {uid}", sysId, uid); - - if (cachedIdentities.TryGetValue(uid, out var identCache)) + lock (cachedIdentities) { - logger.LogTrace("Expiring previously cached identity..."); - identCache.Dispose(); // also clears it out - } + logger.LogDebug("Caching system identity {sysId} of user {uid}", sysId, uid); - identCache = new IdentityCacheObject( - systemIdentity.Clone(), - asyncDelayer, - () => + if (cachedIdentities.TryGetValue(uid, out var identCache)) { - logger.LogDebug("Expiring system identity cache for user {uid}", uid); - lock (cachedIdentities) - cachedIdentities.Remove(uid); - }, - expiry); - cachedIdentities.Add(uid, identCache); + logger.LogTrace("Expiring previously cached identity..."); + oldIdentityDisposal = identCache.DisposeAsync(); // also clears it out + } + + identCache = new IdentityCacheObject( + systemIdentity.Clone(), + asyncDelayer, + () => + { + logger.LogDebug("Expiring system identity cache for user {uid}", uid); + lock (cachedIdentities) + cachedIdentities.Remove(uid); + }, + expiry); + cachedIdentities.Add(uid, identCache); + } + } + finally + { + await oldIdentityDisposal; } } diff --git a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs index 7cc10664e5..af0e57dbd1 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Security /// /// For keeping a specific alive for a period of time. /// - sealed class IdentityCacheObject : IDisposable + sealed class IdentityCacheObject : IAsyncDisposable { /// /// The the manages. @@ -53,6 +53,9 @@ namespace Tgstation.Server.Host.Security { await asyncDelayer.Delay(expiry - now, cancellationToken); } + catch (OperationCanceledException) + { + } finally { onExpiry(); @@ -63,18 +66,11 @@ namespace Tgstation.Server.Host.Security } /// - public void Dispose() + public async ValueTask DisposeAsync() { cancellationTokenSource.Cancel(); - try - { - task.GetAwaiter().GetResult(); - } - catch (OperationCanceledException) - { - } - cancellationTokenSource.Dispose(); + await task; } } } From 6f23cdc67f576b3e62be9a39715f78c075fb23fc Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 10:35:07 -0400 Subject: [PATCH 119/137] Closes #1732 --- .../Extensions/WebHostBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs index e6815cd7a0..9c210856a1 100644 --- a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs @@ -53,7 +53,7 @@ namespace Tgstation.Server.Host.Extensions /// Configures a given . /// /// The to configure. - private static void ConfigureApplication(IApplicationBuilder applicationBuilder) + static void ConfigureApplication(IApplicationBuilder applicationBuilder) => applicationBuilder .ApplicationServices .GetRequiredService() From 01b0ad26120b292ce97a35b2bbf1ad8bda46a7bf Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 10:35:53 -0400 Subject: [PATCH 120/137] Closes #1731 --- src/Tgstation.Server.Host/Utils/FifoSemaphore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Utils/FifoSemaphore.cs b/src/Tgstation.Server.Host/Utils/FifoSemaphore.cs index 0bb696680a..a00d6dba5c 100644 --- a/src/Tgstation.Server.Host/Utils/FifoSemaphore.cs +++ b/src/Tgstation.Server.Host/Utils/FifoSemaphore.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Utils /// /// to represent a ticket in the and whether or not it is . /// - class FifoSemaphoreTicket + sealed class FifoSemaphoreTicket { /// /// Set if the wait operation on a was cancelled to avoid clogging the queue. From afb6192b3e8d002f2128391ee0202f99cf309632 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 11:19:11 -0400 Subject: [PATCH 121/137] Add repo reference to CompileStart event Closes #1356 --- .../Components/Deployment/DreamMaker.cs | 2 ++ src/Tgstation.Server.Host/Components/Events/EventType.cs | 2 +- .../Components/Repository/Repository.cs | 5 +++++ tests/Tgstation.Server.Tests/TestRepository.cs | 3 +++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 27107f7ff8..de540bff62 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -571,6 +571,7 @@ namespace Tgstation.Server.Host.Components.Deployment progressReporter.StageName = "Copying repository"; var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory); var repoOrigin = repository.Origin; + var repoReference = repository.Reference; using (repository) await repository.CopyTo(resolvedOutputDirectory, cancellationToken); @@ -585,6 +586,7 @@ namespace Tgstation.Server.Host.Components.Deployment resolvedOutputDirectory, repoOrigin.ToString(), engineLock.Version.ToString(), + repoReference, }, true, cancellationToken); diff --git a/src/Tgstation.Server.Host/Components/Events/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs index 4872998c61..5d8c0b9054 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -55,7 +55,7 @@ EngineActiveVersionChange, /// - /// After the repo is copied, before CodeModifications are applied. Parameters: Game directory path, origin commit sha, engine version string. + /// After the repo is copied, before CodeModifications are applied. Parameters: Game directory path, origin commit sha, engine version string, repository reference (or "(no branch)" if there is no reference). /// [EventScript("PreCompile")] CompileStart, diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 2a362ff90c..972fae31cc 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -46,6 +46,11 @@ namespace Tgstation.Server.Host.Components.Repository /// public const string RemoteTemporaryBranchName = "___TGSTempBranch"; + /// + /// The value of when not on a reference. + /// + public const string NoReference = "(no branch)"; + /// /// Used when a reference cannot be determined. /// diff --git a/tests/Tgstation.Server.Tests/TestRepository.cs b/tests/Tgstation.Server.Tests/TestRepository.cs index a9b3ca4305..e73731de40 100644 --- a/tests/Tgstation.Server.Tests/TestRepository.cs +++ b/tests/Tgstation.Server.Tests/TestRepository.cs @@ -46,6 +46,9 @@ namespace Tgstation.Server.Tests const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a"; await repo.CheckoutObject(StartSha, null, null, true, new JobProgressReporter(Mock.Of>(), null, (stage, progress) => { }), CancellationToken.None); + + Assert.AreEqual(Host.Components.Repository.Repository.NoReference, repo.Reference); + var result = await repo.CommittishIsParent("2f8588a3ca0f6b027704a2a04381215619de3412", CancellationToken.None); Assert.IsTrue(result); Assert.AreEqual(StartSha, repo.Head); From d1f61dd63332efca15101c40545169a0b92586c3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 11:20:58 -0400 Subject: [PATCH 122/137] Cleanup a message --- .../Components/Repository/TestRepositoryFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs b/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs index 04e7cdb66b..84aea24ef6 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Repository.Tests [TestClass] public sealed class TestRepositoryFactory { - static ILibGit2RepositoryFactory CreateFactory() => new LibGit2RepositoryFactory(Mock.Of>()); + static LibGit2RepositoryFactory CreateFactory() => new (Mock.Of>()); static async Task TestRepoLoading( string path, From 94ca8d38d1c03b73748a6dc6d546fb3d8ebe1cd9 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 11:43:42 -0400 Subject: [PATCH 123/137] MySQL insists on a bunch of migration changes --- ...3929_MYNormalizeVersionUpdates.Designer.cs | 1150 +++++++++++++++++ ...0240420153929_MYNormalizeVersionUpdates.cs | 329 +++++ .../MySqlDatabaseContextModelSnapshot.cs | 38 +- 3 files changed, 1516 insertions(+), 1 deletion(-) create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.Designer.cs new file mode 100644 index 0000000000..982497eff8 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.Designer.cs @@ -0,0 +1,1150 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20240420153929_MYNormalizeVersionUpdates")] + partial class MYNormalizeVersionUpdates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ConnectionString"), "utf8mb4"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("IrcChannel"), "utf8mb4"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Tag"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("DmeName"), "utf8mb4"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("EngineVersion"), "utf8mb4"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Output"), "utf8mb4"); + + b.Property("RepositoryOrigin") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("RepositoryOrigin"), "utf8mb4"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AdditionalParameters"), "utf8mb4"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Port") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ProjectName"), "utf8mb4"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("Online") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Path"), "utf8mb4"); + + b.Property("SwarmIdentifer") + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SwarmIdentifer"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("bigint unsigned"); + + b.Property("ConfigurationRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamDaemonRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamMakerRights") + .HasColumnType("bigint unsigned"); + + b.Property("EngineRights") + .HasColumnType("bigint unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("bigint unsigned"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("bigint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("bigint unsigned"); + + b.Property("CancelRightsType") + .HasColumnType("bigint unsigned"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Description"), "utf8mb4"); + + b.Property("ErrorCode") + .HasColumnType("int unsigned"); + + b.Property("ExceptionDetails") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExceptionDetails"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint unsigned"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExternalUserId"), "utf8mb4"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("bigint unsigned"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("bigint unsigned"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessIdentifier"), "utf8mb4"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("smallint unsigned"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("smallint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessToken"), "utf8mb4"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessUser"), "utf8mb4"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterEmail"), "utf8mb4"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterName"), "utf8mb4"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitSha"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("OriginCommitSha"), "utf8mb4"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Author"), "utf8mb4"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("BodyAtMerge"), "utf8mb4"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Comment"), "utf8mb4"); + + b.Property("MergedAt") + .HasColumnType("datetime(6)"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TargetCommitSha"), "utf8mb4"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TitleAtMerge"), "utf8mb4"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Url"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CanonicalName"), "utf8mb4"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("PasswordHash"), "utf8mb4"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SystemIdentifier"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs new file mode 100644 index 0000000000..20c8b57c4b --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs @@ -0,0 +1,329 @@ +using System; + +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MYNormalizeVersionUpdates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Users", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "TestMerges", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "RevisionInformations", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "RevInfoTestMerges", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "RepositorySettings", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "ReattachInformations", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "PermissionSets", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "OAuthConnections", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Jobs", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Instances", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "InstancePermissionSets", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Groups", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "DreamMakerSettings", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "DreamDaemonSettings", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "CompileJobs", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "ChatChannels", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "ChatBots", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Users", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "TestMerges", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "RevisionInformations", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "RevInfoTestMerges", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "RepositorySettings", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "ReattachInformations", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "PermissionSets", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "OAuthConnections", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Jobs", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Instances", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "InstancePermissionSets", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Groups", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "DreamMakerSettings", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "DreamDaemonSettings", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "CompileJobs", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "ChatChannels", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "ChatBots", + type: "bigint", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index e0449a4ac3..e366e038aa 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -13,15 +13,19 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("ProductVersion", "8.0.4") .HasAnnotation("Relational:MaxIdentifierLength", 64); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ChannelLimit") .IsRequired() .HasColumnType("smallint unsigned"); @@ -65,6 +69,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ChatSettingsId") .HasColumnType("bigint"); @@ -116,6 +122,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("DMApiMajorVersion") .HasColumnType("int"); @@ -185,6 +193,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AdditionalParameters") .IsRequired() .HasMaxLength(10000) @@ -259,6 +269,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ApiValidationPort") .IsRequired() .HasColumnType("smallint unsigned"); @@ -297,6 +309,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AutoUpdateInterval") .IsRequired() .HasColumnType("int unsigned"); @@ -344,6 +358,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ChatBotRights") .HasColumnType("bigint unsigned"); @@ -387,6 +403,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("CancelRight") .HasColumnType("bigint unsigned"); @@ -447,6 +465,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ExternalUserId") .IsRequired() .HasMaxLength(100) @@ -476,6 +496,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AdministrationRights") .HasColumnType("bigint unsigned"); @@ -505,6 +527,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AccessIdentifier") .IsRequired() .HasColumnType("longtext"); @@ -550,6 +574,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AccessToken") .HasMaxLength(10000) .HasColumnType("longtext"); @@ -621,6 +647,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("RevisionInformationId") .HasColumnType("bigint"); @@ -642,6 +670,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("CommitSha") .IsRequired() .HasMaxLength(40) @@ -676,6 +706,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Author") .IsRequired() .HasColumnType("longtext"); @@ -742,6 +774,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("CanonicalName") .IsRequired() .HasMaxLength(100) @@ -805,6 +839,8 @@ namespace Tgstation.Server.Host.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("bigint"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Name") .IsRequired() .HasMaxLength(100) From 3da68fe323b341e4136c84c970b5b29f8f3d53ce Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 12:20:28 -0400 Subject: [PATCH 124/137] Add support for additional compiler arguments Closes #1807 --- build/Version.props | 6 +- .../Models/Internal/DreamMakerSettings.cs | 7 + .../Rights/DreamMakerRights.cs | 5 + .../Components/Deployment/DreamMaker.cs | 11 +- .../Components/Engine/ByondInstallation.cs | 11 +- .../Components/Engine/EngineExecutableLock.cs | 2 +- .../Engine/EngineInstallationBase.cs | 2 +- .../Components/Engine/IEngineInstallation.cs | 3 +- .../Engine/OpenDreamInstallation.cs | 11 +- .../Controllers/DreamMakerController.cs | 21 +- .../Controllers/InstanceController.cs | 1 + .../Database/DatabaseContext.cs | 18 +- ...AddCompilerAdditionalArguments.Designer.cs | 1084 ++++++++++++++++ ...154501_MSAddCompilerAdditionalArguments.cs | 33 + ...AddCompilerAdditionalArguments.Designer.cs | 1154 +++++++++++++++++ ...154509_MYAddCompilerAdditionalArguments.cs | 34 + ...AddCompilerAdditionalArguments.Designer.cs | 1078 +++++++++++++++ ...154517_PGAddCompilerAdditionalArguments.cs | 33 + ...AddCompilerAdditionalArguments.Designer.cs | 1050 +++++++++++++++ ...154525_SLAddCompilerAdditionalArguments.cs | 33 + .../MySqlDatabaseContextModelSnapshot.cs | 4 + ...PostgresSqlDatabaseContextModelSnapshot.cs | 6 +- .../SqlServerDatabaseContextModelSnapshot.cs | 6 +- .../SqliteDatabaseContextModelSnapshot.cs | 6 +- .../Models/DreamMakerSettings.cs | 1 + .../Live/Instance/DeploymentTest.cs | 9 +- 26 files changed, 4606 insertions(+), 23 deletions(-) create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.cs diff --git a/build/Version.props b/build/Version.props index ba966518a4..edecebd5a2 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,10 +5,10 @@ 6.4.1 5.1.0 - 10.2.0 + 10.3.0 7.0.0 - 13.2.0 - 15.2.0 + 13.3.0 + 15.3.0 7.1.2 5.9.0 1.4.1 diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs index d6c229e673..c7a11c1b3b 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -39,5 +39,12 @@ namespace Tgstation.Server.Api.Models.Internal /// [Required] public TimeSpan? Timeout { get; set; } + + /// + /// Additional arguments added to the compiler command line. + /// + [StringLength(Limits.MaximumStringLength)] + [ResponseOptions] + public string? CompilerAdditionalArguments { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index c8852ce338..6e7e882dd4 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -57,5 +57,10 @@ namespace Tgstation.Server.Api.Rights /// User may modify . /// SetTimeout = 1 << 8, + + /// + /// User may modify . + /// + SetCompilerArguments = 1 << 9, } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index de540bff62..76c415bac4 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -632,7 +632,7 @@ namespace Tgstation.Server.Host.Components.Deployment // run compiler progressReporter.StageName = "Running Compiler"; - var compileSuceeded = await RunDreamMaker(engineLock, job, cancellationToken); + var compileSuceeded = await RunDreamMaker(engineLock, job, dreamMakerSettings.CompilerAdditionalArguments, cancellationToken); // Session takes ownership of the lock and Disposes it so save this for later var engineVersion = engineLock.Version; @@ -850,12 +850,17 @@ namespace Tgstation.Server.Host.Components.Deployment /// /// The to use. /// The for the operation. + /// Additional arguments to be added to the compiler. /// The for the operation. /// A resulting in if compilation succeeded, otherwise. - async ValueTask RunDreamMaker(IEngineExecutableLock engineLock, Models.CompileJob job, CancellationToken cancellationToken) + async ValueTask RunDreamMaker( + IEngineExecutableLock engineLock, + Models.CompileJob job, + string? additionalCompilerArguments, + CancellationToken cancellationToken) { var environment = await engineLock.LoadEnv(logger, true, cancellationToken); - var arguments = engineLock.FormatCompilerArguments($"{job.DmeName}.{DmeExtension}"); + var arguments = engineLock.FormatCompilerArguments($"{job.DmeName}.{DmeExtension}", additionalCompilerArguments); await using var dm = await processExecutor.LaunchProcess( engineLock.CompilerExePath, diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs index b3ddd1cc1a..8ee095babc 100644 --- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs @@ -147,7 +147,14 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public override string FormatCompilerArguments(string dmePath) - => $"-clean \"{dmePath ?? throw new ArgumentNullException(nameof(dmePath))}\""; + public override string FormatCompilerArguments(string dmePath, string? additionalArguments) + { + if (String.IsNullOrWhiteSpace(additionalArguments)) + additionalArguments = String.Empty; + else + additionalArguments = $"{additionalArguments.Trim()} "; + + return $"-clean {additionalArguments}\"{dmePath ?? throw new ArgumentNullException(nameof(dmePath))}\""; + } } } diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs index 3136e10aef..3589d6c4af 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs @@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Components.Engine logFilePath); /// - public string FormatCompilerArguments(string dmePath) => Instance.FormatCompilerArguments(dmePath); + public string FormatCompilerArguments(string dmePath, string? additionalArguments) => Instance.FormatCompilerArguments(dmePath, additionalArguments); /// public ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs index 22c1c14987..928f8e3006 100644 --- a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs +++ b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs @@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public abstract string FormatCompilerArguments(string dmePath); + public abstract string FormatCompilerArguments(string dmePath, string? additionalArguments); /// public abstract string FormatServerArguments( diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs index bdcfe2bf90..1d467a2881 100644 --- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs @@ -74,8 +74,9 @@ namespace Tgstation.Server.Host.Components.Engine /// Return the command line arguments for compiling a given if compilation is necessary. /// /// The full path to the .dme to compile. + /// Optional additional arguments provided to the compiler. /// The formatted arguments . - string FormatCompilerArguments(string dmePath); + string FormatCompilerArguments(string dmePath, string? additionalArguments); /// /// Kills a given engine server . diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs index c522b9cedd..49c5ed1470 100644 --- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs +++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs @@ -112,8 +112,15 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public override string FormatCompilerArguments(string dmePath) - => $"--suppress-unimplemented --notices-enabled \"{dmePath ?? throw new ArgumentNullException(nameof(dmePath))}\""; + public override string FormatCompilerArguments(string dmePath, string? additionalArguments) + { + if (String.IsNullOrWhiteSpace(additionalArguments)) + additionalArguments = String.Empty; + else + additionalArguments = $"{additionalArguments.Trim()} "; + + return $"--suppress-unimplemented --notices-enabled {additionalArguments}\"{dmePath ?? throw new ArgumentNullException(nameof(dmePath))}\""; + } /// public override async ValueTask StopServerProcess( diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 8b4cfce563..22325779c7 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -171,7 +171,8 @@ namespace Tgstation.Server.Host.Controllers | DreamMakerRights.SetApiValidationPort | DreamMakerRights.SetSecurityLevel | DreamMakerRights.SetApiValidationRequirement - | DreamMakerRights.SetTimeout)] + | DreamMakerRights.SetTimeout + | DreamMakerRights.SetCompilerArguments)] [ProducesResponseType(typeof(DreamMakerResponse), 200)] [ProducesResponseType(204)] [ProducesResponseType(typeof(ErrorMessageResponse), 410)] @@ -196,7 +197,8 @@ namespace Tgstation.Server.Host.Controllers { if (!dreamMakerRights.HasFlag(DreamMakerRights.SetDme)) return Forbid(); - if (model.ProjectName.Length == 0) + + if (model.ProjectName.Length == 0) // can't use isnullorwhitespace because linux memes hostModel.ProjectName = null; else hostModel.ProjectName = model.ProjectName; @@ -230,6 +232,7 @@ namespace Tgstation.Server.Host.Controllers { if (!dreamMakerRights.HasFlag(DreamMakerRights.SetSecurityLevel)) return Forbid(); + hostModel.ApiValidationSecurityLevel = model.ApiValidationSecurityLevel; } @@ -237,6 +240,7 @@ namespace Tgstation.Server.Host.Controllers { if (!dreamMakerRights.HasFlag(DreamMakerRights.SetApiValidationRequirement)) return Forbid(); + hostModel.RequireDMApiValidation = model.RequireDMApiValidation; } @@ -244,9 +248,22 @@ namespace Tgstation.Server.Host.Controllers { if (!dreamMakerRights.HasFlag(DreamMakerRights.SetTimeout)) return Forbid(); + hostModel.Timeout = model.Timeout; } + if (model.CompilerAdditionalArguments != null) + { + if (!dreamMakerRights.HasFlag(DreamMakerRights.SetCompilerArguments)) + return Forbid(); + + var sanitizedArguments = model.CompilerAdditionalArguments.Trim(); + if (sanitizedArguments.Length == 0) + hostModel.CompilerAdditionalArguments = null; + else + hostModel.CompilerAdditionalArguments = sanitizedArguments; + } + await DatabaseContext.Save(cancellationToken); if (!dreamMakerRights.HasFlag(DreamMakerRights.Read)) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index cbc0f802dd..e74d4fdce1 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -740,6 +740,7 @@ namespace Tgstation.Server.Host.Controllers ApiValidationSecurityLevel = DreamDaemonSecurity.Safe, RequireDMApiValidation = true, Timeout = TimeSpan.FromHours(1), + CompilerAdditionalArguments = null, }, Name = initialSettings.Name, Online = false, diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 7dd49e7f2e..d4fb8fe084 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -375,22 +375,22 @@ namespace Tgstation.Server.Host.Database /// /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. /// - internal static readonly Type MSLatestMigration = typeof(MSAddMinidumpsOption); + internal static readonly Type MSLatestMigration = typeof(MSAddCompilerAdditionalArguments); /// /// Used by unit tests to remind us to setup the correct MYSQL migration downgrades. /// - internal static readonly Type MYLatestMigration = typeof(MYAddMinidumpsOption); + internal static readonly Type MYLatestMigration = typeof(MYAddCompilerAdditionalArguments); /// /// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades. /// - internal static readonly Type PGLatestMigration = typeof(PGAddMinidumpsOption); + internal static readonly Type PGLatestMigration = typeof(PGAddCompilerAdditionalArguments); /// /// Used by unit tests to remind us to setup the correct SQLite migration downgrades. /// - internal static readonly Type SLLatestMigration = typeof(SLAddMinidumpsOption); + internal static readonly Type SLLatestMigration = typeof(SLAddCompilerAdditionalArguments); /// #pragma warning disable CA1502 // Cyclomatic complexity @@ -419,6 +419,16 @@ namespace Tgstation.Server.Host.Database string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); + if (targetVersion < new Version(6, 5, 0)) + targetMigration = currentDatabaseType switch + { + DatabaseType.MySql => nameof(MYAddMinidumpsOption), + DatabaseType.PostgresSql => nameof(PGAddMinidumpsOption), + DatabaseType.SqlServer => nameof(MSAddMinidumpsOption), + DatabaseType.Sqlite => nameof(SLAddMinidumpsOption), + _ => BadDatabaseType(), + }; + if (targetVersion < new Version(6, 2, 0)) targetMigration = currentDatabaseType switch { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.Designer.cs new file mode 100644 index 0000000000..904833311b --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.Designer.cs @@ -0,0 +1,1084 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20240420154501_MSAddCompilerAdditionalArguments")] + partial class MSAddCompilerAdditionalArguments + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .HasColumnType("int"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uniqueidentifier"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RepositoryOrigin") + .HasColumnType("nvarchar(max)"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("HealthCheckSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("bit"); + + b.Property("MapThreads") + .HasColumnType("bigint"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("bit"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .HasColumnType("int"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("int"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Online") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("SwarmIdentifer") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique() + .HasFilter("[SwarmIdentifer] IS NOT NULL"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("decimal(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("EngineRights") + .HasColumnType("decimal(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("decimal(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("decimal(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("decimal(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("decimal(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique() + .HasFilter("[GroupId] IS NOT NULL"); + + b.HasIndex("UserId") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("bit"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("bit"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("bit"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("MergedAt") + .HasColumnType("datetimeoffset"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique() + .HasFilter("[SystemIdentifier] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.cs new file mode 100644 index 0000000000..9ac1be9d78 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154501_MSAddCompilerAdditionalArguments.cs @@ -0,0 +1,33 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MSAddCompilerAdditionalArguments : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AddColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings", + type: "nvarchar(max)", + maxLength: 10000, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.Designer.cs new file mode 100644 index 0000000000..17a818166e --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.Designer.cs @@ -0,0 +1,1154 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20240420154509_MYAddCompilerAdditionalArguments")] + partial class MYAddCompilerAdditionalArguments + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ConnectionString"), "utf8mb4"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("IrcChannel"), "utf8mb4"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Tag"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("DmeName"), "utf8mb4"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("EngineVersion"), "utf8mb4"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Output"), "utf8mb4"); + + b.Property("RepositoryOrigin") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("RepositoryOrigin"), "utf8mb4"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AdditionalParameters"), "utf8mb4"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Port") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("varchar(10000)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ProjectName"), "utf8mb4"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("Online") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Path"), "utf8mb4"); + + b.Property("SwarmIdentifer") + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SwarmIdentifer"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("bigint unsigned"); + + b.Property("ConfigurationRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamDaemonRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamMakerRights") + .HasColumnType("bigint unsigned"); + + b.Property("EngineRights") + .HasColumnType("bigint unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("bigint unsigned"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("bigint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("bigint unsigned"); + + b.Property("CancelRightsType") + .HasColumnType("bigint unsigned"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Description"), "utf8mb4"); + + b.Property("ErrorCode") + .HasColumnType("int unsigned"); + + b.Property("ExceptionDetails") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExceptionDetails"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint unsigned"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExternalUserId"), "utf8mb4"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("bigint unsigned"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("bigint unsigned"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessIdentifier"), "utf8mb4"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("smallint unsigned"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("smallint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessToken"), "utf8mb4"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessUser"), "utf8mb4"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterEmail"), "utf8mb4"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterName"), "utf8mb4"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitSha"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("OriginCommitSha"), "utf8mb4"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Author"), "utf8mb4"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("BodyAtMerge"), "utf8mb4"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Comment"), "utf8mb4"); + + b.Property("MergedAt") + .HasColumnType("datetime(6)"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TargetCommitSha"), "utf8mb4"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TitleAtMerge"), "utf8mb4"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Url"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CanonicalName"), "utf8mb4"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("PasswordHash"), "utf8mb4"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SystemIdentifier"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.cs new file mode 100644 index 0000000000..b5065bfc99 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154509_MYAddCompilerAdditionalArguments.cs @@ -0,0 +1,34 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MYAddCompilerAdditionalArguments : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AddColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings", + type: "varchar(10000)", + maxLength: 10000, + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.Designer.cs new file mode 100644 index 0000000000..f28e016375 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.Designer.cs @@ -0,0 +1,1078 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(PostgresSqlDatabaseContext))] + [Migration("20240420154517_PGAddCompilerAdditionalArguments")] + partial class PGAddCompilerAdditionalArguments + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .HasColumnType("integer"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("integer"); + + b.Property("DMApiMinorVersion") + .HasColumnType("integer"); + + b.Property("DMApiPatchVersion") + .HasColumnType("integer"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("GitHubDeploymentId") + .HasColumnType("integer"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("integer"); + + b.Property("Output") + .IsRequired() + .HasColumnType("text"); + + b.Property("RepositoryOrigin") + .HasColumnType("text"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("HealthCheckSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("MapThreads") + .HasColumnType("bigint"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("SecurityLevel") + .HasColumnType("integer"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .HasColumnType("integer"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("integer"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("interval"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("integer"); + + b.Property("ConfigurationType") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Online") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SwarmIdentifer") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("numeric(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("EngineRights") + .HasColumnType("numeric(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("numeric(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("numeric(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("numeric(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("text"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("smallint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("integer"); + + b.Property("LaunchVisibility") + .HasColumnType("integer"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ProcessId") + .HasColumnType("integer"); + + b.Property("RebootState") + .HasColumnType("integer"); + + b.Property("TopicPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("MergedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.cs new file mode 100644 index 0000000000..6fe29b918b --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154517_PGAddCompilerAdditionalArguments.cs @@ -0,0 +1,33 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class PGAddCompilerAdditionalArguments : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AddColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings", + type: "character varying(10000)", + maxLength: 10000, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.Designer.cs new file mode 100644 index 0000000000..51b37d7870 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.Designer.cs @@ -0,0 +1,1050 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20240420154525_SLAddCompilerAdditionalArguments")] + partial class SLAddCompilerAdditionalArguments + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatSettingsId") + .HasColumnType("INTEGER"); + + b.Property("DiscordChannelId") + .HasColumnType("INTEGER"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DMApiMajorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiMinorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiPatchVersion") + .HasColumnType("INTEGER"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GitHubDeploymentId") + .HasColumnType("INTEGER"); + + b.Property("GitHubRepoId") + .HasColumnType("INTEGER"); + + b.Property("JobId") + .HasColumnType("INTEGER"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("Output") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RepositoryOrigin") + .HasColumnType("TEXT"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Port") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("SecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Visibility") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConfigurationType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Online") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SwarmIdentifer") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatBotRights") + .HasColumnType("INTEGER"); + + b.Property("ConfigurationRights") + .HasColumnType("INTEGER"); + + b.Property("DreamDaemonRights") + .HasColumnType("INTEGER"); + + b.Property("DreamMakerRights") + .HasColumnType("INTEGER"); + + b.Property("EngineRights") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("INTEGER"); + + b.Property("PermissionSetId") + .HasColumnType("INTEGER"); + + b.Property("RepositoryRights") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CancelRight") + .HasColumnType("INTEGER"); + + b.Property("CancelRightsType") + .HasColumnType("INTEGER"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CancelledById") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ErrorCode") + .HasColumnType("INTEGER"); + + b.Property("ExceptionDetails") + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("JobCode") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedById") + .HasColumnType("INTEGER"); + + b.Property("StoppedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdministrationRights") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("InstanceManagerRights") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CompileJobId") + .HasColumnType("INTEGER"); + + b.Property("InitialCompileJobId") + .HasColumnType("INTEGER"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("LaunchVisibility") + .HasColumnType("INTEGER"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ProcessId") + .HasColumnType("INTEGER"); + + b.Property("RebootState") + .HasColumnType("INTEGER"); + + b.Property("TopicPort") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.Property("TestMergeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("MergedAt") + .HasColumnType("TEXT"); + + b.Property("MergedById") + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordUpdate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.cs new file mode 100644 index 0000000000..5115fed881 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420154525_SLAddCompilerAdditionalArguments.cs @@ -0,0 +1,33 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class SLAddCompilerAdditionalArguments : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AddColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings", + type: "TEXT", + maxLength: 10000, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "CompilerAdditionalArguments", + table: "DreamMakerSettings"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index e366e038aa..7022822362 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -278,6 +278,10 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("ApiValidationSecurityLevel") .HasColumnType("int"); + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("varchar(10000)"); + b.Property("InstanceId") .HasColumnType("bigint"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs index b27d3f8e50..2202d8c234 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("ProductVersion", "8.0.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -254,6 +254,10 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("ApiValidationSecurityLevel") .HasColumnType("integer"); + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + b.Property("InstanceId") .HasColumnType("bigint"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs index 6477433e00..29cf386275 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("ProductVersion", "8.0.4") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -256,6 +256,10 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("ApiValidationSecurityLevel") .HasColumnType("int"); + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + b.Property("InstanceId") .HasColumnType("bigint"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs index ce2169101a..3af9f50ca5 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.1"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => { @@ -248,6 +248,10 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("ApiValidationSecurityLevel") .HasColumnType("INTEGER"); + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + b.Property("InstanceId") .HasColumnType("INTEGER"); diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs index 72f5a99f29..33a93e49bd 100644 --- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs @@ -31,6 +31,7 @@ namespace Tgstation.Server.Host.Models ApiValidationSecurityLevel = ApiValidationSecurityLevel, RequireDMApiValidation = RequireDMApiValidation, Timeout = Timeout, + CompilerAdditionalArguments = CompilerAdditionalArguments, }; } } diff --git a/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs b/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs index 28485a7732..a158ed4edf 100644 --- a/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs +++ b/tests/Tgstation.Server.Tests/Live/Instance/DeploymentTest.cs @@ -125,17 +125,24 @@ namespace Tgstation.Server.Tests.Live.Instance { ProjectName = "tests/DMAPI/ApiFree/api_free", ApiValidationPort = dmPort, + CompilerAdditionalArguments = " ", }, cancellationToken); Assert.AreEqual(dmPort, updatedDM.ApiValidationPort); Assert.AreEqual("tests/DMAPI/ApiFree/api_free", updatedDM.ProjectName); + Assert.IsNull(updatedDM.CompilerAdditionalArguments); } else { var updatedDM = await dreamMakerClient.Update(new DreamMakerRequest { - ApiValidationPort = dmPort + ApiValidationPort = dmPort, + CompilerAdditionalArguments = testEngine == EngineType.Byond ? " -DBABABOOEY" : " ", }, cancellationToken); Assert.AreEqual(dmPort, updatedDM.ApiValidationPort); + if (testEngine == EngineType.Byond) + Assert.AreEqual("-DBABABOOEY", updatedDM.CompilerAdditionalArguments); + else + Assert.IsNull(updatedDM.CompilerAdditionalArguments); } Console.WriteLine($"PORT REUSE BUG 1: Setting I-{instanceClient.Metadata.Id} DD to {ddPort}"); From 53a10a6810c416cefeb2b6b86f3e8935302ee271 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 12:21:40 -0400 Subject: [PATCH 125/137] Version bump to 6.5.0 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index edecebd5a2..8f252fdae9 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 6.4.1 + 6.5.0 5.1.0 10.3.0 7.0.0 From 2b2c8f6a72b6340a36b26acf68bfe0b2a0e24095 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 17:52:43 -0400 Subject: [PATCH 126/137] This is bugging and I think we can get away without de-annotating here --- ...0240420153929_MYNormalizeVersionUpdates.cs | 154 ------------------ 1 file changed, 154 deletions(-) diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs b/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs index 20c8b57c4b..81cd88a047 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/20240420153929_MYNormalizeVersionUpdates.cs @@ -170,160 +170,6 @@ namespace Tgstation.Server.Host.Database.Migrations /// protected override void Down(MigrationBuilder migrationBuilder) { - ArgumentNullException.ThrowIfNull(migrationBuilder); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Users", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "TestMerges", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "RevisionInformations", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "RevInfoTestMerges", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "RepositorySettings", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "ReattachInformations", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "PermissionSets", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "OAuthConnections", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Jobs", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Instances", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "InstancePermissionSets", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Groups", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "DreamMakerSettings", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "DreamDaemonSettings", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "CompileJobs", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "ChatChannels", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.AlterColumn( - name: "Id", - table: "ChatBots", - type: "bigint", - nullable: false, - oldClrType: typeof(long), - oldType: "bigint") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); } } } From e98da28130db21c082fd560af247a438af42cc91 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 17:55:42 -0400 Subject: [PATCH 127/137] Update redistributable URL --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 8f252fdae9..8bbf9544b6 100644 --- a/build/Version.props +++ b/build/Version.props @@ -17,7 +17,7 @@ netstandard2.0 8 - https://download.visualstudio.microsoft.com/download/pr/98ff0a08-a283-428f-8e54-19841d97154c/8c7d5f9600eadf264f04c82c813b7aab/dotnet-hosting-8.0.2-win.exe + https://download.visualstudio.microsoft.com/download/pr/00397fee-1bd9-44ef-899b-4504b26e6e96/ab9c73409659f3238d33faee304a8b7c/dotnet-hosting-8.0.4-win.exe 10.11.6 1.22.21 From 153032153d49f70db435d4a8dc55cb33257dea53 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 18:00:45 -0400 Subject: [PATCH 128/137] Fix rerunFlakyTests --- .github/workflows/scripts/rerunFlakyTests.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/scripts/rerunFlakyTests.js b/.github/workflows/scripts/rerunFlakyTests.js index 3e7630f8f6..dd7e653756 100644 --- a/.github/workflows/scripts/rerunFlakyTests.js +++ b/.github/workflows/scripts/rerunFlakyTests.js @@ -16,10 +16,7 @@ async function getFailedJobsForRun(github, context, workflowRunId, runAttempt) { }); return jobs - .filter((job) => job.conclusion === "failure") - .filter((job) => - CONSIDERED_JOBS.some((title) => job.name.startsWith(title)) - ); + .filter((job) => job.conclusion === "failure"); } export async function rerunFlakyTests({ github, context }) { @@ -35,12 +32,14 @@ export async function rerunFlakyTests({ github, context }) { return; } - if (failingJobs.length === 0) { - throw new Error( - "rerunFlakyTests should not have run on a run with no failing jobs" - ); + const filteredFailingJobs = failingJobs.filter((job) => CONSIDERED_JOBS.some((title) => job.name.startsWith(title))); + if (filteredFailingJobs.length === 0) { + console.log("Failing jobs are NOT designated flaky. Not rerunning."); + return; } + console.log(`Rerunning job: ${filteredFailingJobs[0].name}`); + github.rest.actions.reRunWorkflowFailedJobs({ owner: context.repo.owner, repo: context.repo.repo, From c9e6afd09b26d25f474c1575e669cb420d9a02f6 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sat, 20 Apr 2024 21:23:25 -0400 Subject: [PATCH 129/137] Rerun flaky tests fixes --- .github/workflows/scripts/rerunFlakyTests.js | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/scripts/rerunFlakyTests.js b/.github/workflows/scripts/rerunFlakyTests.js index dd7e653756..17a9281085 100644 --- a/.github/workflows/scripts/rerunFlakyTests.js +++ b/.github/workflows/scripts/rerunFlakyTests.js @@ -3,17 +3,21 @@ const CONSIDERED_JOBS = [ "Windows Live Tests", "Linux Live Tests", + "Build .deb Package" ]; async function getFailedJobsForRun(github, context, workflowRunId, runAttempt) { - const { - data: { jobs }, - } = await github.rest.actions.listJobsForWorkflowRunAttempt({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: workflowRunId, - attempt_number: runAttempt, - }); + const jobs = await github.paginate( + github.actions.listJobsForWorkflowRunAttempt, + { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: workflowRunId, + attempt_number: runAttempt + }, + response => { + return response.data.jobs; + }); return jobs .filter((job) => job.conclusion === "failure"); @@ -32,7 +36,10 @@ export async function rerunFlakyTests({ github, context }) { return; } - const filteredFailingJobs = failingJobs.filter((job) => CONSIDERED_JOBS.some((title) => job.name.startsWith(title))); + const filteredFailingJobs = failingJobs.filter((job) => { + console.log(`Failing job: ${job.name}`) + return CONSIDERED_JOBS.some((title) => job.name.startsWith(title)); + }); if (filteredFailingJobs.length === 0) { console.log("Failing jobs are NOT designated flaky. Not rerunning."); return; From 7a9496594224af326c786f0d103f88649c25944a Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 01:15:15 -0400 Subject: [PATCH 130/137] Fix rerun flaky tests --- .github/workflows/scripts/rerunFlakyTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/rerunFlakyTests.js b/.github/workflows/scripts/rerunFlakyTests.js index 17a9281085..955cc2aa09 100644 --- a/.github/workflows/scripts/rerunFlakyTests.js +++ b/.github/workflows/scripts/rerunFlakyTests.js @@ -8,7 +8,7 @@ const CONSIDERED_JOBS = [ async function getFailedJobsForRun(github, context, workflowRunId, runAttempt) { const jobs = await github.paginate( - github.actions.listJobsForWorkflowRunAttempt, + github.rest.actions.listJobsForWorkflowRunAttempt, { owner: context.repo.owner, repo: context.repo.repo, From fb2a2b2ce1c7fffdd880c36efa40453c2e87a75e Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 10:23:14 -0400 Subject: [PATCH 131/137] Add manpages installation. Add `tgs-configure` manpage Closes #1801 --- build/package/deb/install_artifacts.sh | 1 + build/package/deb/man/man1/tgs-configure.1 | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 build/package/deb/man/man1/tgs-configure.1 diff --git a/build/package/deb/install_artifacts.sh b/build/package/deb/install_artifacts.sh index dead938fa2..47e355981a 100755 --- a/build/package/deb/install_artifacts.sh +++ b/build/package/deb/install_artifacts.sh @@ -1,3 +1,4 @@ #!/usr/bin/sh -e cd artifacts && for f in $(find * -type f); do install -D "$f" "$1/opt/tgstation-server/$f"; done +cd ../build/package/deb/man && for f in $(find * -type f); do install -D "$f" "$1/usr/local/man/$f"; done diff --git a/build/package/deb/man/man1/tgs-configure.1 b/build/package/deb/man/man1/tgs-configure.1 new file mode 100644 index 0000000000..a100936d70 --- /dev/null +++ b/build/package/deb/man/man1/tgs-configure.1 @@ -0,0 +1,16 @@ +.TH TGS-CONFIGURE 1 +.SH NAME +tgs-configure \- tgstation-server interactive configuration file generator +.SH SYNOPSIS +.B tgs-configure +.SH DESCRIPTION +.B tgs-configure +.SH OPTIONS +The +.B tgs-configure + command does not take any options. +modifies the tgstation-server configuration file stored in /etc/tgstation-server/appsettings.Production.yml. +.SH BUGS +See issue tracker at https://github.com/tgstation/tgstation-server/issues. +.SH AUTHOR +Jordan Dominion (Cyberboss@users.noreply.github.com) From 7b336a19a25954723fb37c71fb207d5895494c1b Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 10:35:47 -0400 Subject: [PATCH 132/137] Add missing `libgdiplus` install to .deb package script --- build/package/deb/build_package.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/package/deb/build_package.sh b/build/package/deb/build_package.sh index 0732f96486..1a25546da8 100755 --- a/build/package/deb/build_package.sh +++ b/build/package/deb/build_package.sh @@ -19,7 +19,8 @@ apt-get install -y \ ca-certificates \ curl \ gnupg \ - xmlstarlet + xmlstarlet \ + libgdiplus declare repo_version=$(if command -v lsb_release &> /dev/null; then lsb_release -r -s; else grep -oP '(?<=^VERSION_ID=).+' /etc/os-release | tr -d '"'; fi) curl -L https://packages.microsoft.com/config/ubuntu/$repo_version/packages-microsoft-prod.deb -o packages-microsoft-prod.deb From 01dfda32ccfdf26a0a14f124abb8c4c66890c2d2 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 13:07:20 -0400 Subject: [PATCH 133/137] Improve manpages. Adjust description wording --- README.md | 2 +- build/package/deb/man/man1/tgs-configure.1 | 2 ++ build/package/deb/man/man7/tgstation-server.7 | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 build/package/deb/man/man7/tgstation-server.7 diff --git a/README.md b/README.md index ac54b0fde5..92b8f72509 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) -This is a toolset to manage production BYOND servers. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server), the ability to start the server and restart it if it crashes, as well as systems for managing code and game files, and locally merging GitHub Pull Requests for test deployments. +This is a toolset to manage production DreamMaker servers. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server), the ability to start the server and restart it if it crashes, as well as systems for managing code and game files, and locally merging GitHub Pull Requests for test deployments. ## Setup diff --git a/build/package/deb/man/man1/tgs-configure.1 b/build/package/deb/man/man1/tgs-configure.1 index a100936d70..43c8564684 100644 --- a/build/package/deb/man/man1/tgs-configure.1 +++ b/build/package/deb/man/man1/tgs-configure.1 @@ -10,6 +10,8 @@ The .B tgs-configure command does not take any options. modifies the tgstation-server configuration file stored in /etc/tgstation-server/appsettings.Production.yml. +.SH SEE ALSO +tgstation-server(7) .SH BUGS See issue tracker at https://github.com/tgstation/tgstation-server/issues. .SH AUTHOR diff --git a/build/package/deb/man/man7/tgstation-server.7 b/build/package/deb/man/man7/tgstation-server.7 new file mode 100644 index 0000000000..8f3127ecc5 --- /dev/null +++ b/build/package/deb/man/man7/tgstation-server.7 @@ -0,0 +1,11 @@ +.TH TGSTATION-SERVER 7 +.SH NAME +tgstation-server \- A production scale tool for DreamMaker server management +.SH DESCRIPTION +This is a toolset to manage production DreamMaker servers. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server), the ability to start the server and restart it if it crashes, as well as systems for managing code and game files, and locally merging GitHub Pull Requests for test deployments. +.SH SEE ALSO +tgs-configure(1) +.SH BUGS +See issue tracker at https://github.com/tgstation/tgstation-server/issues. +.SH AUTHOR +Jordan Dominion (Cyberboss@users.noreply.github.com) From 40d69f3b0cb1c25dd07aba75a26c7d7db03d9c91 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 13:59:06 -0400 Subject: [PATCH 134/137] Fix manpages installation --- build/package/deb/{man/man1 => debian/man}/tgs-configure.1 | 0 build/package/deb/{man/man7 => debian/man}/tgstation-server.7 | 0 build/package/deb/debian/manpages | 2 ++ build/package/deb/install_artifacts.sh | 1 - 4 files changed, 2 insertions(+), 1 deletion(-) rename build/package/deb/{man/man1 => debian/man}/tgs-configure.1 (100%) rename build/package/deb/{man/man7 => debian/man}/tgstation-server.7 (100%) create mode 100644 build/package/deb/debian/manpages diff --git a/build/package/deb/man/man1/tgs-configure.1 b/build/package/deb/debian/man/tgs-configure.1 similarity index 100% rename from build/package/deb/man/man1/tgs-configure.1 rename to build/package/deb/debian/man/tgs-configure.1 diff --git a/build/package/deb/man/man7/tgstation-server.7 b/build/package/deb/debian/man/tgstation-server.7 similarity index 100% rename from build/package/deb/man/man7/tgstation-server.7 rename to build/package/deb/debian/man/tgstation-server.7 diff --git a/build/package/deb/debian/manpages b/build/package/deb/debian/manpages new file mode 100644 index 0000000000..b78c157959 --- /dev/null +++ b/build/package/deb/debian/manpages @@ -0,0 +1,2 @@ +debian/man/tgs-configure.1 +debian/man/tgstation-server.7 diff --git a/build/package/deb/install_artifacts.sh b/build/package/deb/install_artifacts.sh index 47e355981a..dead938fa2 100755 --- a/build/package/deb/install_artifacts.sh +++ b/build/package/deb/install_artifacts.sh @@ -1,4 +1,3 @@ #!/usr/bin/sh -e cd artifacts && for f in $(find * -type f); do install -D "$f" "$1/opt/tgstation-server/$f"; done -cd ../build/package/deb/man && for f in $(find * -type f); do install -D "$f" "$1/usr/local/man/$f"; done From 281b5069cfb7365ab4c31a0d6b37f66c44cd0ba5 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 16:14:25 -0400 Subject: [PATCH 135/137] Update webpanel version --- build/WebpanelVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/WebpanelVersion.props b/build/WebpanelVersion.props index 1d18faa135..6ce3ba7841 100644 --- a/build/WebpanelVersion.props +++ b/build/WebpanelVersion.props @@ -1,6 +1,6 @@ - 5.6.0 + 5.7.1 From a91915f104edb52c75b1054d905d56af614c53fa Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Sun, 21 Apr 2024 18:43:36 -0400 Subject: [PATCH 136/137] Fix flaky tests rerun again --- .github/workflows/scripts/rerunFlakyTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/rerunFlakyTests.js b/.github/workflows/scripts/rerunFlakyTests.js index 955cc2aa09..5bacef1278 100644 --- a/.github/workflows/scripts/rerunFlakyTests.js +++ b/.github/workflows/scripts/rerunFlakyTests.js @@ -16,7 +16,7 @@ async function getFailedJobsForRun(github, context, workflowRunId, runAttempt) { attempt_number: runAttempt }, response => { - return response.data.jobs; + return response.data; }); return jobs From df0cc1dce349dbef107443b7bd4f867f203b6200 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 26 Apr 2024 21:33:31 -0400 Subject: [PATCH 137/137] Note in README about vc140 dependency --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 92b8f72509..5f9246c5ab 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,11 @@ This is a toolset to manage production DreamMaker servers. It includes the abili ### Pre-Requisites -_Note: If you opt to use the Windows installer, all pre-requisites for running BYOND servers (including MariaDB) are provided out of the box. If you wish to use OpenDream you will need to install the required dotnet SDK manually._ +_Note: If you opt to use the Windows installer, most pre-requisites for running BYOND servers (including MariaDB) are provided out of the box._ + +_If you are running on a Windows Server OS. You **might** need to install the [x86 Visual C++ 2015 Runtime](https://aka.ms/vs/17/release/vc_redist.x86.exe) to run BYOND._ + +_If you wish to use OpenDream you will need to install the required dotnet SDK manually._ tgstation-server needs a relational database to store it's data.