diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs
index 9055a56d31..9d1ca2aac3 100644
--- a/src/Tgstation.Server.Api/Models/DreamMaker.cs
+++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs
@@ -25,5 +25,11 @@ namespace Tgstation.Server.Api.Models
///
[Required]
public DreamDaemonSecurity? ApiValidationSecurityLevel { get; set; }
+
+ ///
+ /// If API validation should be required for a deployment to succeed.
+ ///
+ [Required]
+ public bool? RequireDMApiValidation { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
index 87f4bc13cd..06583edaf0 100644
--- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
@@ -30,7 +30,6 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// The minimum required to run the 's output
///
- [Required]
public DreamDaemonSecurity? MinimumSecurityLevel { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs
index e92f6bfcd3..a78c6b99a9 100644
--- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs
+++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs
@@ -46,6 +46,11 @@ namespace Tgstation.Server.Api.Rights
///
/// User may modify
///
- SetSecurityLevel = 64
+ SetSecurityLevel = 64,
+
+ ///
+ /// User may modify .
+ ///
+ SetApiValidationRequirement = 128,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs
index a52b3cdc2a..118a11df26 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs
@@ -17,6 +17,8 @@ namespace Tgstation.Server.Host.Components.Chat
get => active;
set
{
+ if (active == value)
+ return;
logger.LogTrace(value ? "Activated" : "Deactivated");
active = value;
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
index f17c625e12..fb58f9e9bb 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
@@ -189,18 +189,27 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The for the operation
/// The current
/// The port to use for API validation
+ /// If the API validation is required to complete the deployment.
/// The for the operation
/// A representing the running operation
- async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken)
+ async Task VerifyApi(
+ uint timeout,
+ DreamDaemonSecurity securityLevel,
+ Models.CompileJob job,
+ IByondExecutableLock byondLock,
+ ushort portToUse,
+ bool requireValidate,
+ CancellationToken cancellationToken)
{
- logger.LogTrace("Verifying DMAPI...");
+ logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty);
var launchParameters = new DreamDaemonLaunchParameters
{
AllowWebClient = false,
Port = portToUse,
SecurityLevel = securityLevel,
StartupTimeout = timeout,
- TopicRequestTimeout = 0 // not used
+ TopicRequestTimeout = 0, // not used
+ HeartbeatSeconds = 0 // not used
};
job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider
@@ -219,35 +228,41 @@ namespace Tgstation.Server.Host.Components.Deployment
cancellationToken.ThrowIfCancellationRequested();
}
- if (controller.Lifetime.IsCompleted)
+ if (!controller.Lifetime.IsCompleted)
{
- var validationStatus = controller.ApiValidationStatus;
- logger.LogTrace("API validation status: {0}", validationStatus);
-
- job.DMApiVersion = controller.DMApiVersion;
- switch (validationStatus)
- {
- case ApiValidationStatus.RequiresUltrasafe:
- job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
- return;
- case ApiValidationStatus.RequiresSafe:
- job.MinimumSecurityLevel = DreamDaemonSecurity.Safe;
- return;
- case ApiValidationStatus.RequiresTrusted:
- job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted;
- return;
- case ApiValidationStatus.NeverValidated:
- throw new JobException(ErrorCode.DreamMakerNeverValidated);
- case ApiValidationStatus.BadValidationRequest:
- throw new JobException(ErrorCode.DreamMakerInvalidValidation);
- case ApiValidationStatus.UnaskedValidationRequest:
- default:
- throw new InvalidOperationException(
- $"Session controller returned unexpected ApiValidationStatus: {validationStatus}");
- }
+ if (requireValidate)
+ throw new JobException(ErrorCode.DreamMakerValidationTimeout);
+ controller.Dispose();
}
- throw new JobException(ErrorCode.DreamMakerValidationTimeout);
+ var validationStatus = controller.ApiValidationStatus;
+ logger.LogTrace("API validation status: {0}", validationStatus);
+
+ job.DMApiVersion = controller.DMApiVersion;
+ switch (validationStatus)
+ {
+ case ApiValidationStatus.RequiresUltrasafe:
+ job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
+ return;
+ case ApiValidationStatus.RequiresSafe:
+ job.MinimumSecurityLevel = DreamDaemonSecurity.Safe;
+ return;
+ case ApiValidationStatus.RequiresTrusted:
+ job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted;
+ return;
+ case ApiValidationStatus.NeverValidated:
+ if (requireValidate)
+ throw new JobException(ErrorCode.DreamMakerNeverValidated);
+ job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe;
+ break;
+ case ApiValidationStatus.BadValidationRequest:
+ case ApiValidationStatus.Incompatible:
+ throw new JobException(ErrorCode.DreamMakerInvalidValidation);
+ case ApiValidationStatus.UnaskedValidationRequest:
+ default:
+ throw new InvalidOperationException(
+ $"Session controller returned unexpected ApiValidationStatus: {validationStatus}");
+ }
}
///
@@ -413,7 +428,15 @@ namespace Tgstation.Server.Host.Components.Deployment
ErrorCode.DreamMakerExitCode,
new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}"));
- await VerifyApi(apiValidateTimeout, dreamMakerSettings.ApiValidationSecurityLevel.Value, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false);
+ await VerifyApi(
+ apiValidateTimeout,
+ dreamMakerSettings.ApiValidationSecurityLevel.Value,
+ job,
+ byondLock,
+ dreamMakerSettings.ApiValidationPort.Value,
+ dreamMakerSettings.RequireDMApiValidation.Value,
+ cancellationToken)
+ .ConfigureAwait(false);
}
catch (JobException)
{
diff --git a/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs b/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs
index 1e4077b3e4..c894ce21d2 100644
--- a/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ApiValidationStatus.cs
@@ -33,6 +33,11 @@
///
/// Valid API. The game must be run with a minimum security level of
///
- RequiresUltrasafe
+ RequiresUltrasafe,
+
+ ///
+ /// Valid API, but not compatible with the current TGS version.
+ ///
+ Incompatible,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs b/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs
deleted file mode 100644
index 2ba357cbb9..0000000000
--- a/src/Tgstation.Server.Host/Components/Session/DeadSessionController.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Tgstation.Server.Host.Components.Deployment;
-using Tgstation.Server.Host.Components.Interop.Topic;
-
-namespace Tgstation.Server.Host.Components.Session
-{
- ///
- /// Implements a fake "dead"
- ///
- sealed class DeadSessionController : ISessionController
- {
- ///
- public Task LaunchResult { get; }
-
- ///
- public bool TerminationWasRequested => false;
-
- ///
- public ApiValidationStatus ApiValidationStatus => throw new NotSupportedException();
-
- ///
- public IDmbProvider Dmb { get; }
-
- ///
- public ushort? Port => null;
-
- ///
- public bool ClosePortOnReboot
- {
- get => false;
- set => throw new NotSupportedException();
- }
-
- ///
- public RebootState RebootState => throw new NotSupportedException();
-
- ///
- public Task OnReboot { get; }
-
- ///
- public Task OnPrime { get; }
-
- ///
- public Task Lifetime { get; }
-
- ///
- public Version DMApiVersion => throw new NotSupportedException();
-
- ///
- /// for .
- ///
- readonly object disposeLock;
-
- ///
- /// If the was d
- ///
- bool disposed;
-
- ///
- /// Construct a
- ///
- /// The value of
- public DeadSessionController(IDmbProvider dmbProvider)
- {
- Dmb = dmbProvider ?? throw new ArgumentNullException(nameof(dmbProvider));
- LaunchResult = Task.FromResult(new LaunchResult
- {
- StartupTime = TimeSpan.FromSeconds(0)
- });
- Lifetime = Task.FromResult(-1);
- OnReboot = Extensions.TaskExtensions.InfiniteTask();
- OnPrime = Extensions.TaskExtensions.InfiniteTask();
- disposeLock = new object();
- }
-
- ///
- public void Dispose()
- {
- lock (disposeLock)
- {
- if (disposed)
- return;
- disposed = true;
- }
-
- Dmb.Dispose();
- }
-
- ///
- public void EnableCustomChatCommands() => throw new NotSupportedException();
-
- ///
- public ReattachInformation Release() => throw new NotSupportedException();
-
- ///
- public void ResetRebootState() => throw new NotSupportedException();
-
- ///
- public Task SendCommand(TopicParameters parameters, CancellationToken cancellationToken) => throw new NotSupportedException();
-
- ///
- public void SetHighPriority() => throw new NotSupportedException();
-
- ///
- public Task SetPort(ushort newPort, CancellationToken cancellatonToken) => throw new NotSupportedException();
-
- ///
- public Task SetRebootState(RebootState newRebootState, CancellationToken cancellationToken) => throw new NotSupportedException();
-
- ///
- public void ReplaceDmbProvider(IDmbProvider newProvider) => throw new NotSupportedException();
-
- ///
- public void Suspend() => throw new NotSupportedException();
-
- ///
- public void Resume() => throw new NotSupportedException();
-
- ///
- public Task InstanceRenamed(string newInstanceName, CancellationToken cancellationToken) => Task.CompletedTask;
-
- ///
- public Task CreateDump(string outputFile, CancellationToken cancellationToken) => throw new NotSupportedException();
- }
-}
diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs
index e4386d4dd9..21439f6cc2 100644
--- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs
@@ -62,6 +62,11 @@ namespace Tgstation.Server.Host.Components.Session
///
Task OnPrime { get; }
+ ///
+ /// If the DMAPI may be used this session.
+ ///
+ bool DMApiAvailable { get; }
+
///
/// Releases the without terminating it. Also calls
///
diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs
index 5b110fd8c3..89a7e3d4aa 100644
--- a/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs
@@ -36,12 +36,5 @@ namespace Tgstation.Server.Host.Components.Session
Task Reattach(
ReattachInformation reattachInformation,
CancellationToken cancellationToken);
-
- ///
- /// Creates a that appears to have started and died with exit code -1
- ///
- /// The for the
- /// A dead
- ISessionController CreateDeadSession(IDmbProvider dmbProvider);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
index 601c952136..bdda5618d3 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
@@ -91,6 +91,9 @@ namespace Tgstation.Server.Host.Components.Session
///
public Task OnPrime => primeTcs.Task;
+ ///
+ public bool DMApiAvailable => reattachInformation.Dmb.CompileJob.DMApiVersion?.Major == DMApiConstants.Version.Major;
+
///
/// The up to date
///
@@ -201,6 +204,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The value of
/// The optional time to wait before failing the
/// If this is a reattached session.
+ /// If this is a DMAPI validation session.
public SessionController(
ReattachInformation reattachInformation,
Api.Models.Instance metadata,
@@ -213,7 +217,8 @@ namespace Tgstation.Server.Host.Components.Session
IAssemblyInformationProvider assemblyInformationProvider,
ILogger logger,
uint? startupTimeout,
- bool reattached)
+ bool reattached,
+ bool apiValidate)
{
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
@@ -221,11 +226,22 @@ namespace Tgstation.Server.Host.Components.Session
this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock));
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
this.chatTrackingContext = chatTrackingContext ?? throw new ArgumentNullException(nameof(chatTrackingContext));
- bridgeRegistration = bridgeRegistrar?.RegisterHandler(this) ?? throw new ArgumentNullException(nameof(bridgeRegistrar));
+ if (bridgeRegistrar == null)
+ throw new ArgumentNullException(nameof(bridgeRegistrar));
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
- this.chatTrackingContext.SetChannelSink(this);
+ if (apiValidate || DMApiAvailable)
+ {
+ bridgeRegistration = bridgeRegistrar.RegisterHandler(this);
+ this.chatTrackingContext.SetChannelSink(this);
+ }
+ else
+ logger.LogTrace(
+ "Not registering session with {0} DMAPI version for interop!",
+ reattachInformation.Dmb.CompileJob.DMApiVersion == null
+ ? "no"
+ : $"incompatible ({reattachInformation.Dmb.CompileJob.DMApiVersion})");
portClosedForReboot = false;
disposed = false;
@@ -291,7 +307,7 @@ namespace Tgstation.Server.Host.Components.Session
}
process.Dispose();
- bridgeRegistration.Dispose();
+ bridgeRegistration?.Dispose();
reattachInformation.Dmb?.Dispose(); // will be null when released
chatTrackingContext.Dispose();
reattachTopicCts.Dispose();
@@ -452,6 +468,15 @@ namespace Tgstation.Server.Host.Components.Session
};
DMApiVersion = parameters.Version;
+ if (DMApiVersion.Major != DMApiConstants.Version.Major)
+ {
+ apiValidationStatus = ApiValidationStatus.Incompatible;
+ return new BridgeResponse
+ {
+ ErrorMessage = "Incompatible dmApiVersion!"
+ };
+ }
+
switch (parameters.MinimumSecurityLevel)
{
case DreamDaemonSecurity.Ultrasafe:
@@ -514,7 +539,7 @@ namespace Tgstation.Server.Host.Components.Session
}
///
- public void EnableCustomChatCommands() => chatTrackingContext.Active = true;
+ public void EnableCustomChatCommands() => chatTrackingContext.Active = DMApiAvailable;
///
public ReattachInformation Release()
@@ -535,6 +560,9 @@ namespace Tgstation.Server.Host.Components.Session
///
public async Task SendCommand(TopicParameters parameters, CancellationToken cancellationToken)
{
+ if (parameters == null)
+ throw new ArgumentNullException(nameof(parameters));
+
if (Lifetime.IsCompleted)
{
logger.LogWarning(
@@ -543,6 +571,12 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
+ if (!DMApiAvailable)
+ {
+ logger.LogTrace("Not sending topic request {0} to server without/with incompatible DMAPI!", parameters.CommandType);
+ return null;
+ }
+
parameters.AccessIdentifier = reattachInformation.AccessIdentifier;
var json = JsonConvert.SerializeObject(parameters, DMApiConstants.SerializerSettings);
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
index aa74d8600c..3b7a617e41 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
@@ -188,6 +188,7 @@ namespace Tgstation.Server.Host.Components.Session
bool apiValidate,
CancellationToken cancellationToken)
{
+ logger.LogTrace("Begin session launch...");
if (!launchParameters.Port.HasValue)
throw new InvalidOperationException("Given port is null!");
switch (dmbProvider.CompileJob.MinimumSecurityLevel)
@@ -212,6 +213,11 @@ namespace Tgstation.Server.Host.Components.Session
var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);
try
{
+ logger.LogDebug(
+ "Launching session with CompileJob {0}...",
+ byondLock.Version.Semver(),
+ dmbProvider.CompileJob.Id);
+
if (launchParameters.SecurityLevel == DreamDaemonSecurity.Trusted)
await byondLock.TrustDmbPath(ioManager.ConcatPath(dmbProvider.Directory, dmbProvider.DmbName), cancellationToken).ConfigureAwait(false);
@@ -248,6 +254,9 @@ namespace Tgstation.Server.Host.Components.Session
// See https://github.com/tgstation/tgstation-server/issues/719
var noShellExecute = !platformIdentifier.IsWindows;
+ if (!apiValidate && dmbProvider.CompileJob.DMApiVersion == null)
+ logger.LogDebug("Session will have no DMAPI support!");
+
// launch dd
var process = processExecutor.LaunchProcess(
byondLock.DreamDaemonPath,
@@ -332,7 +341,8 @@ namespace Tgstation.Server.Host.Components.Session
assemblyInformationProvider,
loggerFactory.CreateLogger(),
launchParameters.StartupTimeout,
- false);
+ false,
+ apiValidate);
return sessionController;
}
@@ -366,13 +376,20 @@ namespace Tgstation.Server.Host.Components.Session
if (reattachInformation == null)
throw new ArgumentNullException(nameof(reattachInformation));
+ logger.LogTrace("Begin session reattach...");
var byondTopicSender = topicClientFactory.CreateTopicClient(reattachInformation.TopicRequestTimeout);
var chatTrackingContext = chat.CreateTrackingContext();
try
{
var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);
+
try
{
+ logger.LogDebug(
+ "Attaching to session PID: {0}, CompileJob: {1}...",
+ reattachInformation.ProcessId,
+ reattachInformation.Dmb.CompileJob.Id);
+
var process = processExecutor.GetProcess(reattachInformation.ProcessId);
if (process == null)
return null;
@@ -399,7 +416,8 @@ namespace Tgstation.Server.Host.Components.Session
assemblyInformationProvider,
loggerFactory.CreateLogger(),
null,
- true);
+ true,
+ false);
process = null;
byondLock = null;
@@ -423,9 +441,6 @@ namespace Tgstation.Server.Host.Components.Session
}
}
- ///
- public ISessionController CreateDeadSession(IDmbProvider dmbProvider) => new DeadSessionController(dmbProvider);
-
///
/// Create .
///
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
index e6fd0777e0..f3ecaa3ac6 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
@@ -255,6 +255,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
protected virtual Task HandleNewDmbAvailable(CancellationToken cancellationToken)
{
gracefulRebootRequired = true;
+ if (Server.Dmb.CompileJob.DMApiVersion == null)
+ return Chat.SendWatchdogMessage(
+ "A new deployment has been made but cannot be applied automatically as the currently running server has no DMAPI. Please manually reboot the server to apply the update.",
+ true,
+ cancellationToken);
return Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index 2142835fe8..2fd049a200 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -609,6 +609,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value;
var heartbeat = heartbeatSeconds == 0
+ || !controller.DMApiAvailable
? Extensions.TaskExtensions.InfiniteTask()
: Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds));
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
index 8839a3af5c..7c5fd60e24 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -170,7 +170,11 @@ namespace Tgstation.Server.Host.Controllers
/// Changes applied successfully. The updated settings will be not be returned due to permissions.
/// The database entity for the requested instance could not be retrieved. The instance was likely detached.
[HttpPost]
- [TgsAuthorize(DreamMakerRights.SetDme | DreamMakerRights.SetApiValidationPort | DreamMakerRights.SetApiValidationPort)]
+ [TgsAuthorize(
+ DreamMakerRights.SetDme
+ | DreamMakerRights.SetApiValidationPort
+ | DreamMakerRights.SetSecurityLevel
+ | DreamMakerRights.SetApiValidationRequirement)]
[ProducesResponseType(typeof(DreamMaker), 200)]
[ProducesResponseType(204)]
[ProducesResponseType(typeof(ErrorMessage), 410)]
@@ -215,6 +219,13 @@ namespace Tgstation.Server.Host.Controllers
hostModel.ApiValidationSecurityLevel = model.ApiValidationSecurityLevel;
}
+ if (model.RequireDMApiValidation.HasValue)
+ {
+ if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationRequirement))
+ return Forbid();
+ hostModel.RequireDMApiValidation = model.RequireDMApiValidation;
+ }
+
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
if ((AuthenticationContext.GetRight(RightsType.DreamMaker) & (ulong)DreamMakerRights.Read) == 0)
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index ec5ca653d9..bf274fe9f3 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -256,7 +256,8 @@ namespace Tgstation.Server.Host.Controllers
DreamMakerSettings = new DreamMakerSettings
{
ApiValidationPort = 1339,
- ApiValidationSecurityLevel = DreamDaemonSecurity.Safe
+ ApiValidationSecurityLevel = DreamDaemonSecurity.Safe,
+ RequireDMApiValidation = true
},
Name = model.Name,
Online = false,
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.Designer.cs
new file mode 100644
index 0000000000..991f760efb
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.Designer.cs
@@ -0,0 +1,772 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ [DbContext(typeof(SqlServerDatabaseContext))]
+ [Migration("20200705163512_MSAllowNullDMApi")]
+ partial class MSAllowNullDMApi
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "3.1.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128)
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("ChannelLimit")
+ .HasColumnType("int");
+
+ b.Property("ConnectionString")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(100)")
+ .HasMaxLength(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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("ChatSettingsId")
+ .HasColumnType("bigint");
+
+ b.Property("DiscordChannelId")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("IrcChannel")
+ .HasColumnType("nvarchar(100)")
+ .HasMaxLength(100);
+
+ b.Property("IsAdminChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("IsUpdatesChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("IsWatchdogChannel")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("Tag")
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ 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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ 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("JobId")
+ .HasColumnType("bigint");
+
+ b.Property("MinimumSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("Output")
+ .IsRequired()
+ .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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("AllowWebClient")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("AutoStart")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("HeartbeatSeconds")
+ .HasColumnType("bigint");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Port")
+ .HasColumnType("int");
+
+ b.Property("SecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("StartupTimeout")
+ .HasColumnType("bigint");
+
+ b.Property("TopicRequestTimeout")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamDaemonSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("ApiValidationPort")
+ .HasColumnType("int");
+
+ b.Property("ApiValidationSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("ProjectName")
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("RequireDMApiValidation")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DreamMakerSettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("AutoUpdateInterval")
+ .HasColumnType("bigint");
+
+ b.Property("ChatBotLimit")
+ .HasColumnType("int");
+
+ b.Property("ConfigurationType")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("Online")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Path")
+ .IsUnique();
+
+ b.ToTable("Instances");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ 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("InstanceUserRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("RepositoryRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("UserId", "InstanceId")
+ .IsUnique();
+
+ b.ToTable("InstanceUsers");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ 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.ReattachInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("AccessIdentifier")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CompileJobId")
+ .HasColumnType("bigint");
+
+ b.Property("LaunchSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("Port")
+ .HasColumnType("int");
+
+ b.Property("ProcessId")
+ .HasColumnType("int");
+
+ b.Property("RebootState")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CompileJobId");
+
+ b.ToTable("ReattachInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("AccessToken")
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("AccessUser")
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("AutoUpdatesKeepTestMerges")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("AutoUpdatesSynchronize")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("CommitterEmail")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("CommitterName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("PostTestMergeComment")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("PushTestMergeCommits")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("ShowTestMergeCommitters")
+ .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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ 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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("CommitSha")
+ .IsRequired()
+ .HasColumnType("nvarchar(40)")
+ .HasMaxLength(40);
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("OriginCommitSha")
+ .IsRequired()
+ .HasColumnType("nvarchar(40)")
+ .HasMaxLength(40);
+
+ 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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("Author")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("BodyAtMerge")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Comment")
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("MergedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("MergedById")
+ .HasColumnType("bigint");
+
+ b.Property("Number")
+ .HasColumnType("int");
+
+ b.Property("PrimaryRevisionInformationId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.Property("PullRequestRevision")
+ .IsRequired()
+ .HasColumnType("nvarchar(40)")
+ .HasMaxLength(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")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ b.Property("AdministrationRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("CanonicalName")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("CreatedAt")
+ .IsRequired()
+ .HasColumnType("datetimeoffset");
+
+ b.Property("CreatedById")
+ .HasColumnType("bigint");
+
+ b.Property("Enabled")
+ .IsRequired()
+ .HasColumnType("bit");
+
+ b.Property("InstanceManagerRights")
+ .HasColumnType("decimal(20,0)");
+
+ b.Property("LastPasswordUpdate")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasMaxLength(10000);
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SystemIdentifier")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CanonicalName")
+ .IsUnique();
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("SystemIdentifier")
+ .IsUnique()
+ .HasFilter("[SystemIdentifier] IS NOT NULL");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("ChatSettings")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
+ .WithMany("Channels")
+ .HasForeignKey("ChatSettingsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ 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();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("InstanceUsers")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.User", null)
+ .WithMany("InstanceUsers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
+ .WithMany()
+ .HasForeignKey("CompileJobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("RevisionInformations")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
+ .WithMany("CreatedUsers")
+ .HasForeignKey("CreatedById");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs
new file mode 100644
index 0000000000..ee823e5fb3
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163512_MSAllowNullDMApi.cs
@@ -0,0 +1,50 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ ///
+ /// Update models for making the DMAPI optional for MSSQL.
+ ///
+ public partial class MSAllowNullDMApi : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ if (migrationBuilder == null)
+ throw new ArgumentNullException(nameof(migrationBuilder));
+
+ migrationBuilder.AddColumn(
+ name: "RequireDMApiValidation",
+ table: "DreamMakerSettings",
+ nullable: false,
+ defaultValue: true);
+
+ migrationBuilder.AlterColumn(
+ name: "MinimumSecurityLevel",
+ table: "CompileJobs",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "int");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ if (migrationBuilder == null)
+ throw new ArgumentNullException(nameof(migrationBuilder));
+
+ migrationBuilder.DropColumn(
+ name: "RequireDMApiValidation",
+ table: "DreamMakerSettings");
+
+ migrationBuilder.AlterColumn(
+ name: "MinimumSecurityLevel",
+ table: "CompileJobs",
+ type: "int",
+ nullable: false,
+ oldClrType: typeof(int),
+ oldNullable: true);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.Designer.cs
new file mode 100644
index 0000000000..8d62f69467
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.Designer.cs
@@ -0,0 +1,762 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ [DbContext(typeof(MySqlDatabaseContext))]
+ [Migration("20200705163547_MYAllowNullDMApi")]
+ partial class MYAllowNullDMApi
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "3.1.5")
+ .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()
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("Enabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(100) CHARACTER SET utf8mb4")
+ .HasMaxLength(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")
+ .HasColumnType("varchar(100) CHARACTER SET utf8mb4")
+ .HasMaxLength(100);
+
+ b.Property("IsAdminChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsUpdatesChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsWatchdogChannel")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Tag")
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatSettingsId", "DiscordChannelId")
+ .IsUnique();
+
+ b.HasIndex("ChatSettingsId", "IrcChannel")
+ .IsUnique();
+
+ b.ToTable("ChatChannels");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("ByondVersion")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET 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 CHARACTER SET utf8mb4");
+
+ b.Property("JobId")
+ .HasColumnType("bigint");
+
+ b.Property("MinimumSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("Output")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET 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("AllowWebClient")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("AutoStart")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("HeartbeatSeconds")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Port")
+ .IsRequired()
+ .HasColumnType("smallint unsigned");
+
+ b.Property("SecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("StartupTimeout")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ b.Property("TopicRequestTimeout")
+ .IsRequired()
+ .HasColumnType("int unsigned");
+
+ 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")
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("RequireDMApiValidation")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ 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()
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("Online")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Path")
+ .IsUnique();
+
+ b.ToTable("Instances");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", 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("InstanceUserRights")
+ .HasColumnType("bigint unsigned");
+
+ b.Property("RepositoryRights")
+ .HasColumnType("bigint unsigned");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("UserId", "InstanceId")
+ .IsUnique();
+
+ b.ToTable("InstanceUsers");
+ });
+
+ 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 CHARACTER SET utf8mb4");
+
+ b.Property("ErrorCode")
+ .HasColumnType("int unsigned");
+
+ b.Property("ExceptionDetails")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("StartedAt")
+ .IsRequired()
+ .HasColumnType("datetime(6)");
+
+ b.Property("StartedById")
+ .HasColumnType("bigint");
+
+ b.Property("StoppedAt")
+ .HasColumnType("datetime(6)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CancelledById");
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("StartedById");
+
+ b.ToTable("Jobs");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("AccessIdentifier")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property("CompileJobId")
+ .HasColumnType("bigint");
+
+ b.Property("LaunchSecurityLevel")
+ .HasColumnType("int");
+
+ b.Property("Port")
+ .HasColumnType("smallint unsigned");
+
+ b.Property("ProcessId")
+ .HasColumnType("int");
+
+ b.Property("RebootState")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CompileJobId");
+
+ b.ToTable("ReattachInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("AccessToken")
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("AccessUser")
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("AutoUpdatesKeepTestMerges")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("AutoUpdatesSynchronize")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("CommitterEmail")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("CommitterName")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("PostTestMergeComment")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("PushTestMergeCommits")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("ShowTestMergeCommitters")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("RepositorySettings");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("RevisionInformationId")
+ .HasColumnType("bigint");
+
+ b.Property("TestMergeId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RevisionInformationId");
+
+ b.HasIndex("TestMergeId");
+
+ b.ToTable("RevInfoTestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("CommitSha")
+ .IsRequired()
+ .HasColumnType("varchar(40) CHARACTER SET utf8mb4")
+ .HasMaxLength(40);
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("OriginCommitSha")
+ .IsRequired()
+ .HasColumnType("varchar(40) CHARACTER SET utf8mb4")
+ .HasMaxLength(40);
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "CommitSha")
+ .IsUnique();
+
+ b.ToTable("RevisionInformations");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("Author")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property("BodyAtMerge")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property("Comment")
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("MergedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("MergedById")
+ .HasColumnType("bigint");
+
+ b.Property("Number")
+ .HasColumnType("int");
+
+ b.Property("PrimaryRevisionInformationId")
+ .IsRequired()
+ .HasColumnType("bigint");
+
+ b.Property("PullRequestRevision")
+ .IsRequired()
+ .HasColumnType("varchar(40) CHARACTER SET utf8mb4")
+ .HasMaxLength(40);
+
+ b.Property("TitleAtMerge")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MergedById");
+
+ b.HasIndex("PrimaryRevisionInformationId")
+ .IsUnique();
+
+ b.ToTable("TestMerges");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ b.Property("AdministrationRights")
+ .HasColumnType("bigint unsigned");
+
+ b.Property("CanonicalName")
+ .IsRequired()
+ .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+ b.Property("CreatedAt")
+ .IsRequired()
+ .HasColumnType("datetime(6)");
+
+ b.Property("CreatedById")
+ .HasColumnType("bigint");
+
+ b.Property("Enabled")
+ .IsRequired()
+ .HasColumnType("tinyint(1)");
+
+ b.Property("InstanceManagerRights")
+ .HasColumnType("bigint unsigned");
+
+ b.Property("LastPasswordUpdate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext CHARACTER SET utf8mb4")
+ .HasMaxLength(10000);
+
+ b.Property("PasswordHash")
+ .HasColumnType("longtext CHARACTER SET utf8mb4");
+
+ b.Property("SystemIdentifier")
+ .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CanonicalName")
+ .IsUnique();
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("SystemIdentifier")
+ .IsUnique();
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("ChatSettings")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
+ .WithMany("Channels")
+ .HasForeignKey("ChatSettingsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
+ .WithOne()
+ .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
+ .WithMany("CompileJobs")
+ .HasForeignKey("RevisionInformationId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("InstanceUsers")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Tgstation.Server.Host.Models.User", null)
+ .WithMany("InstanceUsers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
+ .WithMany()
+ .HasForeignKey("CompileJobId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
+ .WithMany("RevisionInformations")
+ .HasForeignKey("InstanceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ 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();
+ });
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
+ {
+ b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
+ .WithMany("CreatedUsers")
+ .HasForeignKey("CreatedById");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs
new file mode 100644
index 0000000000..cd189a8fbc
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163547_MYAllowNullDMApi.cs
@@ -0,0 +1,50 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ ///
+ /// Update models for making the DMAPI optional for MYSQL.
+ ///
+ public partial class MYAllowNullDMApi : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ if (migrationBuilder == null)
+ throw new ArgumentNullException(nameof(migrationBuilder));
+
+ migrationBuilder.AddColumn(
+ name: "RequireDMApiValidation",
+ table: "DreamMakerSettings",
+ nullable: false,
+ defaultValue: true);
+
+ migrationBuilder.AlterColumn(
+ name: "MinimumSecurityLevel",
+ table: "CompileJobs",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "int");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ if (migrationBuilder == null)
+ throw new ArgumentNullException(nameof(migrationBuilder));
+
+ migrationBuilder.DropColumn(
+ name: "RequireDMApiValidation",
+ table: "DreamMakerSettings");
+
+ migrationBuilder.AlterColumn(
+ name: "MinimumSecurityLevel",
+ table: "CompileJobs",
+ type: "int",
+ nullable: false,
+ oldClrType: typeof(int),
+ oldNullable: true);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.Designer.cs
new file mode 100644
index 0000000000..1b1f9880dc
--- /dev/null
+++ b/src/Tgstation.Server.Host/Database/Migrations/20200705163624_PGAllowNullDMApi.Designer.cs
@@ -0,0 +1,769 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace Tgstation.Server.Host.Database.Migrations
+{
+ [DbContext(typeof(PostgresSqlDatabaseContext))]
+ [Migration("20200705163624_PGAllowNullDMApi")]
+ partial class PGAllowNullDMApi
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
+ .HasAnnotation("ProductVersion", "3.1.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
+
+ b.Property("ChannelLimit")
+ .HasColumnType("integer");
+
+ b.Property("ConnectionString")
+ .IsRequired()
+ .HasColumnType("character varying(10000)")
+ .HasMaxLength(10000);
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("InstanceId")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("character varying(100)")
+ .HasMaxLength(100);
+
+ b.Property