From 6bffd99712decd1b24a1a5d545930e1f69a3344c Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 16:33:11 -0400 Subject: [PATCH 01/32] Linting: Remove unnecessary privates --- src/Tgstation.Server.Host/Components/InstanceFactory.cs | 2 +- src/Tgstation.Server.Host/Components/InstanceManager.cs | 2 +- src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs | 2 +- src/Tgstation.Server.Host/Controllers/ChatController.cs | 2 +- src/Tgstation.Server.Host/Controllers/JobController.cs | 2 +- src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index b1d9764321..5a78706831 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -385,6 +385,6 @@ namespace Tgstation.Server.Host.Components /// /// Test that the is functional. /// - private void CheckSystemCompatibility() => repositoryFactory.CreateInMemory(); + void CheckSystemCompatibility() => repositoryFactory.CreateInMemory(); } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 9235da553c..d776370de2 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -538,7 +538,7 @@ namespace Tgstation.Server.Host.Components /// /// Check we have a valid system identity. /// - private void CheckSystemCompatibility() + void CheckSystemCompatibility() { using (var systemIdentity = systemIdentityFactory.GetCurrent()) { diff --git a/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs index 86641b8944..9d0a18f656 100644 --- a/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/SessionConfiguration.cs @@ -13,7 +13,7 @@ /// /// The default value for . /// - private const bool DefaultHighPriorityLiveDreamDaemon = true; + const bool DefaultHighPriorityLiveDreamDaemon = true; /// /// If the public DreamDaemon instances are set to be above normal priority processes. diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 27227b1f81..21c5c72627 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -385,7 +385,7 @@ namespace Tgstation.Server.Host.Controllers /// The to validate. /// If the is being created. /// An to respond with or . - private IActionResult StandardModelChecks(ChatBotApiBase model, bool forCreation) + IActionResult StandardModelChecks(ChatBotApiBase model, bool forCreation) { if (model.ReconnectionInterval == 0) throw new InvalidOperationException("RecconnectionInterval cannot be zero!"); diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 70c01ee82d..d67c7204b7 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -176,7 +176,7 @@ namespace Tgstation.Server.Host.Controllers /// /// The to augment. /// A representing the running operation. - private Task AddJobProgressResponseTransformer(JobResponse jobResponse) + Task AddJobProgressResponseTransformer(JobResponse jobResponse) { jobManager.SetJobProgress(jobResponse); return Task.CompletedTask; diff --git a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs index e5a2d9518a..287c963620 100644 --- a/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs +++ b/src/Tgstation.Server.Host/Core/OpenApiEnumVarNamesExtension.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Core /// Initializes a new instance of the class. /// /// The value of ,. - private OpenApiEnumVarNamesExtension(Type enumType) + OpenApiEnumVarNamesExtension(Type enumType) { this.enumType = enumType ?? throw new ArgumentNullException(nameof(enumType)); } From 40dfe54d6a51d7b5865dc5f5fc63f054a8fe2663 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 16:38:41 -0400 Subject: [PATCH 02/32] Minor speedup in DmbFactory startup --- .../Components/Deployment/DmbFactory.cs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index e2f9fd442a..82241e7c92 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -335,19 +335,24 @@ namespace Tgstation.Server.Host.Components.Deployment List jobUidsToNotErase = null; // find the uids of locked directories - await databaseContextFactory.UseContext(async db => + if (jobIdsToSkip.Any()) { - jobUidsToNotErase = (await db - .CompileJobs - .AsQueryable() - .Where( - x => x.Job.Instance.Id == metadata.Id - && jobIdsToSkip.Contains(x.Id.Value)) - .Select(x => x.DirectoryName.Value) - .ToListAsync(cancellationToken)) - .Select(x => x.ToString()) - .ToList(); - }); + await databaseContextFactory.UseContext(async db => + { + jobUidsToNotErase = (await db + .CompileJobs + .AsQueryable() + .Where( + x => x.Job.Instance.Id == metadata.Id + && jobIdsToSkip.Contains(x.Id.Value)) + .Select(x => x.DirectoryName.Value) + .ToListAsync(cancellationToken)) + .Select(x => x.ToString()) + .ToList(); + }); + } + else + jobUidsToNotErase = new List(); jobUidsToNotErase.Add(SwappableDmbProvider.LiveGameDirectory); From 3646fd148965902d1d3681fb903d1d3ca50c499c Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 17:54:28 -0400 Subject: [PATCH 03/32] Fix issue with processing empty chat messages --- src/Tgstation.Server.Host/Components/Chat/ChatManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index c57099f5a8..d57a900ef1 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -679,7 +679,11 @@ namespace Tgstation.Server.Host.Components.Chat message.User.Channel.IsAdminChannel = mappingChannelRepresentation.IsAdminChannel; } - var splits = new List(message.Content.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries)); + var trimmedMessage = message.Content.Trim(); + if (trimmedMessage.Length == 0) + return; + + var splits = new List(trimmedMessage.Split(' ', StringSplitOptions.RemoveEmptyEntries)); var address = splits[0]; if (address.Length > 1 && (address.Last() == ':' || address.Last() == ',')) address = address[0..^1]; From 7097c3e3726596851007d7993e36d165ba38160c Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 18:00:49 -0400 Subject: [PATCH 04/32] Fix another ProgressTask issue --- .../Components/Deployment/DreamMaker.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 8db34d95a8..e7fb0b81a5 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -744,7 +744,7 @@ namespace Tgstation.Server.Host.Components.Deployment do { var remainingSleepThisInterval = nextInterval - DateTimeOffset.UtcNow; - var nextSleepSpan = remainingSleepThisInterval < minimumSleepInterval ? remainingSleepThisInterval : minimumSleepInterval; + var nextSleepSpan = remainingSleepThisInterval < minimumSleepInterval ? minimumSleepInterval : remainingSleepThisInterval; await Task.Delay(nextSleepSpan, cancellationToken); progressReporter.StageName = currentStage; @@ -764,6 +764,10 @@ namespace Tgstation.Server.Host.Components.Deployment { logger.LogTrace(ex, "ProgressTask aborted."); } + catch (Exception ex) + { + logger.LogError(ex, "ProgressTask crashed!"); + } } /// From ffa8433405ea64ec7c09416d18df57f672d350ed Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 19:19:35 -0400 Subject: [PATCH 05/32] Additional tests meant to catch a WindowsWatchdog issue but they don't yet --- .../Instance/WatchdogTest.cs | 4 +- .../Tgstation.Server.Tests/IntegrationTest.cs | 62 ++++++++++++++++--- tests/Tgstation.Server.Tests/TestingServer.cs | 1 + 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 6fd138d17d..495861ee07 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -40,7 +40,7 @@ namespace Tgstation.Server.Tests.Instance // Increase startup timeout, disable heartbeats var initialSettings = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest { - StartupTimeout = 60, + StartupTimeout = 10, HeartbeatSeconds = 0, Port = IntegrationTest.DDPort }, cancellationToken); @@ -502,7 +502,7 @@ namespace Tgstation.Server.Tests.Instance return ddProc != null; } - async Task TellWorldToReboot(CancellationToken cancellationToken) + public async Task TellWorldToReboot(CancellationToken cancellationToken) { var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); var initialCompileJob = daemonStatus.ActiveCompileJob; diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 872cd9a6ad..2df4ee73b8 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -26,6 +26,7 @@ using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Client; +using Tgstation.Server.Client.Components; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Configuration; @@ -952,12 +953,8 @@ namespace Tgstation.Server.Tests preStartupTime = DateTimeOffset.UtcNow; - // chat bot start, dd autostart, and entity delete tests - serverTask = server.Run(cancellationToken); - using (var adminClient = await CreateAdminClient(server.Url, cancellationToken)) + async Task WaitForInitialJobs(IInstanceClient instanceClient) { - var instanceClient = adminClient.Instances.CreateClient(instance); - var jobs = await instanceClient.Jobs.ListActive(null, cancellationToken); if (!jobs.Any()) { @@ -970,20 +967,71 @@ namespace Tgstation.Server.Tests jobs = getTasks .Select(x => x.Result) .Where(x => x.StartedAt.Value > preStartupTime) - .ToList(); + .ToList(); } var jrt = new JobsRequiredTest(instanceClient.Jobs); foreach (var job in jobs) { Assert.IsTrue(job.StartedAt.Value >= preStartupTime); - await jrt.WaitForJob(job, 140, job.Description.Contains("Reconnect chat bot") ? (bool?)null : (bool?)false, null, cancellationToken); + await jrt.WaitForJob(job, 140, job.Description.Contains("Reconnect chat bot") ? null : false, null, cancellationToken); } + } + + // chat bot start, dd autostart, and reboot with different initial job test + preStartupTime = DateTimeOffset.UtcNow; + serverTask = server.Run(cancellationToken); + long expectedCompileJobId; + DreamDaemonResponse currentDD; + using (var adminClient = await CreateAdminClient(server.Url, cancellationToken)) + { + var instanceClient = adminClient.Instances.CreateClient(instance); + await WaitForInitialJobs(instanceClient); var dd = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); + var compileJob = await instanceClient.DreamMaker.Compile(cancellationToken); + var wdt = new WatchdogTest(instanceClient); + await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); + + currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(currentDD.StagedCompileJob.Job.Id, compileJob.Id); + + await wdt.TellWorldToReboot(cancellationToken); + expectedCompileJobId = compileJob.Id.Value; + + currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(currentDD.ActiveCompileJob.Job.Id, expectedCompileJobId); + expectedCompileJobId = currentDD.ActiveCompileJob.Id.Value; + + compileJob = await instanceClient.DreamMaker.Compile(cancellationToken); + await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); + + currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(currentDD.StagedCompileJob.Job.Id, compileJob.Id); + + await adminClient.Administration.Restart(cancellationToken); + } + + await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)); + Assert.IsTrue(serverTask.IsCompleted); + + // post/entity deletion tests + serverTask = server.Run(cancellationToken); + using (var adminClient = await CreateAdminClient(server.Url, cancellationToken)) + { + var instanceClient = adminClient.Instances.CreateClient(instance); + await WaitForInitialJobs(instanceClient); + + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); + + var lastStaged = currentDD.StagedCompileJob; + currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(currentDD.ActiveCompileJob.Id, expectedCompileJobId); + Assert.AreEqual(currentDD.StagedCompileJob?.Id, lastStaged.Id); + var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs).RunPostTest(cancellationToken); await new ChatTest(instanceClient.ChatBots, adminClient.Instances, instance).RunPostTest(cancellationToken); await repoTest; diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index 3490255754..f1a6286693 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -126,6 +126,7 @@ namespace Tgstation.Server.Tests public void Dispose() { + return; for (int i = 0; i < 5; ++i) try { From baed4daca7724df43c2e99c5657ea1639ee60d53 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 19:21:11 -0400 Subject: [PATCH 06/32] Test performance improvement --- .../Instance/WatchdogTest.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 495861ee07..bd870d3902 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -28,6 +28,8 @@ namespace Tgstation.Server.Tests.Instance { readonly IInstanceClient instanceClient; + bool ranTimeoutTest = false; + public WatchdogTest(IInstanceClient instanceClient) : base(instanceClient.Jobs) { @@ -553,13 +555,18 @@ namespace Tgstation.Server.Tests.Instance Timeout = TimeSpan.FromMilliseconds(1), }, cancellationToken); - Assert.AreEqual(deploymentSecurity, refreshed.ApiValidationSecurityLevel); - Assert.AreEqual(requireApi, refreshed.RequireDMApiValidation); - Assert.AreEqual(TimeSpan.FromMilliseconds(1), refreshed.Timeout); + JobResponse compileJobJob; + if (!ranTimeoutTest) + { + Assert.AreEqual(deploymentSecurity, refreshed.ApiValidationSecurityLevel); + Assert.AreEqual(requireApi, refreshed.RequireDMApiValidation); + Assert.AreEqual(TimeSpan.FromMilliseconds(1), refreshed.Timeout); - var compileJobJob = await instanceClient.DreamMaker.Compile(cancellationToken); + compileJobJob = await instanceClient.DreamMaker.Compile(cancellationToken); + + await WaitForJob(compileJobJob, 90, true, ErrorCode.DeploymentTimeout, cancellationToken); + } - await WaitForJob(compileJobJob, 90, true, ErrorCode.DeploymentTimeout, cancellationToken); await instanceClient.DreamMaker.Update(new DreamMakerRequest { Timeout = TimeSpan.FromMinutes(5), From 3b5614e8833842ceb7d980feb05ef1e3b5b4e1d4 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 19:53:48 -0400 Subject: [PATCH 07/32] Undo this nono --- tests/Tgstation.Server.Tests/TestingServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index f1a6286693..3490255754 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -126,7 +126,6 @@ namespace Tgstation.Server.Tests public void Dispose() { - return; for (int i = 0; i < 5; ++i) try { From 29f070f21abbfd2276c0e9dcae4617ce7cc4e043 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 20:02:00 -0400 Subject: [PATCH 08/32] More test fixes --- .../Instance/WatchdogTest.cs | 6 ++-- .../Tgstation.Server.Tests/IntegrationTest.cs | 30 ++++++++----------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index bd870d3902..e2fb6f4cd3 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -531,10 +531,10 @@ namespace Tgstation.Server.Tests.Instance do { - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + await Task.Delay(TimeSpan.FromSeconds(1), tempToken); + daemonStatus = await instanceClient.DreamDaemon.Read(tempToken); } - while (initialCompileJob.Id == daemonStatus.ActiveCompileJob.Id && !tempToken.IsCancellationRequested); + while (initialCompileJob.Id == daemonStatus.ActiveCompileJob.Id); } } catch (OperationCanceledException) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 2df4ee73b8..4ff01cd9e8 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -940,11 +940,13 @@ namespace Tgstation.Server.Tests Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); await instanceClient.DreamDaemon.Shutdown(cancellationToken); - await instanceClient.DreamDaemon.Update(new DreamDaemonRequest + dd = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest { AutoStart = true }, cancellationToken); + Assert.AreEqual(WatchdogStatus.Offline, dd.Status); + await adminClient.Administration.Restart(cancellationToken); } @@ -982,7 +984,6 @@ namespace Tgstation.Server.Tests preStartupTime = DateTimeOffset.UtcNow; serverTask = server.Run(cancellationToken); long expectedCompileJobId; - DreamDaemonResponse currentDD; using (var adminClient = await CreateAdminClient(server.Url, cancellationToken)) { var instanceClient = adminClient.Instances.CreateClient(instance); @@ -996,21 +997,17 @@ namespace Tgstation.Server.Tests var wdt = new WatchdogTest(instanceClient); await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); - currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); - Assert.AreEqual(currentDD.StagedCompileJob.Job.Id, compileJob.Id); + dd = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(dd.StagedCompileJob.Job.Id, compileJob.Id); await wdt.TellWorldToReboot(cancellationToken); expectedCompileJobId = compileJob.Id.Value; - currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); - Assert.AreEqual(currentDD.ActiveCompileJob.Job.Id, expectedCompileJobId); - expectedCompileJobId = currentDD.ActiveCompileJob.Id.Value; + dd = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); + Assert.AreEqual(dd.ActiveCompileJob.Job.Id, expectedCompileJobId); - compileJob = await instanceClient.DreamMaker.Compile(cancellationToken); - await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); - - currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); - Assert.AreEqual(currentDD.StagedCompileJob.Job.Id, compileJob.Id); + expectedCompileJobId = dd.ActiveCompileJob.Id.Value; await adminClient.Administration.Restart(cancellationToken); } @@ -1025,12 +1022,9 @@ namespace Tgstation.Server.Tests var instanceClient = adminClient.Instances.CreateClient(instance); await WaitForInitialJobs(instanceClient); - await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); - - var lastStaged = currentDD.StagedCompileJob; - currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); - Assert.AreEqual(currentDD.ActiveCompileJob.Id, expectedCompileJobId); - Assert.AreEqual(currentDD.StagedCompileJob?.Id, lastStaged.Id); + var currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(expectedCompileJobId, currentDD.ActiveCompileJob.Id.Value); + Assert.AreEqual(WatchdogStatus.Online, currentDD.Status); var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs).RunPostTest(cancellationToken); await new ChatTest(instanceClient.ChatBots, adminClient.Instances, instance).RunPostTest(cancellationToken); From e08d5ede1a117ff6bef57fcabd5bb2c2bcee7b07 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 21:48:57 -0400 Subject: [PATCH 09/32] Safer instance manager shutdown --- .../Components/InstanceManager.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index d776370de2..ac0b1d230b 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -461,14 +461,27 @@ namespace Tgstation.Server.Host.Components logger.LogDebug("Stopping instance manager..."); var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken); await jobManager.StopAsync(cancellationToken); - await Task.WhenAll(instances.Select(x => x.Value.Instance.StopAsync(cancellationToken))); + + async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken) + { + try + { + await instance.StopAsync(cancellationToken); + } + catch (Exception ex) + { + logger.LogError(ex, "Instance shutdown exception!"); + } + } + + await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken))); await instanceFactoryStopTask; await swarmService.Shutdown(cancellationToken); } catch (Exception ex) { - logger.LogError(ex, "Instance manager stop exception!"); + logger.LogCritical(ex, "Instance manager stop exception!"); } } From 0a18905e1655e41c9cf875969a30d60d3d2c4410 Mon Sep 17 00:00:00 2001 From: Dominion Date: Sun, 2 Apr 2023 22:02:08 -0400 Subject: [PATCH 10/32] Fix DMAPI embed footer serialization --- build/Version.props | 2 +- src/DMAPI/tgs.dm | 2 +- src/DMAPI/tgs/v5/serializers.dm | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build/Version.props b/build/Version.props index e58434a58c..3b2a5f45f4 100644 --- a/build/Version.props +++ b/build/Version.props @@ -8,7 +8,7 @@ 9.9.0 10.3.0 11.3.0 - 6.2.0 + 6.2.1 5.4.0 1.2.1 1.2.1 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 3744a95a0f..a4f35f9331 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "6.2.0" +#define TGS_DMAPI_VERSION "6.2.1" // All functions and datums outside this document are subject to change with any version and should not be relied on. diff --git a/src/DMAPI/tgs/v5/serializers.dm b/src/DMAPI/tgs/v5/serializers.dm index 38814e2d9f..7f9bc731b7 100644 --- a/src/DMAPI/tgs/v5/serializers.dm +++ b/src/DMAPI/tgs/v5/serializers.dm @@ -43,6 +43,13 @@ . = ..() .["iconUrl"] = icon_url .["proxyIconUrl"] = proxy_icon_url + +/datum/tgs_chat_embed/footer/_interop_serialize() + return list( + "text" = text, + "iconUrl" = icon_url, + "proxyIconUrl" = proxy_icon_url + ) /datum/tgs_chat_embed/field/_interop_serialize() return list( From dec50442da7d9d1afa0915d93532ea6fb179c1a6 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 00:02:07 -0400 Subject: [PATCH 11/32] Fix BYOND manager getting a version with 0 patch --- src/Tgstation.Server.Host/Components/Byond/ByondManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 7aa63f18d2..76f5ebce25 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -255,7 +255,7 @@ namespace Tgstation.Server.Host.Components.Byond lock (installedVersions) hasRequestedActiveVersion = installedVersions.ContainsKey(activeVersionString); if (hasRequestedActiveVersion && Version.TryParse(activeVersionString, out var activeVersion)) - ActiveVersion = activeVersion.Semver(); + ActiveVersion = activeVersion; else { logger.LogWarning("Failed to load saved active version {0}!", activeVersionString); From 9c388e4700e9d47cfdca15c827c62868277c166f Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 00:02:30 -0400 Subject: [PATCH 12/32] Finally got the error detection for this test working --- .../Tgstation.Server.Tests/IntegrationTest.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 4ff01cd9e8..123b786ff3 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -1003,12 +1003,28 @@ namespace Tgstation.Server.Tests await wdt.TellWorldToReboot(cancellationToken); expectedCompileJobId = compileJob.Id.Value; - dd = await instanceClient.DreamDaemon.Read(cancellationToken); - Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); + bool first = true; + do + { + if (first) + first = false; + else + await Task.Delay(TimeSpan.FromSeconds(1)); + + dd = await instanceClient.DreamDaemon.Read(cancellationToken); + } + while (dd.Status.Value == WatchdogStatus.Restoring); + Assert.AreEqual(dd.ActiveCompileJob.Job.Id, expectedCompileJobId); + Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); expectedCompileJobId = dd.ActiveCompileJob.Id.Value; + await instanceClient.DreamDaemon.Update(new DreamDaemonRequest + { + AutoStart = false, + }, cancellationToken); + await adminClient.Administration.Restart(cancellationToken); } From 24627e3efdfee82b87151f6489ce79f752419c19 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 00:03:16 -0400 Subject: [PATCH 13/32] Error watchdog tests on runtimes --- tests/DMAPI/ApiFree/Test.dm | 5 ++ tests/DMAPI/BasicOperation/Test.dm | 5 ++ tests/DMAPI/LongRunning/Test.dm | 5 ++ .../Instance/WatchdogTest.cs | 64 +++++++++++-------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/tests/DMAPI/ApiFree/Test.dm b/tests/DMAPI/ApiFree/Test.dm index 42bd226f8a..b7b4ae5535 100644 --- a/tests/DMAPI/ApiFree/Test.dm +++ b/tests/DMAPI/ApiFree/Test.dm @@ -1,2 +1,7 @@ /world/New() + text2file("SUCCESS", "test_success.txt") log << "Hello world!" + +/world/Error(exception) + fdel("test_success.txt") + text2file("Runtime Error: [exception]", "test_fail_reason.txt") diff --git a/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm index d0119e52fc..ad65de0e1a 100644 --- a/tests/DMAPI/BasicOperation/Test.dm +++ b/tests/DMAPI/BasicOperation/Test.dm @@ -1,10 +1,15 @@ /world/New() + text2file("SUCCESS", "test_success.txt") log << "About to call TgsNew()" sleep_offline = FALSE TgsNew(minimum_required_security_level = TGS_SECURITY_SAFE) log << "About to call StartAsync()" StartAsync() +/world/Error(exception) + fdel("test_success.txt") + text2file("Runtime Error: [exception]", "test_fail_reason.txt") + /proc/StartAsync() set waitfor = FALSE Run() diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm index 71174adf7f..e94bfb2e6f 100644 --- a/tests/DMAPI/LongRunning/Test.dm +++ b/tests/DMAPI/LongRunning/Test.dm @@ -2,7 +2,12 @@ sleep_offline = FALSE loop_checks = FALSE +/world/Error(exception) + fdel("test_success.txt") + text2file("Runtime Error: [exception]", "test_fail_reason.txt") + /world/New() + text2file("SUCCESS", "test_success.txt") log << "Initial value of sleep_offline: [sleep_offline]" sleep_offline = FALSE diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index e2fb6f4cd3..99db3e31dd 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -140,6 +140,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(false, daemonStatus.SoftShutdown); Assert.AreEqual(String.Empty, daemonStatus.AdditionalParameters); var initialCompileJob = daemonStatus.ActiveCompileJob; + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); daemonStatus = await DeployTestDme("BasicOperation/basic_operation_test", DreamDaemonSecurity.Trusted, true, cancellationToken); @@ -153,6 +154,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(DreamDaemonSecurity.Trusted, newerCompileJob.MinimumSecurityLevel); Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.StagedCompileJob.DMApiVersion); await instanceClient.DreamDaemon.Shutdown(cancellationToken); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } async Task RunBasicTest(CancellationToken cancellationToken) @@ -260,7 +262,10 @@ namespace Tgstation.Server.Tests.Instance var ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(1U, ddStatus.HeartbeatSeconds.Value); if (ddStatus.Status.Value == WatchdogStatus.Offline) + { + await CheckDMApiFail(ddStatus.ActiveCompileJob, cancellationToken); break; + } if (--timeout == 0) Assert.Fail("DreamDaemon didn't shutdown within the timeout!"); @@ -343,6 +348,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id); Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, newerCompileJob.MinimumSecurityLevel); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); daemonStatus = await TellWorldToReboot(cancellationToken); Assert.AreNotEqual(initialCompileJob.Id, daemonStatus.ActiveCompileJob.Id); @@ -352,6 +358,7 @@ namespace Tgstation.Server.Tests.Instance daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } async Task RunLongRunningTestThenUpdateWithNewDme(CancellationToken cancellationToken) @@ -383,6 +390,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id); Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, newerCompileJob.MinimumSecurityLevel); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); daemonStatus = await TellWorldToReboot(cancellationToken); Assert.AreNotEqual(initialCompileJob.Id, daemonStatus.ActiveCompileJob.Id); @@ -392,6 +400,7 @@ namespace Tgstation.Server.Tests.Instance daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } async Task RunLongRunningTestThenUpdateWithByondVersionSwitch(CancellationToken cancellationToken) @@ -436,12 +445,14 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(true, daemonStatus.SoftRestart); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); daemonStatus = await TellWorldToReboot(cancellationToken); Assert.AreEqual(versionToInstall, daemonStatus.ActiveCompileJob.ByondVersion); Assert.IsNull(daemonStatus.StagedCompileJob); await instanceClient.DreamDaemon.Shutdown(cancellationToken); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); @@ -462,33 +473,30 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); Assert.AreEqual(IntegrationTest.DDPort, daemonStatus.CurrentPort); - // The measure we use to test dream daemon startup doesn't work on linux currently - if (new PlatformIdentifier().IsWindows) + // Try killing the DD process to ensure it gets set to the restoring state + do { - // Try killing the DD process to ensure it gets set to the restoring state - do - { - KillDD(true); - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); - } - while (daemonStatus.Status == WatchdogStatus.Online); - Assert.AreEqual(WatchdogStatus.Restoring, daemonStatus.Status.Value); - - // Kill it again - do - { - KillDD(false); - daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); - } - while (daemonStatus.Status == WatchdogStatus.Online || daemonStatus.Status == WatchdogStatus.Restoring); - Assert.AreEqual(WatchdogStatus.DelayedRestart, daemonStatus.Status.Value); - - await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken); - + KillDD(true); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); - Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); } + while (daemonStatus.Status == WatchdogStatus.Online); + Assert.AreEqual(WatchdogStatus.Restoring, daemonStatus.Status.Value); + + // Kill it again + do + { + KillDD(false); + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + } + while (daemonStatus.Status == WatchdogStatus.Online || daemonStatus.Status == WatchdogStatus.Restoring); + Assert.AreEqual(WatchdogStatus.DelayedRestart, daemonStatus.Status.Value); + + await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken); + + daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken); + Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value); + await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } static bool KillDD(bool require) @@ -565,6 +573,7 @@ namespace Tgstation.Server.Tests.Instance compileJobJob = await instanceClient.DreamMaker.Compile(cancellationToken); await WaitForJob(compileJobJob, 90, true, ErrorCode.DeploymentTimeout, cancellationToken); + ranTimeoutTest = true; } await instanceClient.DreamMaker.Update(new DreamMakerRequest @@ -606,9 +615,14 @@ namespace Tgstation.Server.Tests.Instance async Task CheckDMApiFail(CompileJobResponse compileJob, CancellationToken cancellationToken) { - var failFile = Path.Combine(instanceClient.Metadata.Path, "Game", compileJob.DirectoryName.Value.ToString(), "A", Path.GetDirectoryName(compileJob.DmeName), "test_fail_reason.txt"); + var gameDir = Path.Combine(instanceClient.Metadata.Path, "Game", compileJob.DirectoryName.Value.ToString(), Path.GetDirectoryName(compileJob.DmeName)); + var failFile = Path.Combine(gameDir, "test_fail_reason.txt"); if (!File.Exists(failFile)) + { + var successFile = Path.Combine(gameDir, "test_success.txt"); + Assert.IsTrue(File.Exists(successFile)); return; + } var text = await File.ReadAllTextAsync(failFile, cancellationToken); Assert.Fail(text); From c4501940a5507821bbc018a60a354717e42a5797 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 00:17:42 -0400 Subject: [PATCH 14/32] Version bump to 5.7.3 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 3b2a5f45f4..2722d7155e 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 5.7.2 + 5.7.3 4.4.0 9.9.0 10.3.0 From 002fbd2afed5da268071dd8271fb0667b60b79c6 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 01:26:29 -0400 Subject: [PATCH 15/32] Fix mishandling of world.params --- tests/DMAPI/BasicOperation/Test.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm index ad65de0e1a..42d311056c 100644 --- a/tests/DMAPI/BasicOperation/Test.dm +++ b/tests/DMAPI/BasicOperation/Test.dm @@ -19,7 +19,7 @@ sleep(50) world.TgsTargetedChatBroadcast("Sample admin-only message", TRUE) - var/list/world_params = params2list(world.params) + var/list/world_params = world.params if(!("test" in world_params) || world_params["test"] != "bababooey") text2file("Expected parameter test=bababooey but did not receive", "test_fail_reason.txt") From 235dbb78ce302263ac154ab10e66a43805fc3ad3 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 01:40:40 -0400 Subject: [PATCH 16/32] Fix bad StartupTimeout --- tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 99db3e31dd..a96918eac9 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -42,7 +42,7 @@ namespace Tgstation.Server.Tests.Instance // Increase startup timeout, disable heartbeats var initialSettings = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest { - StartupTimeout = 10, + StartupTimeout = 15, HeartbeatSeconds = 0, Port = IntegrationTest.DDPort }, cancellationToken); From fb62e927ee0ed13eb3f85e7a2cbe18c53ea9d180 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 01:48:50 -0400 Subject: [PATCH 17/32] Add missing CancellationTokens --- tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index a96918eac9..38803bb154 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -98,7 +98,7 @@ namespace Tgstation.Server.Tests.Instance killTaskStarted.SetResult(null); while (!jobTcs.Task.IsCompleted) KillDD(false); - }); + }, cancellationToken); JobResponse job; try @@ -243,7 +243,7 @@ namespace Tgstation.Server.Tests.Instance .GetProcess(ddProc.Id); // Ensure it's responding to heartbeats - await Task.WhenAny(Task.Delay(20000), ourProcessHandler.Lifetime); + await Task.WhenAny(Task.Delay(20000, cancellationToken), ourProcessHandler.Lifetime); Assert.IsFalse(ddProc.HasExited); await instanceClient.DreamDaemon.Update(new DreamDaemonRequest @@ -253,7 +253,7 @@ namespace Tgstation.Server.Tests.Instance ourProcessHandler.Suspend(); - await Task.WhenAny(ourProcessHandler.Lifetime, Task.Delay(TimeSpan.FromMinutes(1))); + await Task.WhenAny(ourProcessHandler.Lifetime, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)); var timeout = 20; do From 30d2e48200838dc8723eca1d582e7112b885fb20 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 01:49:44 -0400 Subject: [PATCH 18/32] Use more performant LINQ --- src/Tgstation.Server.Host/Database/DatabaseContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index b7a197143d..57159bdb32 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -298,7 +298,7 @@ namespace Tgstation.Server.Host.Database else logger.LogDebug("No migrations to apply"); - wasEmpty |= (await Users.AsQueryable().CountAsync(cancellationToken)) == 0; + wasEmpty |= !await Users.AsQueryable().AnyAsync(cancellationToken); return wasEmpty; } From 5d2bbc907c8f1478a14b4800baf25b5b31488eab Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 01:50:40 -0400 Subject: [PATCH 19/32] Add initial CompileJob to reattach info in WindowsWatchdog --- .../Components/Deployment/DmbFactory.cs | 23 +- .../Components/Session/ReattachInformation.cs | 8 + .../Components/Session/SessionController.cs | 21 +- .../Components/Session/SessionPersistor.cs | 12 + .../Components/Watchdog/BasicWatchdog.cs | 12 +- .../Components/Watchdog/PosixWatchdog.cs | 8 +- .../Components/Watchdog/WatchdogBase.cs | 2 +- .../Components/Watchdog/WindowsWatchdog.cs | 40 +- ...dReattachInfoInitialCompileJob.Designer.cs | 1065 ++++++++++++++++ ...0623_MSAddReattachInfoInitialCompileJob.cs | 58 + ...dReattachInfoInitialCompileJob.Designer.cs | 1098 +++++++++++++++++ ...0737_MYAddReattachInfoInitialCompileJob.cs | 58 + ...dReattachInfoInitialCompileJob.Designer.cs | 1059 ++++++++++++++++ ...0832_PGAddReattachInfoInitialCompileJob.cs | 58 + ...dReattachInfoInitialCompileJob.Designer.cs | 1030 ++++++++++++++++ ...0941_SLAddReattachInfoInitialCompileJob.cs | 58 + .../MySqlDatabaseContextModelSnapshot.cs | 11 + ...PostgresSqlDatabaseContextModelSnapshot.cs | 11 + .../SqlServerDatabaseContextModelSnapshot.cs | 11 + .../SqliteDatabaseContextModelSnapshot.cs | 11 + .../Models/ReattachInformation.cs | 10 + 21 files changed, 4619 insertions(+), 45 deletions(-) create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.Designer.cs create mode 100644 src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.cs diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index 82241e7c92..67e59737cb 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -416,17 +416,20 @@ namespace Tgstation.Server.Host.Components.Deployment } lock (jobLockCounts) - if (!jobLockCounts.TryGetValue(job.Id.Value, out var currentVal) || currentVal == 1) - { - jobLockCounts.Remove(job.Id.Value); - logger.LogDebug("Cleaning lock-free compile job {0} => {1}", job.Id, job.DirectoryName); - cleanupTask = HandleCleanup(); - } + if (jobLockCounts.TryGetValue(job.Id.Value, out var currentVal)) + if (currentVal == 1) + { + jobLockCounts.Remove(job.Id.Value); + logger.LogDebug("Cleaning lock-free compile job {0} => {1}", job.Id, job.DirectoryName); + cleanupTask = HandleCleanup(); + } + else + { + var decremented = --jobLockCounts[job.Id.Value]; + logger.LogTrace("Compile job {0} lock count now: {1}", job.Id, decremented); + } else - { - var decremented = --jobLockCounts[job.Id.Value]; - logger.LogTrace("Compile job {0} lock count now: {1}", job.Id, decremented); - } + logger.LogError("Extra Dispose of DmbProvider for CompileJob {compileJobId}!", job.Id); } /// diff --git a/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs index 184eb9f1b5..7a9661d5c7 100644 --- a/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs @@ -17,6 +17,11 @@ namespace Tgstation.Server.Host.Components.Session /// public IDmbProvider Dmb { get; set; } + /// + /// The initially used to launch DreamDaemon. Should be a different than . Should not be set if persisting the initial isn't necessary. + /// + public IDmbProvider InitialDmb { get; set; } + /// /// The for the DMAPI. /// @@ -37,13 +42,16 @@ namespace Tgstation.Server.Host.Components.Session /// /// The to copy values from. /// The value of . + /// The value of . /// The value of . public ReattachInformation( Models.ReattachInformation copy, IDmbProvider dmb, + IDmbProvider initialDmb, TimeSpan topicRequestTimeout) : base(copy) { Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb)); + InitialDmb = initialDmb; TopicRequestTimeout = topicRequestTimeout; runtimeInformationLock = new object(); diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 37a0a6ac64..452c4fefec 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -270,22 +270,18 @@ namespace Tgstation.Server.Host.Components.Session logger.LogTrace("Disposing..."); if (!released) - { process.Terminate(); - byondLock.Dispose(); - } await process.DisposeAsync(); + byondLock.Dispose(); bridgeRegistration?.Dispose(); - ReattachInformation.Dmb?.Dispose(); // will be null when released + ReattachInformation.Dmb.Dispose(); + ReattachInformation.InitialDmb?.Dispose(); chatTrackingContext.Dispose(); reattachTopicCts.Dispose(); if (!released) - { - // finish the async callback - await Lifetime; - } + await Lifetime; // finish the async callback } /// @@ -441,14 +437,11 @@ namespace Tgstation.Server.Host.Components.Session { CheckDisposed(); - // we still don't want to dispose the dmb yet, even though we're keeping it alive - var tmpProvider = ReattachInformation.Dmb; - ReattachInformation.Dmb = null; + ReattachInformation.Dmb.KeepAlive(); + ReattachInformation.InitialDmb?.KeepAlive(); + byondLock.DoNotDeleteThisSession(); released = true; await DisposeAsync(); - byondLock.DoNotDeleteThisSession(); - tmpProvider.KeepAlive(); - ReattachInformation.Dmb = tmpProvider; } /// diff --git a/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs b/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs index c6701c4ef0..34b46db93c 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs @@ -78,6 +78,7 @@ namespace Tgstation.Server.Host.Components.Session { AccessIdentifier = reattachInformation.AccessIdentifier, CompileJobId = reattachInformation.Dmb.CompileJob.Id.Value, + InitialCompileJobId = reattachInformation.InitialDmb?.CompileJob.Id.Value, Port = reattachInformation.Port, ProcessId = reattachInformation.ProcessId, RebootState = reattachInformation.RebootState, @@ -128,6 +129,7 @@ namespace Tgstation.Server.Host.Components.Session .AsQueryable() .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id) .Include(x => x.CompileJob) + .Include(x => x.InitialCompileJob) .ToListAsync(cancellationToken); result = dbReattachInfos.FirstOrDefault(); if (result == default) @@ -191,9 +193,19 @@ namespace Tgstation.Server.Host.Components.Session return null; } + IDmbProvider initialDmb = null; + if (result.InitialCompileJob != null) + { + logger.LogTrace("Loading initial compile job..."); + initialDmb = await dmbFactory.FromCompileJob(result.InitialCompileJob, cancellationToken); + } + + logger.LogTrace("Retrieved ReattachInformation"); + var info = new ReattachInformation( result, dmb, + initialDmb, topicTimeout.Value); logger.LogDebug("Reattach information loaded: {info}", info); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index 19150a513e..6c7be93c1c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -249,7 +249,7 @@ namespace Tgstation.Server.Host.Components.Watchdog // Server.AdjustPriority(true); if (!reattachInProgress) - await SessionPersistor.Save(Server.ReattachInformation, cancellationToken); + await SessionStartupPersist(cancellationToken); await CheckLaunchResult(Server, "Server", cancellationToken); @@ -273,6 +273,16 @@ namespace Tgstation.Server.Host.Components.Watchdog } } + /// + /// Called to save the current into the when initially launched. + /// + /// The for the operation. + /// A representing the running operation. + protected virtual Task SessionStartupPersist(CancellationToken cancellationToken) + { + return SessionPersistor.Save(Server.ReattachInformation, cancellationToken); + } + /// /// Handler for when the is . /// diff --git a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs index dc503f37ca..8195e3c9e5 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/PosixWatchdog.cs @@ -82,6 +82,9 @@ namespace Tgstation.Server.Host.Components.Watchdog { } + /// + protected override Task ApplyInitialDmb(CancellationToken cancellationToken) => Task.CompletedTask; + /// protected override async Task InitialLink(CancellationToken cancellationToken) { @@ -107,6 +110,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected override async Task InitController(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken) { + var suspended = false; try { await base.InitController(chatTask, reattachInfo, cancellationToken); @@ -120,6 +124,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { Logger.LogTrace("Unhardlinking compile job..."); Server?.Suspend(); + suspended = true; var hardLink = hardLinkedDmb.Directory; var originalPosition = hardLinkedDmb.CompileJob.DirectoryName.ToString(); await GameIOManager.MoveDirectory( @@ -149,7 +154,8 @@ namespace Tgstation.Server.Host.Components.Watchdog Logger.LogTrace("Symlinking compile job..."); await ActiveSwappable.MakeActive(cancellationToken); - Server.Resume(); + if (suspended) + Server.Resume(); } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 369c7756e5..d45d2a39de 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -727,7 +727,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The for the operation. /// A representing the running operation. - private async Task MonitorRestart(CancellationToken cancellationToken) + async Task MonitorRestart(CancellationToken cancellationToken) { Logger.LogTrace("Monitor restart!"); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs index 2c14e8c7f1..45c11df84d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -41,11 +41,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// SwappableDmbProvider pendingSwappable; - /// - /// The the was started with. - /// - IDmbProvider startupDmbProvider; - /// /// Initializes a new instance of the class. /// @@ -120,9 +115,6 @@ namespace Tgstation.Server.Host.Components.Watchdog ActiveSwappable = null; pendingSwappable?.Dispose(); pendingSwappable = null; - - startupDmbProvider?.Dispose(); - startupDmbProvider = null; } /// @@ -136,6 +128,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ActiveSwappable = pendingSwappable; pendingSwappable = null; + await SessionPersistor.Save(Server.ReattachInformation, cancellationToken); await updateTask; } else @@ -218,18 +211,12 @@ namespace Tgstation.Server.Host.Components.Watchdog { if (ActiveSwappable != null) throw new InvalidOperationException("Expected activeSwappable to be null!"); - if (startupDmbProvider != null) - throw new InvalidOperationException("Expected startupDmbProvider to be null!"); + if (pendingSwappable != null) + throw new InvalidOperationException("Expected pendingSwappable to be null!"); - Logger.LogTrace("Prep for server launch. pendingSwappable is {0}available", pendingSwappable == null ? "not " : String.Empty); - - // Add another lock to the startup DMB because it'll be used throughout the lifetime of the watchdog - startupDmbProvider = await DmbFactory.FromCompileJob(dmbToUse.CompileJob, cancellationToken); - - pendingSwappable ??= new SwappableDmbProvider(dmbToUse, GameIOManager, symlinkFactory); - ActiveSwappable = pendingSwappable; - pendingSwappable = null; + Logger.LogTrace("Prep for server launch"); + ActiveSwappable = new SwappableDmbProvider(dmbToUse, GameIOManager, symlinkFactory); try { await InitialLink(cancellationToken); @@ -245,6 +232,23 @@ namespace Tgstation.Server.Host.Components.Watchdog return ActiveSwappable; } + /// + /// Set the for the . + /// + /// The for the operation. + /// A representing the running operation. + protected virtual async Task ApplyInitialDmb(CancellationToken cancellationToken) + { + Server.ReattachInformation.InitialDmb = await DmbFactory.FromCompileJob(Server.CompileJob, cancellationToken); + } + + /// + protected override async Task SessionStartupPersist(CancellationToken cancellationToken) + { + await ApplyInitialDmb(cancellationToken); + await base.SessionStartupPersist(cancellationToken); + } + /// /// Create the initial link to the live game directory using . /// diff --git a/src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.Designer.cs new file mode 100644 index 0000000000..f749992580 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.Designer.cs @@ -0,0 +1,1065 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20230403050623_MSAddReattachInfoInitialCompileJob")] + partial class MSAddReattachInfoInitialCompileJob + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.15") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + 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"), 1L, 1); + + 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("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"), 1L, 1); + + b.Property("ByondVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + 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("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"), 1L, 1); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("DumpOnHeartbeatRestart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("HeartbeatSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .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"), 1L, 1); + + 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"), 1L, 1); + + 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"), 1L, 1); + + b.Property("ByondRights") + .HasColumnType("decimal(20,0)"); + + 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("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"), 1L, 1); + + 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("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"), 1L, 1); + + 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"), 1L, 1); + + 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"), 1L, 1); + + 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.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"), 1L, 1); + + 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"), 1L, 1); + + 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"), 1L, 1); + + 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"), 1L, 1); + + 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"), 1L, 1); + + 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"), 1L, 1); + + 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/20230403050623_MSAddReattachInfoInitialCompileJob.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.cs new file mode 100644 index 0000000000..085832fc0c --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050623_MSAddReattachInfoInitialCompileJob.cs @@ -0,0 +1,58 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Adds the InitialCompileJobId to the ReattachInformations table for MSSQL. + /// + public partial class MSAddReattachInfoInitialCompileJob : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "InitialCompileJobId", + table: "ReattachInformations", + type: "bigint", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId"); + + migrationBuilder.AddForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId", + principalTable: "CompileJobs", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropColumn( + name: "InitialCompileJobId", + table: "ReattachInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.Designer.cs new file mode 100644 index 0000000000..a53708cfc0 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.Designer.cs @@ -0,0 +1,1098 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20230403050737_MYAddReattachInfoInitialCompileJob")] + partial class MYAddReattachInfoInitialCompileJob + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.15") + .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("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("ByondVersion") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ByondVersion"), "utf8mb4"); + + 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("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("DumpOnHeartbeatRestart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HeartbeatSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .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("ByondRights") + .HasColumnType("bigint unsigned"); + + 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("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("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.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/20230403050737_MYAddReattachInfoInitialCompileJob.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.cs new file mode 100644 index 0000000000..bf2a0b84d3 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050737_MYAddReattachInfoInitialCompileJob.cs @@ -0,0 +1,58 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Adds the InitialCompileJobId to the ReattachInformations table for MYSQL. + /// + public partial class MYAddReattachInfoInitialCompileJob : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "InitialCompileJobId", + table: "ReattachInformations", + type: "bigint", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId"); + + migrationBuilder.AddForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId", + principalTable: "CompileJobs", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropColumn( + name: "InitialCompileJobId", + table: "ReattachInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.Designer.cs new file mode 100644 index 0000000000..8e4962a1e0 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.Designer.cs @@ -0,0 +1,1059 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(PostgresSqlDatabaseContext))] + [Migration("20230403050832_PGAddReattachInfoInitialCompileJob")] + partial class PGAddReattachInfoInitialCompileJob + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.15") + .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("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("ByondVersion") + .IsRequired() + .HasColumnType("text"); + + 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("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("DumpOnHeartbeatRestart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("HeartbeatSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .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("ByondRights") + .HasColumnType("numeric(20,0)"); + + 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("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("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.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/20230403050832_PGAddReattachInfoInitialCompileJob.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.cs new file mode 100644 index 0000000000..0f46df6c05 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050832_PGAddReattachInfoInitialCompileJob.cs @@ -0,0 +1,58 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Adds the InitialCompileJobId to the ReattachInformations table for PostgresSQL. + /// + public partial class PGAddReattachInfoInitialCompileJob : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "InitialCompileJobId", + table: "ReattachInformations", + type: "bigint", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId"); + + migrationBuilder.AddForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId", + principalTable: "CompileJobs", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropColumn( + name: "InitialCompileJobId", + table: "ReattachInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.Designer.cs new file mode 100644 index 0000000000..8330e52cb1 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.Designer.cs @@ -0,0 +1,1030 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20230403050941_SLAddReattachInfoInitialCompileJob")] + partial class SLAddReattachInfoInitialCompileJob + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.15"); + + 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("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("ByondVersion") + .IsRequired() + .HasColumnType("TEXT"); + + 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("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("DumpOnHeartbeatRestart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("HeartbeatSeconds") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("LogOutput") + .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("ByondRights") + .HasColumnType("INTEGER"); + + b.Property("ChatBotRights") + .HasColumnType("INTEGER"); + + b.Property("ConfigurationRights") + .HasColumnType("INTEGER"); + + b.Property("DreamDaemonRights") + .HasColumnType("INTEGER"); + + b.Property("DreamMakerRights") + .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("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.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/20230403050941_SLAddReattachInfoInitialCompileJob.cs b/src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.cs new file mode 100644 index 0000000000..1b06d44947 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20230403050941_SLAddReattachInfoInitialCompileJob.cs @@ -0,0 +1,58 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Adds the InitialCompileJobId to the ReattachInformations table for SQLite. + /// + public partial class SLAddReattachInfoInitialCompileJob : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "InitialCompileJobId", + table: "ReattachInformations", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId"); + + migrationBuilder.AddForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations", + column: "InitialCompileJobId", + principalTable: "CompileJobs", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropForeignKey( + name: "FK_ReattachInformations_CompileJobs_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropIndex( + name: "IX_ReattachInformations_InitialCompileJobId", + table: "ReattachInformations"); + + migrationBuilder.DropColumn( + name: "InitialCompileJobId", + table: "ReattachInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index 3cb4c5c1bf..420b24ef34 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -502,6 +502,9 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("CompileJobId") .HasColumnType("bigint"); + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + b.Property("LaunchSecurityLevel") .HasColumnType("int"); @@ -521,6 +524,8 @@ namespace Tgstation.Server.Host.Database.Migrations b.HasIndex("CompileJobId"); + b.HasIndex("InitialCompileJobId"); + b.ToTable("ReattachInformations"); }); @@ -942,7 +947,13 @@ namespace Tgstation.Server.Host.Database.Migrations .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 => diff --git a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs index 5d1e2d4387..f291fe575d 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs @@ -485,6 +485,9 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("CompileJobId") .HasColumnType("bigint"); + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + b.Property("LaunchSecurityLevel") .HasColumnType("integer"); @@ -504,6 +507,8 @@ namespace Tgstation.Server.Host.Database.Migrations b.HasIndex("CompileJobId"); + b.HasIndex("InitialCompileJobId"); + b.ToTable("ReattachInformations"); }); @@ -903,7 +908,13 @@ namespace Tgstation.Server.Host.Database.Migrations .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 => diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs index cc6e484f9b..34e3f31f07 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -490,6 +490,9 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("CompileJobId") .HasColumnType("bigint"); + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + b.Property("LaunchSecurityLevel") .HasColumnType("int"); @@ -509,6 +512,8 @@ namespace Tgstation.Server.Host.Database.Migrations b.HasIndex("CompileJobId"); + b.HasIndex("InitialCompileJobId"); + b.ToTable("ReattachInformations"); }); @@ -909,7 +914,13 @@ namespace Tgstation.Server.Host.Database.Migrations .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 => diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs index cdc004f6a5..f302b47c7e 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs @@ -468,6 +468,9 @@ namespace Tgstation.Server.Host.Database.Migrations b.Property("CompileJobId") .HasColumnType("INTEGER"); + b.Property("InitialCompileJobId") + .HasColumnType("INTEGER"); + b.Property("LaunchSecurityLevel") .HasColumnType("INTEGER"); @@ -487,6 +490,8 @@ namespace Tgstation.Server.Host.Database.Migrations b.HasIndex("CompileJobId"); + b.HasIndex("InitialCompileJobId"); + b.ToTable("ReattachInformations"); }); @@ -874,7 +879,13 @@ namespace Tgstation.Server.Host.Database.Migrations .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 => diff --git a/src/Tgstation.Server.Host/Models/ReattachInformation.cs b/src/Tgstation.Server.Host/Models/ReattachInformation.cs index e6d714fb43..00409df99a 100644 --- a/src/Tgstation.Server.Host/Models/ReattachInformation.cs +++ b/src/Tgstation.Server.Host/Models/ReattachInformation.cs @@ -22,5 +22,15 @@ namespace Tgstation.Server.Host.Models /// The of . /// public long CompileJobId { get; set; } + + /// + /// The the server was initially launched with in the case of Windows. + /// + public CompileJob InitialCompileJob { get; set; } + + /// + /// The of . + /// + public long? InitialCompileJobId { get; set; } } } From 96c894c7b994dfa81a006913ec4c9941e440c732 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 02:05:07 -0400 Subject: [PATCH 20/32] Stabilize BYOND version used for testing --- tests/Tgstation.Server.Tests/Instance/ByondTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Instance/ByondTest.cs b/tests/Tgstation.Server.Tests/Instance/ByondTest.cs index f2e69aeee4..d7eacaff2d 100644 --- a/tests/Tgstation.Server.Tests/Instance/ByondTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/ByondTest.cs @@ -21,7 +21,7 @@ namespace Tgstation.Server.Tests.Instance { sealed class ByondTest : JobsRequiredTest { - public static readonly Version TestVersion = new (515, 1592); + public static readonly Version TestVersion = new (514, 1588); readonly IByondClient byondClient; From 50934aabbc3ccea658c87d252d0bb6dd8b5cc6fc Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 02:19:55 -0400 Subject: [PATCH 21/32] More tests --- .../Tgstation.Server.Tests/IntegrationTest.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 123b786ff3..f45fef48bc 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -983,7 +983,7 @@ namespace Tgstation.Server.Tests // chat bot start, dd autostart, and reboot with different initial job test preStartupTime = DateTimeOffset.UtcNow; serverTask = server.Run(cancellationToken); - long expectedCompileJobId; + long expectedCompileJobId, expectedStaged; using (var adminClient = await CreateAdminClient(server.Url, cancellationToken)) { var instanceClient = adminClient.Instances.CreateClient(instance); @@ -1000,20 +1000,14 @@ namespace Tgstation.Server.Tests dd = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(dd.StagedCompileJob.Job.Id, compileJob.Id); - await wdt.TellWorldToReboot(cancellationToken); expectedCompileJobId = compileJob.Id.Value; + dd = await wdt.TellWorldToReboot(cancellationToken); - bool first = true; - do + while (dd.Status.Value == WatchdogStatus.Restoring) { - if (first) - first = false; - else - await Task.Delay(TimeSpan.FromSeconds(1)); - + await Task.Delay(TimeSpan.FromSeconds(1)); dd = await instanceClient.DreamDaemon.Read(cancellationToken); } - while (dd.Status.Value == WatchdogStatus.Restoring); Assert.AreEqual(dd.ActiveCompileJob.Job.Id, expectedCompileJobId); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); @@ -1025,6 +1019,10 @@ namespace Tgstation.Server.Tests AutoStart = false, }, cancellationToken); + compileJob = await instanceClient.DreamMaker.Compile(cancellationToken); + await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken); + expectedStaged = compileJob.Id.Value; + await adminClient.Administration.Restart(cancellationToken); } @@ -1041,6 +1039,12 @@ namespace Tgstation.Server.Tests var currentDD = await instanceClient.DreamDaemon.Read(cancellationToken); Assert.AreEqual(expectedCompileJobId, currentDD.ActiveCompileJob.Id.Value); Assert.AreEqual(WatchdogStatus.Online, currentDD.Status); + Assert.AreEqual(expectedStaged, currentDD.StagedCompileJob.Job.Id.Value); + + var wdt = new WatchdogTest(instanceClient); + currentDD = await wdt.TellWorldToReboot(cancellationToken); + Assert.AreEqual(expectedStaged, currentDD.ActiveCompileJob.Job.Id.Value); + Assert.IsNull(currentDD.StagedCompileJob); var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs).RunPostTest(cancellationToken); await new ChatTest(instanceClient.ChatBots, adminClient.Instances, instance).RunPostTest(cancellationToken); From 60d21e7637d4df3db3b993130e8512c4a062f0e6 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 09:42:59 -0400 Subject: [PATCH 22/32] Fix down migrations --- .../Database/DatabaseContext.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 57159bdb32..bf77cbd332 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -379,22 +379,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(MSAddDreamDaemonLogOutput); + internal static readonly Type MSLatestMigration = typeof(MSAddReattachInfoInitialCompileJob); /// /// Used by unit tests to remind us to setup the correct MYSQL migration downgrades. /// - internal static readonly Type MYLatestMigration = typeof(MYAddDreamDaemonLogOutput); + internal static readonly Type MYLatestMigration = typeof(MYAddReattachInfoInitialCompileJob); /// /// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades. /// - internal static readonly Type PGLatestMigration = typeof(PGAddDreamDaemonLogOutput); + internal static readonly Type PGLatestMigration = typeof(PGAddReattachInfoInitialCompileJob); /// /// Used by unit tests to remind us to setup the correct SQLite migration downgrades. /// - internal static readonly Type SLLatestMigration = typeof(SLAddDreamDaemonLogOutput); + internal static readonly Type SLLatestMigration = typeof(SLAddReattachInfoInitialCompileJob); /// #pragma warning disable CA1502 // Cyclomatic complexity @@ -425,6 +425,15 @@ namespace Tgstation.Server.Host.Database string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); + if (targetVersion < new Version(5, 7, 3)) + targetMigration = currentDatabaseType switch + { + DatabaseType.MySql => nameof(MYAddDreamDaemonLogOutput), + DatabaseType.PostgresSql => nameof(PGAddDreamDaemonLogOutput), + DatabaseType.SqlServer => nameof(MSAddDreamDaemonLogOutput), + DatabaseType.Sqlite => nameof(SLAddDreamDaemonLogOutput), + _ => BadDatabaseType(), + }; if (targetVersion < new Version(5, 7, 0)) targetMigration = currentDatabaseType switch { From 614096be4e10fe5f5677828e32545bcdcb4f82d3 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 11:31:09 -0400 Subject: [PATCH 23/32] Don't send errorMessage if unnecessary --- src/DMAPI/tgs/v5/api.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm index 36610b1242..725e53102d 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -99,7 +99,8 @@ /datum/tgs_api/v5/proc/TopicResponse(error_message = null) var/list/response = list() - response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message + if(error_message) + response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message return json_encode(response) From 8eb9ffe78f2144dba5c6391758c9c0bdb378dd23 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 11:31:19 -0400 Subject: [PATCH 24/32] Adjust test timeout values --- .../Tgstation.Server.Tests/Instance/DeploymentTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs b/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs index 738b941384..37570083e9 100644 --- a/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/DeploymentTest.cs @@ -60,10 +60,10 @@ namespace Tgstation.Server.Tests.Instance var updatedDD = await dreamDaemonClient.Update(new DreamDaemonRequest { - StartupTimeout = 5, + StartupTimeout = 15, Port = IntegrationTest.DDPort }, cancellationToken); - Assert.AreEqual(5U, updatedDD.StartupTimeout); + Assert.AreEqual(15U, updatedDD.StartupTimeout); Assert.AreEqual(IntegrationTest.DDPort, updatedDD.Port); await ApiAssert.ThrowsException(() => dreamDaemonClient.Update(new DreamDaemonRequest @@ -77,7 +77,7 @@ namespace Tgstation.Server.Tests.Instance }, cancellationToken), ErrorCode.PortNotAvailable); deployJob = await dreamMakerClient.Compile(cancellationToken); - await WaitForJob(deployJob, 30, true, ErrorCode.DreamMakerNeverValidated, cancellationToken); + await WaitForJob(deployJob, 40, true, ErrorCode.DreamMakerNeverValidated, cancellationToken); const string FailProject = "tests/DMAPI/BuildFail/build_fail"; var updated = await dreamMakerClient.Update(new DreamMakerRequest @@ -88,7 +88,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(FailProject, updated.ProjectName); deployJob = await dreamMakerClient.Compile(cancellationToken); - await WaitForJob(deployJob, 30, true, ErrorCode.DreamMakerExitCode, cancellationToken); + await WaitForJob(deployJob, 40, true, ErrorCode.DreamMakerExitCode, cancellationToken); await dreamMakerClient.Update(new DreamMakerRequest { @@ -96,7 +96,7 @@ namespace Tgstation.Server.Tests.Instance }, cancellationToken); deployJob = await dreamMakerClient.Compile(cancellationToken); - await WaitForJob(deployJob, 30, true, ErrorCode.DreamMakerMissingDme, cancellationToken); + await WaitForJob(deployJob, 40, true, ErrorCode.DreamMakerMissingDme, cancellationToken); // check that we can change the visibility From 0c252440d620439670eadd5c21a88a4fe5b414ea Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 12:26:11 -0400 Subject: [PATCH 25/32] Add escape characters to DMAPI test event messages --- tests/DMAPI/LongRunning/Test.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DMAPI/LongRunning/Test.dm b/tests/DMAPI/LongRunning/Test.dm index e94bfb2e6f..6ab061b10c 100644 --- a/tests/DMAPI/LongRunning/Test.dm +++ b/tests/DMAPI/LongRunning/Test.dm @@ -74,7 +74,7 @@ /datum/tgs_event_handler/impl/HandleEvent(event_code, ...) set waitfor = FALSE - world.TgsChatBroadcast("Recieved event: [json_encode(args)]") + world.TgsChatBroadcast("Recieved event: `[json_encode(args)]`") /world/Export(url) log << "Export: [url]" From 2ef30b2290a1de051a0528ddc85f0f60025fe32f Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 12:27:37 -0400 Subject: [PATCH 26/32] Safer chat message parsing --- .../Components/Session/SessionController.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 452c4fefec..0f76b5993f 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -316,9 +316,20 @@ namespace Tgstation.Server.Host.Components.Session if (parameters.ChatMessage.Text == null) return Error("Missing message field in chatMessage!"); + var anyFailed = false; + var parsedChannels = parameters.ChatMessage.ChannelIds.Select( + channelString => + { + anyFailed |= !UInt64.TryParse(channelString, out var channelId); + return channelId; + }); + + if (anyFailed) + return Error("Failed to parse channelIds as U64!"); + chat.QueueMessage( parameters.ChatMessage, - parameters.ChatMessage.ChannelIds.Select(UInt64.Parse)); + parsedChannels); break; case BridgeCommandType.Prime: var oldPrimeTcs = primeTcs; From 28571bd0566aa41301dbd2cff2679880c443ff93 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 12:29:16 -0400 Subject: [PATCH 27/32] Support chat message queuing from chat commands. --- build/Version.props | 4 +- src/DMAPI/tgs.dm | 2 +- src/DMAPI/tgs/v5/__interop_version.dm | 2 +- src/DMAPI/tgs/v5/api.dm | 3 ++ .../Components/Watchdog/WatchdogBase.cs | 41 ++++++++++++------- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/build/Version.props b/build/Version.props index 2722d7155e..8802116119 100644 --- a/build/Version.props +++ b/build/Version.props @@ -8,8 +8,8 @@ 9.9.0 10.3.0 11.3.0 - 6.2.1 - 5.4.0 + 6.3.0 + 5.5.0 1.2.1 1.2.1 1.0.1 diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index a4f35f9331..e35ccb69d1 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "6.2.1" +#define TGS_DMAPI_VERSION "6.3.0" // All functions and datums outside this document are subject to change with any version and should not be relied on. diff --git a/src/DMAPI/tgs/v5/__interop_version.dm b/src/DMAPI/tgs/v5/__interop_version.dm index 4add7374ad..d0ac7e92ea 100644 --- a/src/DMAPI/tgs/v5/__interop_version.dm +++ b/src/DMAPI/tgs/v5/__interop_version.dm @@ -1 +1 @@ -"5.4.0" +"5.5.0" diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm index 725e53102d..664875a70d 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -129,9 +129,12 @@ switch(command) if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND) + intercepted_message_queue = list() var/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND]) if(!result) result = TopicResponse("Error running chat command!") + result[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue + intercepted_message_queue = null return result if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION) intercepted_message_queue = list() diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d45d2a39de..d6377963eb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -307,6 +307,8 @@ namespace Tgstation.Server.Host.Components.Watchdog commandResponse.Text = "TGS: Command processed but no DMAPI response returned!"; } + HandleChatResponses(commandResult); + return commandResponse; } } @@ -465,20 +467,7 @@ namespace Tgstation.Server.Host.Components.Watchdog cancellationToken) ; - if (result?.InteropResponse?.ChatResponses != null) - foreach (var response in result.InteropResponse.ChatResponses) - Chat.QueueMessage( - response, - response.ChannelIds - .Select(channelIdString => - { - if (UInt64.TryParse(channelIdString, out var channelId)) - return (ulong?)channelId; - - return null; - }) - .Where(nullableChannelId => nullableChannelId.HasValue) - .Select(nullableChannelId => nullableChannelId.Value)); + HandleChatResponses(result); } /// @@ -1100,5 +1089,29 @@ namespace Tgstation.Server.Host.Components.Watchdog return MonitorAction.Continue; } + + /// + /// Handle any in a given topic . + /// + /// The . + void HandleChatResponses(CombinedTopicResponse result) + { + if (result?.InteropResponse?.ChatResponses != null) + foreach (var response in result.InteropResponse.ChatResponses) + Chat.QueueMessage( + response, + response.ChannelIds + .Select(channelIdString => + { + if (UInt64.TryParse(channelIdString, out var channelId)) + return (ulong?)channelId; + else + Logger.LogWarning("Could not parse chat response channel ID: {channelID}", channelIdString); + + return null; + }) + .Where(nullableChannelId => nullableChannelId.HasValue) + .Select(nullableChannelId => nullableChannelId.Value)); + } } } From 01aa580fb8d10d628a957f4173ace0494f4a7d34 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 13:13:02 -0400 Subject: [PATCH 28/32] Modify DMAPI release to help webpanel --- .github/workflows/ci-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-suite.yml b/.github/workflows/ci-suite.yml index 0a762c4afb..9175dc01d8 100644 --- a/.github/workflows/ci-suite.yml +++ b/.github/workflows/ci-suite.yml @@ -631,7 +631,7 @@ jobs: with: tag_name: dmapi-v${{ env.TGS_DM_VERSION }} release_name: tgstation-server DMAPI v${{ env.TGS_DM_VERSION }} - body: The TGS DMAPI + body: The TGS DMAPI \#tgs-dmapi-release commitish: ${{ github.event.head_commit.id }} - name: Upload DMAPI Artifact From ba3bbf486e5c0cb2d594b9dd695b3f213e221b9c Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 15:16:20 -0400 Subject: [PATCH 29/32] Fix issue with reattached servers never updating until a new deployment is made --- .../Components/Watchdog/WatchdogBase.cs | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d6377963eb..2bbff38173 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -770,6 +770,28 @@ namespace Tgstation.Server.Host.Components.Watchdog } } + /// + /// Check for a new . + /// + /// The session's current . + /// A that completes if and when a newer is available. + Task InitialCheckDmbUpdated(CompileJob currentCompileJob) + { + var factoryTask = DmbFactory.OnNewerDmb; + + var latestCompileJob = DmbFactory.LatestCompileJob(); + if (latestCompileJob == null) + return factoryTask; + + if (latestCompileJob.Id != currentCompileJob.Id) + { + Logger.LogDebug("Found new CompileJob without waiting"); + return Task.CompletedTask; + } + + return factoryTask; + } + /// /// The main loop of the watchdog. Ayschronously waits for events to occur and then responds to them. /// @@ -792,6 +814,7 @@ namespace Tgstation.Server.Host.Components.Watchdog activeLaunchParametersChanged = null, newDmbAvailable = null; ISessionController lastController = null; + var ranInitialDmbCheck = false; for (ulong iteration = 1; nextAction != MonitorAction.Exit; ++iteration) using (LogContext.PushProperty("Monitor", iteration)) try @@ -803,19 +826,19 @@ namespace Tgstation.Server.Host.Components.Watchdog void UpdateMonitoredTasks() { - static void TryUpdateTask(ref Task oldTask, Task newTask) + static void TryUpdateTask(ref Task oldTask, Func newTaskFactory) { if (oldTask?.IsCompleted == true) return; - oldTask = newTask; + oldTask = newTaskFactory(); } if (lastController == controller) { - TryUpdateTask(ref activeServerLifetime, controller.Lifetime); - TryUpdateTask(ref activeServerReboot, controller.OnReboot); - TryUpdateTask(ref serverPrimed, controller.OnPrime); + TryUpdateTask(ref activeServerLifetime, () => controller.Lifetime); + TryUpdateTask(ref activeServerReboot, () => controller.OnReboot); + TryUpdateTask(ref serverPrimed, () => controller.OnPrime); } else { @@ -825,8 +848,17 @@ namespace Tgstation.Server.Host.Components.Watchdog lastController = controller; } - TryUpdateTask(ref activeLaunchParametersChanged, ActiveParametersUpdated.Task); - TryUpdateTask(ref newDmbAvailable, DmbFactory.OnNewerDmb); + TryUpdateTask(ref activeLaunchParametersChanged, () => ActiveParametersUpdated.Task); + TryUpdateTask( + ref newDmbAvailable, + () => + { + var result = ranInitialDmbCheck + ? DmbFactory.OnNewerDmb + : InitialCheckDmbUpdated(controller.CompileJob); + ranInitialDmbCheck = true; + return result; + }); } UpdateMonitoredTasks(); From 627200d88ab3b871861005ecb1c1375c079bc521 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 16:24:43 -0400 Subject: [PATCH 30/32] More IO tweaks --- .../IO/DefaultIOManager.cs | 30 +++++-------------- src/Tgstation.Server.Host/IO/IIOManager.cs | 4 +-- .../System/ProcessExecutor.cs | 2 +- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 5404da705a..1d6d90e22f 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -83,7 +83,7 @@ namespace Tgstation.Server.Host.IO } /// - public async Task CopyDirectory( + public Task CopyDirectory( string src, string dest, IEnumerable ignore, @@ -98,13 +98,7 @@ namespace Tgstation.Server.Host.IO src = ResolvePath(src); dest = ResolvePath(dest); - var allTasks = CopyDirectoryImpl(src, dest, ignore, postCopyCallback, cancellationToken); - - // Special tactics, increase the size of the ThreadPool until we have a 10-1 file-thread ratio. - var allFileTasks = allTasks.Skip(1); - - var unityTask = Task.WhenAll(allFileTasks); - await unityTask.ConfigureAwait(false); + return Task.WhenAll(CopyDirectoryImpl(src, dest, ignore, postCopyCallback, cancellationToken)); } /// @@ -118,24 +112,18 @@ namespace Tgstation.Server.Host.IO if (dest == null) throw new ArgumentNullException(nameof(dest)); - // 0 size buffers prevents unnecessary buffering, async mode just uses the copy buffers See https://github.com/dotnet/runtime/blob/ad8031c813bae48d529ed6d265a2441c4b41fe7b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs#L163-L169 + // tested to hell and back, these are the optimal buffer sizes using var srcStream = new FileStream( ResolvePath(src), FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, - 0, - FileOptions.Asynchronous | FileOptions.SequentialScan); - using var destStream = new FileStream( - ResolvePath(dest), - FileMode.Create, - FileAccess.Write, - FileShare.Read | FileShare.Delete, - 0, + DefaultBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan); + using var destStream = CreateAsyncSequentialWriteStream(dest); // value taken from documentation - await srcStream.CopyToAsync(destStream, DefaultBufferSize, cancellationToken); + await srcStream.CopyToAsync(destStream, 81920, cancellationToken); } /// @@ -251,12 +239,12 @@ namespace Tgstation.Server.Host.IO /// public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken) { - using var file = CreateAsyncWriteStream(path); + using var file = CreateAsyncSequentialWriteStream(path); await file.WriteAsync(contents, cancellationToken); } /// - public FileStream CreateAsyncWriteStream(string path) + public FileStream CreateAsyncSequentialWriteStream(string path) { path = ResolvePath(path); return new FileStream( @@ -430,9 +418,7 @@ namespace Tgstation.Server.Host.IO async Task CopyThisFile() { - // Grab all tasks before firing await subdirCreationTask; - await Task.Yield(); await CopyFile(sourceFile, destFile, cancellationToken); if (postCopyCallback != null) await postCopyCallback(sourceFile, destFile); diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index e46bb74676..a51d130720 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -102,11 +102,11 @@ namespace Tgstation.Server.Host.IO Task> GetFiles(string path, CancellationToken cancellationToken); /// - /// Creates a for writing. + /// Creates an asynchronous for sequential writing. /// /// The path of the file to write, will be truncated. /// The open . - FileStream CreateAsyncWriteStream(string path); + FileStream CreateAsyncSequentialWriteStream(string path); /// /// Writes some to a file at overwriting previous content. diff --git a/src/Tgstation.Server.Host/System/ProcessExecutor.cs b/src/Tgstation.Server.Host/System/ProcessExecutor.cs index 6b035eb23a..806bfea114 100644 --- a/src/Tgstation.Server.Host/System/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/System/ProcessExecutor.cs @@ -292,7 +292,7 @@ namespace Tgstation.Server.Host.System return line; } - using var fileStream = fileRedirect != null ? ioManager.CreateAsyncWriteStream(fileRedirect) : null; + using var fileStream = fileRedirect != null ? ioManager.CreateAsyncSequentialWriteStream(fileRedirect) : null; using var writer = fileStream != null ? new StreamWriter(fileStream) : null; string text; From dd49cb2d8195326f5ecd2cbdca1e84f8f1d9ce48 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 16:27:20 -0400 Subject: [PATCH 31/32] Remove superfluous check --- tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 38803bb154..1c350fda37 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -154,7 +154,6 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(DreamDaemonSecurity.Trusted, newerCompileJob.MinimumSecurityLevel); Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.StagedCompileJob.DMApiVersion); await instanceClient.DreamDaemon.Shutdown(cancellationToken); - await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken); } async Task RunBasicTest(CancellationToken cancellationToken) From 500acaf7314da511191e9460e33543a33f92a711 Mon Sep 17 00:00:00 2001 From: Dominion Date: Mon, 3 Apr 2023 16:43:35 -0400 Subject: [PATCH 32/32] Throttle directory copy operations to 100 files per core --- src/Tgstation.Server.Host/IO/DefaultIOManager.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 1d6d90e22f..7ad9ad7872 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.IO @@ -83,7 +84,7 @@ namespace Tgstation.Server.Host.IO } /// - public Task CopyDirectory( + public async Task CopyDirectory( string src, string dest, IEnumerable ignore, @@ -98,7 +99,8 @@ namespace Tgstation.Server.Host.IO src = ResolvePath(src); dest = ResolvePath(dest); - return Task.WhenAll(CopyDirectoryImpl(src, dest, ignore, postCopyCallback, cancellationToken)); + using var semaphore = new SemaphoreSlim(100 * Environment.ProcessorCount); + await Task.WhenAll(CopyDirectoryImpl(src, dest, ignore, postCopyCallback, semaphore, cancellationToken)); } /// @@ -371,6 +373,7 @@ namespace Tgstation.Server.Host.IO /// The destination directory path. /// Files and folders to ignore at the root level. /// The optional callback called for each source/dest file pair post copy. + /// used to limit degree of parallelism. /// The for the operation. /// A of s representing the running operations. The first returned is always the necessary call to . IEnumerable CopyDirectoryImpl( @@ -378,6 +381,7 @@ namespace Tgstation.Server.Host.IO string dest, IEnumerable ignore, Func postCopyCallback, + SemaphoreSlim semaphore, CancellationToken cancellationToken) { var dir = new DirectoryInfo(src); @@ -388,7 +392,7 @@ namespace Tgstation.Server.Host.IO continue; var checkingSubdirCreationTask = true; - foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, postCopyCallback, cancellationToken)) + foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, postCopyCallback, semaphore, cancellationToken)) { if (subdirCreationTask == null) { @@ -419,6 +423,7 @@ namespace Tgstation.Server.Host.IO async Task CopyThisFile() { await subdirCreationTask; + using var lockContext = await SemaphoreSlimContext.Lock(semaphore, cancellationToken); await CopyFile(sourceFile, destFile, cancellationToken); if (postCopyCallback != null) await postCopyCallback(sourceFile, destFile);