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
diff --git a/build/Version.props b/build/Version.props
index e58434a58c..8802116119 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,13 +3,13 @@
- 5.7.2
+ 5.7.3
4.4.0
9.9.0
10.3.0
11.3.0
- 6.2.0
- 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 3744a95a0f..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.0"
+#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 36610b1242..664875a70d 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)
@@ -128,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/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(
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);
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];
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
index e2f9fd442a..67e59737cb 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);
@@ -411,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/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!");
+ }
}
///
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..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!");
}
}
@@ -538,7 +551,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/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..0f76b5993f 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
}
///
@@ -320,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;
@@ -441,14 +448,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..2bbff38173 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);
}
///
@@ -727,7 +716,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!");
@@ -781,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.
///
@@ -803,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
@@ -814,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
{
@@ -836,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();
@@ -1100,5 +1121,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));
+ }
}
}
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/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));
}
diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
index b7a197143d..bf77cbd332 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;
}
@@ -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
{
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