diff --git a/build/TestCommon.props b/build/TestCommon.props
index c932f8919da..b7a3d408c09 100644
--- a/build/TestCommon.props
+++ b/build/TestCommon.props
@@ -8,19 +8,19 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
-
+
diff --git a/build/Version.props b/build/Version.props
index 8bbf9544b6d..ae7135aacbf 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,12 +3,12 @@
- 6.5.0
+ 6.6.0
5.1.0
- 10.3.0
+ 10.4.0
7.0.0
- 13.3.0
- 15.3.0
+ 13.4.0
+ 15.4.0
7.1.2
5.9.0
1.4.1
@@ -17,8 +17,10 @@
netstandard2.0
8
- https://download.visualstudio.microsoft.com/download/pr/00397fee-1bd9-44ef-899b-4504b26e6e96/ab9c73409659f3238d33faee304a8b7c/dotnet-hosting-8.0.4-win.exe
- 10.11.6
+ https://download.visualstudio.microsoft.com/download/pr/751d3fcd-72db-4da2-b8d0-709c19442225/33cc492bde704bfd6d70a2b9109005a0/dotnet-hosting-8.0.6-win.exe
+ 10.11.8
+
+ https://mirror.its.dal.ca/mariadb//mariadb-10.11.8/winx64-packages/mariadb-10.11.8-winx64.msi
1.22.21
diff --git a/build/package/winget/prepare_installer_input_artifacts.ps1 b/build/package/winget/prepare_installer_input_artifacts.ps1
index 8e25606a9dd..d90583d5092 100644
--- a/build/package/winget/prepare_installer_input_artifacts.ps1
+++ b/build/package/winget/prepare_installer_input_artifacts.ps1
@@ -35,7 +35,15 @@ try
try
{
Invoke-WebRequest -Uri $redistUrl -OutFile artifacts/hosting-bundle.exe
- Invoke-WebRequest -Uri $dbRedistUrl -OutFile artifacts/mariadb.msi
+ try
+ {
+ Invoke-WebRequest -Uri $dbRedistUrl -OutFile artifacts/mariadb.msi
+ }
+ catch
+ {
+ $dbRedistUrl = $versionXML.Project.PropertyGroup.TgsMariaDBFallbackRedist
+ Invoke-WebRequest -Uri $dbRedistUrl -OutFile artifacts/mariadb.msi
+ }
} finally {
$ProgressPreference = $previousProgressPreference
}
diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs
index c4ae9dccecc..3881eec855d 100644
--- a/src/Tgstation.Server.Api/Models/Instance.cs
+++ b/src/Tgstation.Server.Api/Models/Instance.cs
@@ -31,9 +31,18 @@ public abstract class Instance : NamedEntity
///
/// The time interval in minutes the repository is automatically pulled and compiles. 0 disables.
///
+ /// Auto-updates intervals start counting when set, TGS is started, or from the completion of the previous update. Incompatible with .
[Required]
public uint? AutoUpdateInterval { get; set; }
+ ///
+ /// A cron expression indicating when auto-updates should trigger. Must be a valid 6 part cron schedule (SECONDS MINUTES HOURS DAY/MONTH MONTH DAY/WEEK). Empty disables.
+ ///
+ /// Updates will not be triggered if the previous update is still running. Incompatible with .
+ [Required]
+ [StringLength(Limits.MaximumStringLength)]
+ public string? AutoUpdateCron { get; set; }
+
///
/// The maximum number of chat bots the may contain.
///
diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
index 502f74b2552..a9a49e05e19 100644
--- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
+++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
index f085719f84e..ef5a83c7a37 100644
--- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
+++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
@@ -11,9 +11,9 @@
-
+
-
+
diff --git a/src/Tgstation.Server.Host/.config/dotnet-tools.json b/src/Tgstation.Server.Host/.config/dotnet-tools.json
index 8e82e300184..4bb3f8cae9b 100644
--- a/src/Tgstation.Server.Host/.config/dotnet-tools.json
+++ b/src/Tgstation.Server.Host/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "8.0.4",
+ "version": "8.0.6",
"commands": [
"dotnet-ef"
]
diff --git a/src/Tgstation.Server.Host/Components/IInstanceCore.cs b/src/Tgstation.Server.Host/Components/IInstanceCore.cs
index 78bce9e3f31..c9fb88becce 100644
--- a/src/Tgstation.Server.Host/Components/IInstanceCore.cs
+++ b/src/Tgstation.Server.Host/Components/IInstanceCore.cs
@@ -45,10 +45,11 @@ public interface IInstanceCore : ILatestCompileJobProvider, IRenameNotifyee
IConfiguration Configuration { get; }
///
- /// Change the for the .
+ /// Change the auto-update timing for the .
///
- /// The new auto update inteval.
+ /// The new auto-update inteval.
+ /// The new auto-update cron schedule.
/// A representing the running operation.
- ValueTask SetAutoUpdateInterval(uint newInterval);
+ ValueTask ScheduleAutoUpdate(uint newInterval, string? newCron);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index a8b9a407212..f6d417eb7a3 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+
+using NCrontab;
+
using Serilog.Context;
using Tgstation.Server.Api.Rights;
@@ -183,7 +187,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
await Task.WhenAll(
- SetAutoUpdateInterval(metadata.Require(x => x.AutoUpdateInterval)).AsTask(),
+ ScheduleAutoUpdate(metadata.Require(x => x.AutoUpdateInterval), metadata.AutoUpdateCron).AsTask(),
Configuration.StartAsync(cancellationToken),
EngineManager.StartAsync(cancellationToken),
Chat.StartAsync(cancellationToken),
@@ -202,7 +206,7 @@ public async Task StopAsync(CancellationToken cancellationToken)
using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
logger.LogDebug("Stopping instance...");
- await SetAutoUpdateInterval(0);
+ await ScheduleAutoUpdate(0, null);
await Watchdog.StopAsync(cancellationToken);
await Task.WhenAll(
Configuration.StopAsync(cancellationToken),
@@ -213,11 +217,13 @@ await Task.WhenAll(
}
///
- public async ValueTask SetAutoUpdateInterval(uint newInterval)
+ public async ValueTask ScheduleAutoUpdate(uint newInterval, string? newCron)
{
+ if (newInterval > 0 && !String.IsNullOrWhiteSpace(newCron))
+ throw new ArgumentException("Only one of newInterval and newCron may be set!");
+
Task toWait;
lock (timerLock)
- {
if (timerTask != null)
{
logger.LogTrace("Cancelling auto-update task");
@@ -229,12 +235,11 @@ public async ValueTask SetAutoUpdateInterval(uint newInterval)
}
else
toWait = Task.CompletedTask;
- }
await toWait;
- if (newInterval == 0)
+ if (newInterval == 0 && String.IsNullOrWhiteSpace(newCron))
{
- logger.LogTrace("New auto-update interval is 0. Not starting task.");
+ logger.LogTrace("Auto-update disabled 0. Not starting task.");
return;
}
@@ -243,12 +248,12 @@ public async ValueTask SetAutoUpdateInterval(uint newInterval)
// race condition, just quit
if (timerTask != null)
{
- logger.LogWarning("Aborting auto update interval change due to race condition!");
+ logger.LogWarning("Aborting auto-update scheduling change due to race condition!");
return;
}
timerCts = new CancellationTokenSource();
- timerTask = TimerLoop(newInterval, timerCts.Token);
+ timerTask = TimerLoop(newInterval, newCron, timerCts.Token);
}
}
@@ -484,82 +489,128 @@ await repo.ResetToOrigin(
/// Pull the repository and compile for every set of given .
///
/// How many minutes the operation should repeat. Does not include running time.
+ /// Alternative cron schedule.
/// The for the operation.
/// A representing the running operation.
#pragma warning disable CA1502 // TODO: Decomplexify
- async Task TimerLoop(uint minutes, CancellationToken cancellationToken)
+ async Task TimerLoop(uint minutes, string? cron, CancellationToken cancellationToken)
{
logger.LogDebug("Entering auto-update loop");
while (true)
try
{
- await asyncDelayer.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : minutes), cancellationToken);
- logger.LogInformation("Beginning auto update...");
- await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), true, cancellationToken);
- try
+ TimeSpan delay;
+ if (!String.IsNullOrWhiteSpace(cron))
{
- var repositoryUpdateJob = Job.Create(Api.Models.JobCode.RepositoryAutoUpdate, null, metadata, RepositoryRights.CancelPendingChanges);
- await jobManager.RegisterOperation(
- repositoryUpdateJob,
- RepositoryAutoUpdateJob,
- cancellationToken);
+ logger.LogTrace("Using cron schedule: {cron}", cron);
+ var schedule = CrontabSchedule.Parse(
+ cron,
+ new CrontabSchedule.ParseOptions
+ {
+ IncludingSeconds = true,
+ });
+ var now = DateTime.UtcNow;
+ var nextOccurrence = schedule.GetNextOccurrence(now);
+ delay = nextOccurrence - now;
+ }
+ else
+ {
+ logger.LogTrace("Using interval: {interval}m", minutes);
- var repoUpdateJobResult = await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, cancellationToken);
- if (repoUpdateJobResult == false)
- {
- logger.LogWarning("Aborting auto-update due to repository update error!");
- continue;
- }
+ delay = TimeSpan.FromMinutes(minutes);
+ }
- Job compileProcessJob;
- using (var repo = await RepositoryManager.LoadRepository(cancellationToken))
- {
- if (repo == null)
- throw new JobException(Api.Models.ErrorCode.RepoMissing);
+ logger.LogInformation("Next auto-update will occur at {time}", DateTimeOffset.UtcNow + delay);
- var deploySha = repo.Head;
- if (deploySha == null)
- {
- logger.LogTrace("Aborting auto update, repository error!");
- continue;
- }
+ // https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay?view=net-8.0#system-threading-tasks-task-delay(system-timespan)
+ const uint DelayMinutesLimit = UInt32.MaxValue - 1;
+ Debug.Assert(DelayMinutesLimit == 4294967294, "Delay limit assertion failure!");
- if (deploySha == LatestCompileJob()?.RevisionInformation.CommitSha)
- {
- logger.LogTrace("Aborting auto update, same revision as latest CompileJob");
- continue;
- }
+ var maxDelayIterations = 0UL;
+ if (delay.TotalMilliseconds >= UInt32.MaxValue)
+ {
+ maxDelayIterations = (ulong)Math.Floor(delay.TotalMilliseconds / DelayMinutesLimit);
+ logger.LogDebug("Breaking interval into {iterationCount} iterations", maxDelayIterations + 1);
+ delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds - (maxDelayIterations * DelayMinutesLimit));
+ }
- // finally set up the job
- compileProcessJob = Job.Create(Api.Models.JobCode.AutomaticDeployment, null, metadata, DreamMakerRights.CancelCompile);
- await jobManager.RegisterOperation(
- compileProcessJob,
- (core, databaseContextFactory, job, progressReporter, jobCancellationToken) =>
- {
- if (core != this)
- throw new InvalidOperationException(DifferentCoreExceptionMessage);
- return DreamMaker.DeploymentProcess(
- job,
- databaseContextFactory,
- progressReporter,
- jobCancellationToken);
- },
- cancellationToken);
+ if (maxDelayIterations > 0)
+ {
+ var longDelayTimeSpan = TimeSpan.FromMilliseconds(DelayMinutesLimit);
+ for (var i = 0UL; i < maxDelayIterations; ++i)
+ {
+ logger.LogTrace("Long delay #{iteration}...", i + 1);
+ await asyncDelayer.Delay(longDelayTimeSpan, cancellationToken);
}
- await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken);
+ logger.LogTrace("Final delay iteration #{iteration}...", maxDelayIterations + 1);
}
- catch (Exception e) when (e is not OperationCanceledException)
+
+ await asyncDelayer.Delay(delay, cancellationToken);
+ logger.LogInformation("Beginning auto update...");
+ await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty(), true, cancellationToken);
+
+ var repositoryUpdateJob = Job.Create(Api.Models.JobCode.RepositoryAutoUpdate, null, metadata, RepositoryRights.CancelPendingChanges);
+ await jobManager.RegisterOperation(
+ repositoryUpdateJob,
+ RepositoryAutoUpdateJob,
+ cancellationToken);
+
+ var repoUpdateJobResult = await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, cancellationToken);
+ if (repoUpdateJobResult == false)
{
- logger.LogWarning(e, "Error in auto update loop!");
+ logger.LogWarning("Aborting auto-update due to repository update error!");
continue;
}
+
+ Job compileProcessJob;
+ using (var repo = await RepositoryManager.LoadRepository(cancellationToken))
+ {
+ if (repo == null)
+ throw new JobException(Api.Models.ErrorCode.RepoMissing);
+
+ var deploySha = repo.Head;
+ if (deploySha == null)
+ {
+ logger.LogTrace("Aborting auto update, repository error!");
+ continue;
+ }
+
+ if (deploySha == LatestCompileJob()?.RevisionInformation.CommitSha)
+ {
+ logger.LogTrace("Aborting auto update, same revision as latest CompileJob");
+ continue;
+ }
+
+ // finally set up the job
+ compileProcessJob = Job.Create(Api.Models.JobCode.AutomaticDeployment, null, metadata, DreamMakerRights.CancelCompile);
+ await jobManager.RegisterOperation(
+ compileProcessJob,
+ (core, databaseContextFactory, job, progressReporter, jobCancellationToken) =>
+ {
+ if (core != this)
+ throw new InvalidOperationException(DifferentCoreExceptionMessage);
+ return DreamMaker.DeploymentProcess(
+ job,
+ databaseContextFactory,
+ progressReporter,
+ jobCancellationToken);
+ },
+ cancellationToken);
+ }
+
+ await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken);
}
catch (OperationCanceledException)
{
logger.LogDebug("Cancelled auto update loop!");
break;
}
+ catch (Exception e)
+ {
+ logger.LogError(e, "Error in auto update loop!");
+ continue;
+ }
logger.LogTrace("Leaving auto update loop...");
}
diff --git a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
index ba2c84d6880..249342fc379 100644
--- a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
@@ -55,7 +55,7 @@ public InstanceWrapper()
public ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Instance.InstanceRenamed(newInstanceName, cancellationToken);
///
- public ValueTask SetAutoUpdateInterval(uint newInterval) => Instance.SetAutoUpdateInterval(newInterval);
+ public ValueTask ScheduleAutoUpdate(uint newInterval, string? newCron) => Instance.ScheduleAutoUpdate(newInterval, newCron);
///
public CompileJob? LatestCompileJob() => Instance.LatestCompileJob();
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index e74d4fdce18..c17b5a455d3 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -13,6 +13,8 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using NCrontab;
+
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
@@ -144,6 +146,10 @@ public async ValueTask Create([FromBody] InstanceCreateRequest mo
if (String.IsNullOrWhiteSpace(model.Name) || String.IsNullOrWhiteSpace(model.Path))
return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceWhitespaceNameOrPath));
+ IActionResult? earlyOut = ValidateCronSetting(model);
+ if (earlyOut != null)
+ return earlyOut;
+
var unNormalizedPath = model.Path;
var targetInstancePath = NormalizePath(unNormalizedPath);
model.Path = targetInstancePath;
@@ -166,7 +172,6 @@ bool InstanceIsChildOf(string otherPath)
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
// Validate it's not a child of any other instance
- IActionResult? earlyOut = null;
ulong countOfOtherInstances = 0;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
@@ -415,9 +420,19 @@ bool CheckModified(Expression> expression, Insta
}
var oldAutoUpdateInterval = originalModel.AutoUpdateInterval!.Value;
+ var oldAutoUpdateCron = originalModel.AutoUpdateCron;
+
+ var earlyOut = ValidateCronSetting(model);
+ if (earlyOut != null)
+ return earlyOut;
+
+ var changedAutoInterval = model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval;
+ var changedAutoCron = model.AutoUpdateCron != null && oldAutoUpdateCron != model.AutoUpdateCron;
+
var renamed = model.Name != null && originalModel.Name != model.Name;
if (CheckModified(x => x.AutoUpdateInterval, InstanceManagerRights.SetAutoUpdate)
+ || CheckModified(x => x.AutoUpdateCron, InstanceManagerRights.SetAutoUpdate)
|| CheckModified(x => x.ConfigurationType, InstanceManagerRights.SetConfiguration)
|| CheckModified(x => x.Name, InstanceManagerRights.Rename)
|| CheckModified(x => x.Online, InstanceManagerRights.SetOnline)
@@ -436,6 +451,11 @@ bool CheckModified(Expression> expression, Insta
return Conflict(new ErrorMessageResponse(ErrorCode.ChatBotMax));
}
+ if (changedAutoCron)
+ model.AutoUpdateInterval = 0;
+ else if (changedAutoInterval)
+ model.AutoUpdateCron = String.Empty;
+
await DatabaseContext.Save(cancellationToken);
if (renamed)
@@ -497,13 +517,13 @@ await jobManager.RegisterOperation(
api.MoveJob = job.ToApi();
}
- if (model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval)
+ if (changedAutoInterval || changedAutoCron)
{
// ignoring retval because we don't care if it's offline
await WithComponentInstanceNullable(
async componentInstance =>
{
- await componentInstance.SetAutoUpdateInterval(model.AutoUpdateInterval.Value);
+ await componentInstance.ScheduleAutoUpdate(model.AutoUpdateInterval!.Value, model.AutoUpdateCron);
return null;
},
originalModel);
@@ -746,6 +766,7 @@ public async ValueTask GrantPermissions(long id, CancellationToke
Online = false,
Path = initialSettings.Path,
AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0,
+ AutoUpdateCron = initialSettings.AutoUpdateCron ?? String.Empty,
ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
RepositorySettings = new RepositorySettings
{
@@ -821,5 +842,29 @@ async ValueTask CheckAccessible(InstanceResponse instanceResponse, CancellationT
.Where(x => x.InstanceId == instanceResponse.Id && x.PermissionSetId == AuthenticationContext.PermissionSet.Id)
.AnyAsync(cancellationToken);
}
+
+ ///
+ /// Validates a given 's setting.
+ ///
+ /// The to validate.
+ /// if has a valid setting, a otherwise.
+ BadRequestObjectResult? ValidateCronSetting(Api.Models.Instance instance)
+ {
+ if (!String.IsNullOrWhiteSpace(instance.AutoUpdateCron))
+ {
+ if ((instance.AutoUpdateInterval.HasValue && instance.AutoUpdateInterval.Value != 0)
+ || (CrontabSchedule.TryParse(
+ instance.AutoUpdateCron,
+ new CrontabSchedule.ParseOptions
+ {
+ IncludingSeconds = true,
+ }) == null))
+ return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
+ }
+ else
+ instance.AutoUpdateCron = String.Empty;
+
+ return null;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
index d4fb8fe0845..3f955613225 100644
--- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
@@ -375,22 +375,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
///
/// Used by unit tests to remind us to setup the correct MSSQL migration downgrades.
///
- internal static readonly Type MSLatestMigration = typeof(MSAddCompilerAdditionalArguments);
+ internal static readonly Type MSLatestMigration = typeof(MSAddCronAutoUpdates);
///
/// Used by unit tests to remind us to setup the correct MYSQL migration downgrades.
///
- internal static readonly Type MYLatestMigration = typeof(MYAddCompilerAdditionalArguments);
+ internal static readonly Type MYLatestMigration = typeof(MYAddCronAutoUpdates);
///
/// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades.
///
- internal static readonly Type PGLatestMigration = typeof(PGAddCompilerAdditionalArguments);
+ internal static readonly Type PGLatestMigration = typeof(PGAddCronAutoUpdates);
///
/// Used by unit tests to remind us to setup the correct SQLite migration downgrades.
///
- internal static readonly Type SLLatestMigration = typeof(SLAddCompilerAdditionalArguments);
+ internal static readonly Type SLLatestMigration = typeof(SLAddCronAutoUpdates);
///
#pragma warning disable CA1502 // Cyclomatic complexity
@@ -419,6 +419,16 @@ public async ValueTask SchemaDowngradeForServerVersion(
string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
+ if (targetVersion < new Version(6, 6, 0))
+ targetMigration = currentDatabaseType switch
+ {
+ DatabaseType.MySql => nameof(MYAddCompilerAdditionalArguments),
+ DatabaseType.PostgresSql => nameof(PGAddCompilerAdditionalArguments),
+ DatabaseType.SqlServer => nameof(MSAddCompilerAdditionalArguments),
+ DatabaseType.Sqlite => nameof(SLAddCompilerAdditionalArguments),
+ _ => BadDatabaseType(),
+ };
+
if (targetVersion < new Version(6, 5, 0))
targetMigration = currentDatabaseType switch
{
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240701234445_MSSwitchTo64BitDeploymentIds.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240701234445_MSSwitchTo64BitDeploymentIds.Designer.cs
new file mode 100644
index 00000000000..b864cbad51e
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20240701234445_MSSwitchTo64BitDeploymentIds.Designer.cs
@@ -0,0 +1,1084 @@
+//
+using System;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ [DbContext(typeof(SqlServerDatabaseContext))]
+ [Migration("20240701234445_MSSwitchTo64BitDeploymentIds")]
+ partial class MSSwitchTo64BitDeploymentIds
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ChannelLimit")
+ .HasColumnType("int");
+
+ b.Property("ConnectionString")
+ .IsRequired()
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Provider")
+ .HasColumnType("int");
+
+ b.Property("ReconnectionInterval")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "Name")
+ .IsUnique();
+
+ b.ToTable("ChatBots");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ChatSettingsId")
+ .HasColumnType("bigint");
+
+ b.Property("DiscordChannelId")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("IrcChannel")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("IsAdminChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("IsSystemChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("IsUpdatesChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("IsWatchdogChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("Tag")
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatSettingsId", "DiscordChannelId")
+ .IsUnique()
+ .HasFilter("[DiscordChannelId] IS NOT NULL");
+
+ b.HasIndex("ChatSettingsId", "IrcChannel")
+ .IsUnique()
+ .HasFilter("[IrcChannel] IS NOT NULL");
+
+ b.ToTable("ChatChannels");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("DMApiMajorVersion")
+ .HasColumnType("int");
+
+ b.Property("DMApiMinorVersion")
+ .HasColumnType("int");
+
+ b.Property("DMApiPatchVersion")
+ .HasColumnType("int");
+
+ b.Property("DirectoryName")
+ .IsRequired()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DmeName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EngineVersion")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("GitHubDeploymentId")
+ .HasColumnType("bigint");
+
+ b.Property("GitHubRepoId")
+ .HasColumnType("bigint");
+
+ b.Property("JobId")
+ .HasColumnType("bigint");
+
+ b.Property("MinimumSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("Output")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RepositoryOrigin")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RevisionInformationId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DirectoryName");
+
+ b.HasIndex("JobId")
+ .IsUnique();
+
+ b.HasIndex("RevisionInformationId");
+
+ b.ToTable("CompileJobs");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AdditionalParameters")
+ .IsRequired()
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AllowWebClient")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("AutoStart")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("DumpOnHealthCheckRestart")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("HealthCheckSeconds")
+ .HasColumnType("bigint");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("LogOutput")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("MapThreads")
+ .HasColumnType("bigint");
+
+ b.Property("Minidumps")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("Port")
+ .HasColumnType("int");
+
+ b.Property("SecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("StartProfiler")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("StartupTimeout")
+ .HasColumnType("bigint");
+
+ b.Property("TopicRequestTimeout")
+ .HasColumnType("bigint");
+
+ b.Property("Visibility")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamDaemonSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ApiValidationPort")
+ .HasColumnType("int");
+
+ b.Property("ApiValidationSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("CompilerAdditionalArguments")
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("ProjectName")
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RequireDMApiValidation")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("Timeout")
+ .IsRequired()
+ .HasColumnType("time");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamMakerSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AutoUpdateInterval")
+ .HasColumnType("bigint");
+
+ b.Property("ChatBotLimit")
+ .HasColumnType("int");
+
+ b.Property("ConfigurationType")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Online")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("SwarmIdentifer")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Path", "SwarmIdentifer")
+ .IsUnique()
+ .HasFilter("[SwarmIdentifer] IS NOT NULL");
+
+ b.ToTable("Instances");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ChatBotRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("ConfigurationRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("DreamDaemonRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("DreamMakerRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("EngineRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("InstancePermissionSetRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("PermissionSetId")
+ .HasColumnType("bigint");
+
+ b.Property("RepositoryRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("PermissionSetId", "InstanceId")
+ .IsUnique();
+
+ b.ToTable("InstancePermissionSets");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CancelRight")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("CancelRightsType")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("Cancelled")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("CancelledById")
+ .HasColumnType("bigint");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ErrorCode")
+ .HasColumnType("bigint");
+
+ b.Property("ExceptionDetails")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("JobCode")
+ .HasColumnType("tinyint");
+
+ b.Property("StartedAt")
+ .IsRequired()
+ .HasColumnType("datetimeoffset");
+
+ b.Property("StartedById")
+ .HasColumnType("bigint");
+
+ b.Property("StoppedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CancelledById");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("StartedById");
+
+ b.ToTable("Jobs");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ExternalUserId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Provider")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("Provider", "ExternalUserId")
+ .IsUnique();
+
+ b.ToTable("OAuthConnections");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AdministrationRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("GroupId")
+ .HasColumnType("bigint");
+
+ b.Property("InstanceManagerRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GroupId")
+ .IsUnique()
+ .HasFilter("[GroupId] IS NOT NULL");
+
+ b.HasIndex("UserId")
+ .IsUnique()
+ .HasFilter("[UserId] IS NOT NULL");
+
+ b.ToTable("PermissionSets");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AccessIdentifier")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CompileJobId")
+ .HasColumnType("bigint");
+
+ b.Property("InitialCompileJobId")
+ .HasColumnType("bigint");
+
+ b.Property("LaunchSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("LaunchVisibility")
+ .HasColumnType("int");
+
+ b.Property("Port")
+ .HasColumnType("int");
+
+ b.Property("ProcessId")
+ .HasColumnType("int");
+
+ b.Property("RebootState")
+ .HasColumnType("int");
+
+ b.Property("TopicPort")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CompileJobId");
+
+ b.HasIndex("InitialCompileJobId");
+
+ b.ToTable("ReattachInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AccessToken")
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AccessUser")
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AutoUpdatesKeepTestMerges")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("AutoUpdatesSynchronize")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("CommitterEmail")
+ .IsRequired()
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CommitterName")
+ .IsRequired()
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreateGitHubDeployments")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("PostTestMergeComment")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("PushTestMergeCommits")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("ShowTestMergeCommitters")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("UpdateSubmodules")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("RepositorySettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("RevisionInformationId")
+ .HasColumnType("bigint");
+
+ b.Property("TestMergeId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RevisionInformationId");
+
+ b.HasIndex("TestMergeId");
+
+ b.ToTable("RevInfoTestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CommitSha")
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("OriginCommitSha")
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)");
+
+ b.Property("Timestamp")
+ .HasColumnType("datetimeoffset");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "CommitSha")
+ .IsUnique();
+
+ b.ToTable("RevisionInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Author")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("BodyAtMerge")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Comment")
+ .HasMaxLength(10000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MergedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("MergedById")
+ .HasColumnType("bigint");
+
+ b.Property("Number")
+ .HasColumnType("int");
+
+ b.Property("PrimaryRevisionInformationId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.Property("TargetCommitSha")
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)");
+
+ b.Property("TitleAtMerge")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MergedById");
+
+ b.HasIndex("PrimaryRevisionInformationId")
+ .IsUnique();
+
+ b.ToTable("TestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CanonicalName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("CreatedAt")
+ .IsRequired()
+ .HasColumnType("datetimeoffset");
+
+ b.Property("CreatedById")
+ .HasColumnType("bigint");
+
+ b.Property("Enabled")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("GroupId")
+ .HasColumnType("bigint");
+
+ b.Property("LastPasswordUpdate")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SystemIdentifier")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CanonicalName")
+ .IsUnique();
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("GroupId");
+
+ b.HasIndex("SystemIdentifier")
+ .IsUnique()
+ .HasFilter("[SystemIdentifier] IS NOT NULL");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("Groups");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("ChatSettings")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Instance");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
+ .WithMany("Channels")
+ .HasForeignKey("ChatSettingsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ChatSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
+ .WithOne()
+ .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
+ .WithMany("CompileJobs")
+ .HasForeignKey("RevisionInformationId")
+ .OnDelete(DeleteBehavior.ClientNoAction)
+ .IsRequired();
+
+ b.Navigation("Job");
+
+ b.Navigation("RevisionInformation");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithOne("DreamDaemonSettings")
+ .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Instance");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithOne("DreamMakerSettings")
+ .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Instance");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("InstancePermissionSets")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet")
+ .WithMany("InstancePermissionSets")
+ .HasForeignKey("PermissionSetId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Instance");
+
+ b.Navigation("PermissionSet");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
+ .WithMany()
+ .HasForeignKey("CancelledById");
+
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("Jobs")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
+ .WithMany()
+ .HasForeignKey("StartedById")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CancelledBy");
+
+ b.Navigation("Instance");
+
+ b.Navigation("StartedBy");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "User")
+ .WithMany("OAuthConnections")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
+ .WithOne("PermissionSet")
+ .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("Tgstation.Server.Host.Models.User", "User")
+ .WithOne("PermissionSet")
+ .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.Navigation("Group");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
+ .WithMany()
+ .HasForeignKey("CompileJobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob")
+ .WithMany()
+ .HasForeignKey("InitialCompileJobId");
+
+ b.Navigation("CompileJob");
+
+ b.Navigation("InitialCompileJob");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithOne("RepositorySettings")
+ .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Instance");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
+ .WithMany("ActiveTestMerges")
+ .HasForeignKey("RevisionInformationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
+ .WithMany("RevisonInformations")
+ .HasForeignKey("TestMergeId")
+ .OnDelete(DeleteBehavior.ClientNoAction)
+ .IsRequired();
+
+ b.Navigation("RevisionInformation");
+
+ b.Navigation("TestMerge");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("RevisionInformations")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Instance");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
+ .WithMany("TestMerges")
+ .HasForeignKey("MergedById")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
+ .WithOne("PrimaryTestMerge")
+ .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("MergedBy");
+
+ b.Navigation("PrimaryRevisionInformation");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
+ .WithMany("CreatedUsers")
+ .HasForeignKey("CreatedById");
+
+ b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
+ .WithMany("Users")
+ .HasForeignKey("GroupId");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("Group");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.Navigation("Channels");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
+ {
+ b.Navigation("ChatSettings");
+
+ b.Navigation("DreamDaemonSettings");
+
+ b.Navigation("DreamMakerSettings");
+
+ b.Navigation("InstancePermissionSets");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("RepositorySettings");
+
+ b.Navigation("RevisionInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
+ {
+ b.Navigation("InstancePermissionSets");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.Navigation("ActiveTestMerges");
+
+ b.Navigation("CompileJobs");
+
+ b.Navigation("PrimaryTestMerge");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
+ {
+ b.Navigation("RevisonInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.Navigation("CreatedUsers");
+
+ b.Navigation("OAuthConnections");
+
+ b.Navigation("PermissionSet");
+
+ b.Navigation("TestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b =>
+ {
+ b.Navigation("PermissionSet")
+ .IsRequired();
+
+ b.Navigation("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240701234445_MSSwitchTo64BitDeploymentIds.cs b/src/Tgstation.Server.Host/Database/Migrations/20240701234445_MSSwitchTo64BitDeploymentIds.cs
new file mode 100644
index 00000000000..7891c837e3d
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20240701234445_MSSwitchTo64BitDeploymentIds.cs
@@ -0,0 +1,40 @@
+using System;
+
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ ///
+ public partial class MSSwitchTo64BitDeploymentIds : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ ArgumentNullException.ThrowIfNull(migrationBuilder);
+
+ migrationBuilder.AlterColumn(
+ name: "GitHubDeploymentId",
+ table: "CompileJobs",
+ type: "bigint",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "int",
+ oldNullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ ArgumentNullException.ThrowIfNull(migrationBuilder);
+
+ migrationBuilder.AlterColumn(
+ name: "GitHubDeploymentId",
+ table: "CompileJobs",
+ type: "int",
+ nullable: true,
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldNullable: true);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20240701234452_MYSwitchTo64BitDeploymentIds.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20240701234452_MYSwitchTo64BitDeploymentIds.Designer.cs
new file mode 100644
index 00000000000..68a80674d7f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20240701234452_MYSwitchTo64BitDeploymentIds.Designer.cs
@@ -0,0 +1,1154 @@
+//
+using System;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ [DbContext(typeof(MySqlDatabaseContext))]
+ [Migration("20240701234452_MYSwitchTo64BitDeploymentIds")]
+ partial class MYSwitchTo64BitDeploymentIds
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ChannelLimit")
+ .IsRequired()
+ .HasColumnType("smallint unsigned");
+
+ b.Property("ConnectionString")
+ .IsRequired()
+ .HasMaxLength(10000)
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ConnectionString"), "utf8mb4");
+
+ b.Property("Enabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
+
+ b.Property("Provider")
+ .HasColumnType("int");
+
+ b.Property("ReconnectionInterval")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "Name")
+ .IsUnique();
+
+ b.ToTable("ChatBots");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ChatSettingsId")
+ .HasColumnType("bigint");
+
+ b.Property("DiscordChannelId")
+ .HasColumnType("bigint unsigned");
+
+ b.Property("IrcChannel")
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("IrcChannel"), "utf8mb4");
+
+ b.Property("IsAdminChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsSystemChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsUpdatesChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsWatchdogChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Tag")
+ .HasMaxLength(10000)
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Tag"), "utf8mb4");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatSettingsId", "DiscordChannelId")
+ .IsUnique();
+
+ b.HasIndex("ChatSettingsId", "IrcChannel")
+ .IsUnique();
+
+ b.ToTable("ChatChannels");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("DMApiMajorVersion")
+ .HasColumnType("int");
+
+ b.Property("DMApiMinorVersion")
+ .HasColumnType("int");
+
+ b.Property("DMApiPatchVersion")
+ .HasColumnType("int");
+
+ b.Property("DirectoryName")
+ .IsRequired()
+ .HasColumnType("char(36)");
+
+ b.Property("DmeName")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("DmeName"), "utf8mb4");
+
+ b.Property("EngineVersion")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("EngineVersion"), "utf8mb4");
+
+ b.Property("GitHubDeploymentId")
+ .HasColumnType("bigint");
+
+ b.Property("GitHubRepoId")
+ .HasColumnType("bigint");
+
+ b.Property("JobId")
+ .HasColumnType("bigint");
+
+ b.Property("MinimumSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("Output")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Output"), "utf8mb4");
+
+ b.Property("RepositoryOrigin")
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("RepositoryOrigin"), "utf8mb4");
+
+ b.Property("RevisionInformationId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DirectoryName");
+
+ b.HasIndex("JobId")
+ .IsUnique();
+
+ b.HasIndex("RevisionInformationId");
+
+ b.ToTable("CompileJobs");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AdditionalParameters")
+ .IsRequired()
+ .HasMaxLength(10000)
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AdditionalParameters"), "utf8mb4");
+
+ b.Property("AllowWebClient")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("AutoStart")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("DumpOnHealthCheckRestart")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("HealthCheckSeconds")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("LogOutput")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("MapThreads")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("Minidumps")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Port")
+ .IsRequired()
+ .HasColumnType("smallint unsigned");
+
+ b.Property("SecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("StartProfiler")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("StartupTimeout")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("TopicRequestTimeout")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("Visibility")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamDaemonSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ApiValidationPort")
+ .IsRequired()
+ .HasColumnType("smallint unsigned");
+
+ b.Property("ApiValidationSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("CompilerAdditionalArguments")
+ .HasMaxLength(10000)
+ .HasColumnType("varchar(10000)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("ProjectName")
+ .HasMaxLength(10000)
+ .HasColumnType("longtext");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ProjectName"), "utf8mb4");
+
+ b.Property("RequireDMApiValidation")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Timeout")
+ .IsRequired()
+ .HasColumnType("time(6)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamMakerSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AutoUpdateInterval")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("ChatBotLimit")
+ .IsRequired()
+ .HasColumnType("smallint unsigned");
+
+ b.Property("ConfigurationType")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
+
+ MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4");
+
+ b.Property