diff --git a/src/Tgstation.Server.Api/Models/CompilerStatus.cs b/src/Tgstation.Server.Api/Models/CompilerStatus.cs index dc71d316f9..9cb0a7a5ed 100644 --- a/src/Tgstation.Server.Api/Models/CompilerStatus.cs +++ b/src/Tgstation.Server.Api/Models/CompilerStatus.cs @@ -32,6 +32,10 @@ /// Verifying, /// + /// Post-compile scripts are running + /// + PostCompile, + /// /// The compile results are being duplicated /// Duplicating, @@ -40,10 +44,6 @@ /// Symlinking, /// - /// Post-compile scripts are running - /// - PostCompile, - /// /// A failed compile job is being erased /// Cleanup diff --git a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs index 094332d1e4..68804af41b 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemonSecurity.cs @@ -14,7 +14,7 @@ /// Safe, /// - /// Server will not be able to run shell commands or access anything but temporary files + /// Server will not be able to run shell commands or access anything but temporary files. Currently unsupported! /// Ultrasafe } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 858bc3792b..4a62f8130e 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -100,22 +100,24 @@ namespace Tgstation.Server.Host.Components.Compiler /// Run a quick DD instance to test the DMAPI is installed on the target code /// /// The timeout in seconds for validation + /// The level to use to validate the API /// The for the operation /// The current /// The for the operation /// A resulting in if the DMAPI was successfully validated, otherwise - async Task VerifyApi(uint timeout, Models.CompileJob job, IByondExecutableLock byondLock, CancellationToken cancellationToken) + async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, CancellationToken cancellationToken) { + logger.LogTrace("Verifying DMAPI..."); var launchParameters = new DreamDaemonLaunchParameters { AllowWebClient = false, PrimaryPort = 0, //pick any port - SecurityLevel = DreamDaemonSecurity.Safe, //all it needs to read the file and exit + SecurityLevel = securityLevel, //all it needs to read the file and exit StartupTimeout = timeout }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Join('.', job.DmeName, DmbExtension), job); + var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job); var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false)) @@ -129,9 +131,14 @@ namespace Tgstation.Server.Host.Components.Compiler cancellationToken.ThrowIfCancellationRequested(); } if (!controller.Lifetime.IsCompleted) + { + logger.LogDebug("API validation timed out!"); return false; + } - return controller.ApiValidated; + var validated = controller.ApiValidated; + logger.LogTrace("API valid: {0}", validated); + return validated; } } @@ -167,6 +174,7 @@ namespace Tgstation.Server.Host.Components.Compiler var dmTcs = new TaskCompletionSource(); dm.Exited += (a, b) => dmTcs.TrySetResult(null); + logger.LogTrace("Running DreamMaker..."); dm.Start(); dm.BeginOutputReadLine(); dm.BeginErrorReadLine(); @@ -184,8 +192,10 @@ namespace Tgstation.Server.Host.Components.Compiler } } - job.Output = OutputList.ToString(); job.ExitCode = dm.ExitCode; + logger.LogDebug("DreamMaker exit code: {0}", job.ExitCode); + job.Output = OutputList.ToString(); + logger.LogTrace("DreamMaker output: {0}", job.Output); } } @@ -209,7 +219,18 @@ namespace Tgstation.Server.Host.Components.Compiler var dmeModifications = await dmeModificationsTask.ConfigureAwait(false); if (dmeModifications == null || dmeModifications.TotalDmeOverwrite) + { + if (dmeModifications != null) + logger.LogDebug(".dme replacement configured!"); + else + logger.LogTrace("No .dme modifications required."); return; + } + + if (dmeModifications.HeadIncludeLine != null) + logger.LogDebug("Head .dme include line: {0}", dmeModifications.HeadIncludeLine); + if (dmeModifications.TailIncludeLine != null) + logger.LogDebug("Tail .dme include line: {0}", dmeModifications.TailIncludeLine); var dmeLines = new List(dme.Split(new[] { Environment.NewLine }, StringSplitOptions.None)); for (var I = 0; I < dmeLines.Count; ++I) @@ -232,8 +253,14 @@ namespace Tgstation.Server.Host.Components.Compiler } /// - public async Task Compile(string projectName, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) + public async Task Compile(string projectName, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) { + if (repository == null) + throw new ArgumentNullException(nameof(repository)); + + if (securityLevel == DreamDaemonSecurity.Ultrasafe) + throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, "Cannot compile with ultrasafe security!"); + logger.LogTrace("Begin Compile"); var job = new Models.CompileJob @@ -242,11 +269,14 @@ namespace Tgstation.Server.Host.Components.Compiler DmeName = projectName }; + logger.LogTrace("Compile output GUID: {0}", job.DirectoryName); + lock (this) { if(Status != CompilerStatus.Idle) { job.Output = "There is already a compile in progress!"; + logger.LogInformation(job.Output); return job; } @@ -260,6 +290,7 @@ namespace Tgstation.Server.Host.Components.Compiler async Task CleanupFailedCompile() { + logger.LogTrace("Cleaning compile directory..."); Status = CompilerStatus.Cleanup; try { @@ -273,6 +304,7 @@ namespace Tgstation.Server.Host.Components.Compiler try { + logger.LogTrace("Copying repository to game directory..."); //copy the repository var fullDirA = ioManager.ResolvePath(dirA); var repoOrigin = repository.Origin; @@ -287,16 +319,20 @@ namespace Tgstation.Server.Host.Components.Compiler if (job.DmeName == null) { + logger.LogTrace("Searching for available .dmes..."); var path = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); if (path == default) { job.Output = "Unable to find any .dme!"; + logger.LogWarning(job.Output); return job; } var dmeWithExtension = ioManager.GetFileName(path); job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1); } + logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName); + await ModifyDme(job, cancellationToken).ConfigureAwait(false); Status = CompilerStatus.Compiling; @@ -311,7 +347,7 @@ namespace Tgstation.Server.Host.Components.Compiler Status = CompilerStatus.Verifying; - ddVerified = job.ExitCode == 0 && await VerifyApi(apiValidateTimeout, job, byondLock, cancellationToken).ConfigureAwait(false); + ddVerified = job.ExitCode == 0 && await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, cancellationToken).ConfigureAwait(false); } if (!ddVerified) @@ -324,11 +360,17 @@ namespace Tgstation.Server.Host.Components.Compiler { job.DMApiValidated = true; + logger.LogTrace("Running post compile event..."); + Status = CompilerStatus.PostCompile; + await eventConsumer.HandleEvent(EventType.CompileComplete, new List { ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)) }, cancellationToken).ConfigureAwait(false); + + logger.LogTrace("Duplicating compiled game..."); Status = CompilerStatus.Duplicating; //duplicate the dmb et al await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); + logger.LogTrace("Applying static game file symlinks..."); Status = CompilerStatus.Symlinking; //symlink in the static data @@ -336,8 +378,8 @@ namespace Tgstation.Server.Host.Components.Compiler var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken); await Task.WhenAll(symATask, symBTask).ConfigureAwait(false); - Status = CompilerStatus.PostCompile; - await eventConsumer.HandleEvent(EventType.CompileComplete, null, cancellationToken).ConfigureAwait(false); + + logger.LogDebug("Compile complete!"); } return job; } diff --git a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs index da6878c667..d1e31dad33 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/IDreamMaker.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Components.Repository; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components.Compiler { @@ -19,10 +19,11 @@ namespace Tgstation.Server.Host.Components.Compiler /// Starts a compile /// /// The optional name of the .dme to compile without the extension if not pre + /// The level allowed for API validation /// The time in seconds to wait while validating the API /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(string projectName, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); + Task Compile(string projectName, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs index 89e5076fd6..57de1c2bac 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/EventType.cs @@ -51,7 +51,7 @@ /// CompileFailure = 10, /// - /// No parameters + /// Parameters: Game directory path /// CompileComplete = 11, diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index a47476351f..f5baaa0b6d 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -122,15 +122,19 @@ namespace Tgstation.Server.Host.Components RepositorySettings repositorySettings = null; string projectName = null; - uint timeout = 0; + DreamDaemonSettings ddSettings = null; var dbTask = databaseContextFactory.UseContext(async (db) => { var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id); - var timeoutTask = instanceQuery.Select(x => x.DreamDaemonSettings.StartupTimeout).FirstAsync(cancellationToken); + var ddSettingsTask = instanceQuery.Select(x => x.DreamDaemonSettings).Select(x => new DreamDaemonSettings + { + StartupTimeout = x.StartupTimeout, + SecurityLevel = x.SecurityLevel + }).FirstAsync(cancellationToken); var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken); repositorySettings = await instanceQuery.Select(x => x.RepositorySettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); projectName = await projectNameTask.ConfigureAwait(false); - timeout = (await timeoutTask.ConfigureAwait(false)).Value; + ddSettings = await ddSettingsTask.ConfigureAwait(false); }); using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) { @@ -170,7 +174,7 @@ namespace Tgstation.Server.Host.Components if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, cancellationToken).ConfigureAwait(false); - var job = await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false); + var job = await DreamMaker.Compile(projectName, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException) { } diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 5e9cf98168..91b434a737 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -169,6 +169,9 @@ namespace Tgstation.Server.Host.Controllers || !CheckModified(x => x.SoftShutdown, DreamDaemonRights.SoftShutdown) || !CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)) return Forbid(); + + if (current.SecurityLevel == DreamDaemonSecurity.Ultrasafe) + return BadRequest(new ErrorMessage { Message = "TGS does not support the ultrasafe DreamDaemon configuration!" }); var wd = instanceManager.GetInstance(Instance).Watchdog; diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 8b666c2a8b..4c7502d025 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -101,9 +101,12 @@ namespace Tgstation.Server.Host.Controllers var instanceManager = serviceProvider.GetRequiredService(); var databaseContext = serviceProvider.GetRequiredService(); - var timeoutTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.StartupTimeout).FirstOrDefaultAsync(cancellationToken); + var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => new DreamDaemonSettings{ + StartupTimeout = x.StartupTimeout, + SecurityLevel = x.SecurityLevel + }).FirstOrDefaultAsync(cancellationToken); var projectName = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.ProjectName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - var timeout = await timeoutTask.ConfigureAwait(false); + var ddSettings = await ddSettingsTask.ConfigureAwait(false); var instance = instanceManager.GetInstance(instanceModel); @@ -119,7 +122,7 @@ namespace Tgstation.Server.Host.Controllers } repoSha = repo.Head; revInfoTask = databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha).Select(x => new RevisionInformation { Id = x.Id }).FirstOrDefaultAsync(); - compileJob = await instance.DreamMaker.Compile(projectName, timeout.Value, repo, cancellationToken).ConfigureAwait(false); + compileJob = await instance.DreamMaker.Compile(projectName, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false); } if (compileJob.DMApiValidated != true) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index b1430a4fd1..a25768c465 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -117,7 +117,7 @@ namespace Tgstation.Server.Host.Controllers AutoStart = false, PrimaryPort = 1337, SecondaryPort = 1338, - SecurityLevel = DreamDaemonSecurity.Ultrasafe, + SecurityLevel = DreamDaemonSecurity.Safe, SoftRestart = false, SoftShutdown = false, StartupTimeout = 20