/// <inheritdoc /> public async Task <bool> HandleEvent(EventType eventType, IEnumerable <string> parameters, CancellationToken cancellationToken) { await EnsureDirectories(cancellationToken).ConfigureAwait(false); if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName)) { return(true); } //always execute in serial using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, SystemScriptFileExtension, cancellationToken).ConfigureAwait(false); var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory); foreach (var I in files.Select(x => ioManager.GetFileName(x)).Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))) { using (var script = processExecutor.LaunchProcess(ioManager.ConcatPath(resolvedScriptsDir, I), resolvedScriptsDir, String.Join(' ', parameters), noShellExecute: true)) using (cancellationToken.Register(() => script.Terminate())) { var exitCode = await script.Lifetime.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); if (exitCode != 0) { return(false); } } } } return(true); }
/// <inheritdoc /> public override async Task InstallByond(string path, Version version, CancellationToken cancellationToken) { async Task SetNoPromptTrusted() { var configPath = IOManager.ConcatPath(path, ByondConfigDir); await IOManager.CreateDirectory(configPath, cancellationToken).ConfigureAwait(false); await IOManager.WriteAllBytes(IOManager.ConcatPath(configPath, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken).ConfigureAwait(false); } var setNoPromptTrustedModeTask = SetNoPromptTrusted(); // after this version lummox made DD depend of directx lol // but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/ // then he readded it again so -_- if (!installedDirectX) { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (!installedDirectX) { // ^check again because race conditions // always install it, it's pretty fast and will do better redundancy checking than us var rbdx = IOManager.ConcatPath(path, ByondDXDir); // noShellExecute because we aren't doing runas shennanigans IProcess directXInstaller; try { directXInstaller = processExecutor.LaunchProcess(IOManager.ConcatPath(rbdx, "DXSETUP.exe"), rbdx, "/silent", noShellExecute: true); } catch (Exception e) { throw new JobException("Unable to start DirectX installer process! Is the server running with admin privileges?", e); } using (directXInstaller) { int exitCode; using (cancellationToken.Register(() => directXInstaller.Terminate())) exitCode = await directXInstaller.Lifetime.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); if (exitCode != 0) { throw new JobException(String.Format(CultureInfo.InvariantCulture, "Failed to install included DirectX! Exit code: {0}", exitCode)); } installedDirectX = true; } } } await setNoPromptTrustedModeTask.ConfigureAwait(false); }
/// <summary> /// Attempt to install the DirectX redistributable included with BYOND. /// </summary> /// <param name="path">The path to the BYOND installation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param> /// <returns>A <see cref="Task"/> representing the running operation.</returns> async Task InstallDirectX(string path, CancellationToken cancellationToken) { using var lockContext = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false); if (installedDirectX) { Logger.LogTrace("DirectX already installed."); return; } Logger.LogTrace("Installing DirectX redistributable..."); // always install it, it's pretty fast and will do better redundancy checking than us var rbdx = IOManager.ConcatPath(path, ByondDXDir); try { // noShellExecute because we aren't doing runas shennanigans using var directXInstaller = processExecutor.LaunchProcess( IOManager.ConcatPath(rbdx, "DXSETUP.exe"), rbdx, "/silent", noShellExecute: true); int exitCode; using (cancellationToken.Register(() => directXInstaller.Terminate())) exitCode = await directXInstaller.Lifetime.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); if (exitCode != 0) { throw new JobException(ErrorCode.ByondDirectXInstallFail, new JobException($"Invalid exit code: {exitCode}")); } installedDirectX = true; } catch (Exception e) { throw new JobException(ErrorCode.ByondDirectXInstallFail, e); } }
/// <summary> /// Compiles a .dme with DreamMaker /// </summary> /// <param name="dreamMakerPath">The path to the DreamMaker executable</param> /// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> async Task <int> RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken) { using (var dm = processExecutor.LaunchProcess(dreamMakerPath, ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)), String.Format(CultureInfo.InvariantCulture, "-clean {0}.{1}", job.DmeName, DmeExtension), true, true)) { int exitCode; using (cancellationToken.Register(() => dm.Terminate())) exitCode = await dm.Lifetime.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); logger.LogDebug("DreamMaker exit code: {0}", exitCode); job.Output = dm.GetCombinedOutput(); logger.LogTrace("DreamMaker output: {0}{1}", Environment.NewLine, job.Output); return(exitCode); } }
/// <inheritdoc /> public async Task InstallByond(string path, Version version, CancellationToken cancellationToken) { async Task SetNoPromptTrusted() { var configPath = ioManager.ConcatPath(path, ByondConfigDir); await ioManager.CreateDirectory(configPath, cancellationToken).ConfigureAwait(false); await ioManager.WriteAllBytes(ioManager.ConcatPath(configPath, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken).ConfigureAwait(false); }; var setNoPromptTrustedModeTask = SetNoPromptTrusted(); //after this version lummox made DD depend of directx lol if (version.Major >= 512 && version.Minor >= 1427 && !installedDirectX) { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (!installedDirectX) { //always install it, it's pretty fast and will do better redundancy checking than us var rbdx = ioManager.ConcatPath(path, ByondDXDir); //noShellExecute because we aren't doing runas shennanigans using (var p = processExecutor.LaunchProcess(ioManager.ConcatPath(rbdx, "DXSETUP.exe"), rbdx, "/silent", noShellExecute: true)) { int exitCode; using (cancellationToken.Register(() => p.Terminate())) exitCode = await p.Lifetime.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); if (exitCode != 0) { throw new JobException(String.Format(CultureInfo.InvariantCulture, "Failed to install included DirectX! Exit code: {0}", exitCode)); } installedDirectX = true; } } } await setNoPromptTrustedModeTask.ConfigureAwait(false); }
/// <summary> /// Compiles a .dme with DreamMaker /// </summary> /// <param name="dreamMakerPath">The path to the DreamMaker executable</param> /// <param name="job">The <see cref="CompileJob"/> for the operation</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> async Task <int> RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken) { using var dm = processExecutor.LaunchProcess( dreamMakerPath, ioManager.ResolvePath( job.DirectoryName.ToString()), $"-clean {job.DmeName}.{DmeExtension}", true, true, true); int exitCode; using (cancellationToken.Register(() => dm.Terminate())) exitCode = await dm.Lifetime.ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); logger.LogDebug("DreamMaker exit code: {0}", exitCode); job.Output = await dm.GetCombinedOutput(cancellationToken).ConfigureAwait(false); currentDreamMakerOutput = job.Output; logger.LogDebug("DreamMaker output: {0}{1}", Environment.NewLine, job.Output); return(exitCode); }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public async Task <ISessionController> LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) { var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort; if (!portToUse.HasValue) { throw new InvalidOperationException("Given port is null!"); } var accessIdentifier = cryptographySuite.GetSecureString(); const string JsonPostfix = "tgs.json"; var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; // delete all previous tgs json files var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false); await Task.WhenAll(files.Select(x => ioManager.DeleteFile(x, cancellationToken))).ConfigureAwait(false); // i changed this back from guids, hopefully i don't regret that string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix); var securityLevelToUse = launchParameters.SecurityLevel.Value; switch (dmbProvider.CompileJob.MinimumSecurityLevel) { case DreamDaemonSecurity.Ultrasafe: break; case DreamDaemonSecurity.Safe: if (securityLevelToUse == DreamDaemonSecurity.Ultrasafe) { securityLevelToUse = DreamDaemonSecurity.Safe; } break; case DreamDaemonSecurity.Trusted: securityLevelToUse = DreamDaemonSecurity.Trusted; break; default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel)); } // setup interop files var interopInfo = new JsonFile { AccessIdentifier = accessIdentifier, ApiValidateOnly = apiValidate, ChatChannelsJson = JsonFile("chat_channels"), ChatCommandsJson = JsonFile("chat_commands"), ServerCommandsJson = JsonFile("server_commands"), InstanceName = instance.Name, SecurityLevel = securityLevelToUse, Revision = new Api.Models.Internal.RevisionInformation { CommitSha = dmbProvider.CompileJob.RevisionInformation.CommitSha, OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha } }; interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new Interop.TestMerge(x, interopInfo.Revision))); var interopJsonFile = JsonFile("interop"); var interopJson = JsonConvert.SerializeObject(interopInfo, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); var chatJsonTrackingTask = chat.TrackJsons(basePath, interopInfo.ChatChannelsJson, interopInfo.ChatCommandsJson, cancellationToken); await ioManager.WriteAllBytes(ioManager.ConcatPath(basePath, interopJsonFile), Encoding.UTF8.GetBytes(interopJson), cancellationToken).ConfigureAwait(false); var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { // get the byond lock var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false); try { // create interop context var context = new CommContext(ioManager, loggerFactory.CreateLogger <CommContext>(), basePath, interopInfo.ServerCommandsJson); try { // set command line options // more sanitization here cause it uses the same scheme var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); var visibility = apiValidate ? "invisible" : "public"; // important to run on all ports to allow port changing var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -{5} -public -params \"{4}\"", dmbProvider.DmbName, primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(securityLevelToUse), parameters, visibility); // See https://github.com/tgstation/tgstation-server/issues/719 var noShellExecute = !platformIdentifier.IsWindows; // launch dd var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute); try { networkPromptReaper.RegisterProcess(process); // return the session controller for it var result = new SessionController(new ReattachInformation { AccessIdentifier = accessIdentifier, Dmb = dmbProvider, IsPrimary = primaryDirectory, Port = portToUse.Value, ProcessId = process.Id, ChatChannelsJson = interopInfo.ChatChannelsJson, ChatCommandsJson = interopInfo.ChatCommandsJson, ServerCommandsJson = interopInfo.ServerCommandsJson, }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger <SessionController>(), launchParameters.SecurityLevel, launchParameters.StartupTimeout); // writeback launch parameter's fixed security level launchParameters.SecurityLevel = securityLevelToUse; return(result); } catch { process.Dispose(); throw; } } catch { context.Dispose(); throw; } } catch { if (currentByondLock == null) { byondLock.Dispose(); } throw; } } catch { chatJsonTrackingContext.Dispose(); throw; } }