/// <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);
        }
Beispiel #2
0
        /// <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);
            }
        }
Beispiel #4
0
        /// <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;
            }
        }