/// <inheritdoc />
        public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
        {
            async Task SymlinkBase(bool files)
            {
                Task <IReadOnlyList <string> > task;

                if (files)
                {
                    task = ioManager.GetFiles(GameStaticFilesSubdirectory, cancellationToken);
                }
                else
                {
                    task = ioManager.GetDirectories(GameStaticFilesSubdirectory, cancellationToken);
                }
                var entries = await task.ConfigureAwait(false);

                await Task.WhenAll(task.Result.Select(async x =>
                {
                    var destPath = ioManager.ConcatPath(destination, ioManager.GetFileName(x));
                    logger.LogTrace("Symlinking {0} to {1}...", x, destPath);
                    var fileExistsTask = ioManager.FileExists(destPath, cancellationToken);
                    if (await ioManager.DirectoryExists(destPath, cancellationToken).ConfigureAwait(false))
                    {
                        await ioManager.DeleteDirectory(destPath, cancellationToken).ConfigureAwait(false);
                    }
                    var fileExists = await fileExistsTask.ConfigureAwait(false);
                    if (fileExists)
                    {
                        await ioManager.DeleteFile(destPath, cancellationToken).ConfigureAwait(false);
                    }
                    await symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(x), ioManager.ResolvePath(destPath), cancellationToken).ConfigureAwait(false);
                })).ConfigureAwait(false);
            }

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
            {
                await EnsureDirectories(cancellationToken).ConfigureAwait(false);

                await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false);
            }
        }
        /// <inheritdoc />
        public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
        {
            async Task <IReadOnlyList <string> > GetIgnoreFiles()
            {
                var ignoreFileBytes = await ioManager.ReadAllBytes(StaticIgnorePath(), cancellationToken).ConfigureAwait(false);

                var ignoreFileText = Encoding.UTF8.GetString(ignoreFileBytes);

                var results = new List <string> {
                    StaticIgnoreFile
                };

                //we don't want to lose trailing whitespace on linux
                using (var reader = new StringReader(ignoreFileText))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    var line = await reader.ReadLineAsync().ConfigureAwait(false);

                    if (!String.IsNullOrEmpty(line))
                    {
                        results.Add(line);
                    }
                }

                return(results);
            };

            IReadOnlyList <string> ignoreFiles;

            async Task SymlinkBase(bool files)
            {
                Task <IReadOnlyList <string> > task;

                if (files)
                {
                    task = ioManager.GetFiles(GameStaticFilesSubdirectory, cancellationToken);
                }
                else
                {
                    task = ioManager.GetDirectories(GameStaticFilesSubdirectory, cancellationToken);
                }
                var entries = await task.ConfigureAwait(false);

                await Task.WhenAll(entries.Select(async x =>
                {
                    var fileName = ioManager.GetFileName(x);

                    bool ignored;
                    if (platformIdentifier.IsWindows)
                    {
                        //need to normalize
                        ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant());
                    }
                    else
                    {
                        ignored = ignoreFiles.Any(y => fileName == y);
                    }

                    if (ignored)
                    {
                        logger.LogTrace("Ignoring static file {0}...", fileName);
                        return;
                    }

                    var destPath = ioManager.ConcatPath(destination, fileName);
                    logger.LogTrace("Symlinking {0} to {1}...", x, destPath);
                    var fileExistsTask = ioManager.FileExists(destPath, cancellationToken);
                    if (await ioManager.DirectoryExists(destPath, cancellationToken).ConfigureAwait(false))
                    {
                        await ioManager.DeleteDirectory(destPath, cancellationToken).ConfigureAwait(false);
                    }
                    var fileExists = await fileExistsTask.ConfigureAwait(false);
                    if (fileExists)
                    {
                        await ioManager.DeleteFile(destPath, cancellationToken).ConfigureAwait(false);
                    }
                    await symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(x), ioManager.ResolvePath(destPath), cancellationToken).ConfigureAwait(false);
                })).ConfigureAwait(false);
            }

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
            {
                await EnsureDirectories(cancellationToken).ConfigureAwait(false);

                ignoreFiles = await GetIgnoreFiles().ConfigureAwait(false);

                await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false);
            }
        }
Exemple #3
0
        public async Task <IActionResult> Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (String.IsNullOrWhiteSpace(model.Name))
            {
                return(BadRequest(new ErrorMessage(ErrorCode.InstanceWhitespaceName)));
            }

            var unNormalizedPath   = model.Path;
            var targetInstancePath = NormalizePath(unNormalizedPath);

            model.Path = targetInstancePath;

            var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory);

            bool InstanceIsChildOf(string otherPath)
            {
                if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal))
                {
                    return(false);
                }

                bool sameLength       = targetInstancePath.Length == otherPath.Length;
                char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)];

                return(sameLength ||
                       dirSeparatorChar == Path.DirectorySeparatorChar ||
                       dirSeparatorChar == Path.AltDirectorySeparatorChar);
            }

            if (InstanceIsChildOf(installationDirectoryPath))
            {
                return(Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath)));
            }

            // Validate it's not a child of any other instance
            IActionResult earlyOut = null;
            ulong         countOfOtherInstances = 0;

            using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
            {
                var newCancellationToken = cts.Token;
                try
                {
                    await DatabaseContext
                    .Instances
                    .AsQueryable()
                    .Select(x => new Models.Instance
                    {
                        Path = x.Path
                    })
                    .ForEachAsync(
                        otherInstance =>
                    {
                        if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
                        {
                            earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
                        }
                        else if (InstanceIsChildOf(otherInstance.Path))
                        {
                            earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
                        }

                        if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
                        {
                            cts.Cancel();
                        }
                    },
                        newCancellationToken)
                    .ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }

            if (earlyOut != null)
            {
                return(earlyOut);
            }

            // Last test, ensure it's in the list of valid paths
            if (!(generalConfiguration.ValidInstancePaths?
                  .Select(path => NormalizePath(path))
                  .Any(path => InstanceIsChildOf(path)) ?? true))
            {
                return(BadRequest(new ErrorMessage(ErrorCode.InstanceNotAtWhitelistedPath)));
            }

            async Task <bool> DirExistsAndIsNotEmpty()
            {
                if (!await ioManager.DirectoryExists(model.Path, cancellationToken).ConfigureAwait(false))
                {
                    return(false);
                }

                var filesTask = ioManager.GetFiles(model.Path, cancellationToken);
                var dirsTask  = ioManager.GetDirectories(model.Path, cancellationToken);

                var files = await filesTask.ConfigureAwait(false);

                var dirs = await dirsTask.ConfigureAwait(false);

                return(files.Concat(dirs).Any());
            }

            var  dirExistsTask = DirExistsAndIsNotEmpty();
            bool attached      = false;

            if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false))
            {
                if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken).ConfigureAwait(false))
                {
                    return(Conflict(new ErrorMessage(ErrorCode.InstanceAtExistingPath)));
                }
                else
                {
                    attached = true;
                }
            }

            var newInstance = CreateDefaultInstance(model);

            DatabaseContext.Instances.Add(newInstance);
            try
            {
                await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);

                try
                {
                    // actually reserve it now
                    await ioManager.CreateDirectory(unNormalizedPath, cancellationToken).ConfigureAwait(false);

                    await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    // oh shit delete the model
                    DatabaseContext.Instances.Remove(newInstance);

                    // DCT: Operation must always run
                    await DatabaseContext.Save(default).ConfigureAwait(false);
        public async Task <IActionResult> Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (String.IsNullOrWhiteSpace(model.Name))
            {
                return(BadRequest(new ErrorMessage {
                    Message = "name must not be empty!"
                }));
            }

            if (model.Path == null)
            {
                return(BadRequest(new ErrorMessage {
                    Message = "path must not be empty!"
                }));
            }

            NormalizeModelPath(model, out var rawPath);

            var localPath = ioManager.ResolvePath(".");

            NormalizeModelPath(new Api.Models.Instance
            {
                Path = localPath
            }, out var normalizedLocalPath);

            if (rawPath.StartsWith(normalizedLocalPath, StringComparison.Ordinal))
            {
                bool sameLength       = rawPath.Length == normalizedLocalPath.Length;
                char dirSeparatorChar = rawPath.ToCharArray()[normalizedLocalPath.Length];
                if (sameLength ||
                    dirSeparatorChar == Path.DirectorySeparatorChar ||
                    dirSeparatorChar == Path.AltDirectorySeparatorChar)
                {
                    return(Conflict(new ErrorMessage {
                        Message = "Instances cannot be created in the installation directory!"
                    }));
                }
            }

            var  dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken);
            bool attached      = false;

            if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false))
            {
                if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken).ConfigureAwait(false))
                {
                    return(Conflict(new ErrorMessage {
                        Message = "Path not empty!"
                    }));
                }
                else
                {
                    attached = true;
                }
            }

            var newInstance = new Models.Instance
            {
                ConfigurationType   = model.ConfigurationType ?? ConfigurationType.Disallowed,
                DreamDaemonSettings = new DreamDaemonSettings
                {
                    AllowWebClient = false,
                    AutoStart      = false,
                    PrimaryPort    = 1337,
                    SecondaryPort  = 1338,
                    SecurityLevel  = DreamDaemonSecurity.Safe,
                    SoftRestart    = false,
                    SoftShutdown   = false,
                    StartupTimeout = 20
                },
                DreamMakerSettings = new DreamMakerSettings
                {
                    ApiValidationPort          = 1339,
                    ApiValidationSecurityLevel = DreamDaemonSecurity.Safe
                },
                Name               = model.Name,
                Online             = false,
                Path               = model.Path,
                AutoUpdateInterval = model.AutoUpdateInterval ?? 0,
                RepositorySettings = new RepositorySettings
                {
                    CommitterEmail            = "*****@*****.**",
                    CommitterName             = application.VersionPrefix,
                    PushTestMergeCommits      = false,
                    ShowTestMergeCommitters   = false,
                    AutoUpdatesKeepTestMerges = false,
                    AutoUpdatesSynchronize    = false,
                    PostTestMergeComment      = false
                },
                InstanceUsers = new List <Models.InstanceUser>                // give this user full privileges on the instance
                {
                    InstanceAdminUser()
                }
            };

            DatabaseContext.Instances.Add(newInstance);
            try
            {
                await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);

                try
                {
                    // actually reserve it now
                    await ioManager.CreateDirectory(rawPath, cancellationToken).ConfigureAwait(false);

                    await ioManager.DeleteFile(ioManager.ConcatPath(rawPath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    // oh shit delete the model
                    DatabaseContext.Instances.Remove(newInstance);

                    await DatabaseContext.Save(default).ConfigureAwait(false);
        /// <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;
            }
        }
Exemple #6
0
        /// <summary>
        /// Prompts the user to create a <see cref="FileLoggingConfiguration"/>
        /// </summary>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="FileLoggingConfiguration"/></returns>
        async Task <FileLoggingConfiguration> ConfigureLogging(CancellationToken cancellationToken)
        {
            var fileLoggingConfiguration = new FileLoggingConfiguration();
            await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);

            fileLoggingConfiguration.Disable = !await PromptYesNo("Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(false);

            if (!fileLoggingConfiguration.Disable)
            {
                do
                {
                    await console.WriteAsync("Log file directory path (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);

                    fileLoggingConfiguration.Directory = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);

                    if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
                    {
                        fileLoggingConfiguration.Directory = null;
                        break;
                    }
                    //test a write of it
                    await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);

                    await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false);

                    try
                    {
                        await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(false);

                        var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture, "WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
                        await ioManager.WriteAllBytes(testFile, Array.Empty <byte>(), cancellationToken).ConfigureAwait(false);

                        try
                        {
                            await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(false);
                        }
                        catch (OperationCanceledException)
                        {
                            throw;
                        }
                        catch (Exception e)
                        {
                            await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Error deleting test log file: {0}", testFile), true, cancellationToken).ConfigureAwait(false);

                            await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);

                            await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
                        }
                        break;
                    }
                    catch (OperationCanceledException)
                    {
                        throw;
                    }
                    catch (Exception e)
                    {
                        await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);

                        await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);

                        await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false);
                    }
                } while (true);

                async Task <LogLevel?> PromptLogLevel(string question)
                {
                    do
                    {
                        await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);

                        await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false);

                        await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);

                        var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);

                        if (String.IsNullOrWhiteSpace(responseString))
                        {
                            return(null);
                        }
                        if (Enum.TryParse <LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
                        {
                            return(logLevel);
                        }
                        await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false);
                    } while (true);
                }

                fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel;

                fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
            }
            return(fileLoggingConfiguration);
        }
Exemple #7
0
        /// <inheritdoc />
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            async Task <byte[]> GetActiveVersion()
            {
                var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);

                return(!activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false));
            }

            var activeVersionBytesTask = GetActiveVersion();

            // Create local cfg directory in case it doesn't exist
            var localCfgDirectory = ioManager.ConcatPath(
                byondInstaller.PathToUserByondFolder,
                CfgDirectoryName);
            await ioManager.CreateDirectory(
                localCfgDirectory,
                cancellationToken).ConfigureAwait(false);

            // Delete trusted.txt so it doesn't grow too large
            var trustedFilePath =
                ioManager.ConcatPath(
                    localCfgDirectory,
                    TrustedDmbFileName);

            logger.LogTrace("Deleting trusted .dmbs file {0}", trustedFilePath);
            await ioManager.DeleteFile(
                trustedFilePath,
                cancellationToken).ConfigureAwait(false);

            var byondDirectory = ioManager.ResolvePath();
            await ioManager.CreateDirectory(byondDirectory, cancellationToken).ConfigureAwait(false);

            var directories = await ioManager.GetDirectories(byondDirectory, cancellationToken).ConfigureAwait(false);

            async Task ReadVersion(string path)
            {
                var versionFile = ioManager.ConcatPath(path, VersionFileName);

                if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(false))
                {
                    logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
                    await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);                     // cleanup

                    return;
                }

                var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false);

                var text = Encoding.UTF8.GetString(bytes);

                if (Version.TryParse(text, out var version))
                {
                    var key = VersionKey(version);
                    lock (installedVersions)
                        if (!installedVersions.ContainsKey(key))
                        {
                            logger.LogDebug("Adding detected BYOND version {0}...", key);
                            installedVersions.Add(key, Task.CompletedTask);
                            return;
                        }
                }

                await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);
            }

            await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false);

            var activeVersionBytes = await activeVersionBytesTask.ConfigureAwait(false);

            if (activeVersionBytes != null)
            {
                var  activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
                bool hasRequestedActiveVersion;
                lock (installedVersions)
                    hasRequestedActiveVersion = installedVersions.ContainsKey(activeVersionString);
                if (hasRequestedActiveVersion && Version.TryParse(activeVersionString, out var activeVersion))
                {
                    ActiveVersion = activeVersion.Semver();
                }
                else
                {
                    logger.LogWarning("Failed to load saved active version {0}!", activeVersionString);
                    await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
                }
            }
        }
Exemple #8
0
        /// <summary>
        /// Ensure a given <paramref name="testConnection"/> works.
        /// </summary>
        /// <param name="testConnection">The test <see cref="DbConnection"/>.</param>
        /// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/> may have derived data populated.</param>
        /// <param name="databaseName">The database name (or path in the case of a <see cref="DatabaseType.Sqlite"/> database).</param>
        /// <param name="dbExists">Whether or not the database exists.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
        /// <returns>A <see cref="Task"/> representing the running operation.</returns>
        async Task TestDatabaseConnection(
            DbConnection testConnection,
            DatabaseConfiguration databaseConfiguration,
            string databaseName,
            bool dbExists,
            CancellationToken cancellationToken)
        {
            bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;

            using (testConnection)
            {
                await console.WriteAsync("Testing connection...", true, cancellationToken).ConfigureAwait(false);

                await testConnection.OpenAsync(cancellationToken).ConfigureAwait(false);

                await console.WriteAsync("Connection successful!", true, cancellationToken).ConfigureAwait(false);

                if (databaseConfiguration.DatabaseType == DatabaseType.MariaDB ||
                    databaseConfiguration.DatabaseType == DatabaseType.MySql)
                {
                    await console.WriteAsync("Checking MySQL/MariaDB version...", true, cancellationToken).ConfigureAwait(false);

                    using (var command = testConnection.CreateCommand())
                    {
                        command.CommandText = "SELECT VERSION()";
                        var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);

                        await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false);

                        var splits = fullVersion.Split('-');
                        databaseConfiguration.MySqlServerVersion = splits.First();
                    }
                }

                if (!isSqliteDB && !dbExists)
                {
                    await console.WriteAsync("Testing create DB permission...", true, cancellationToken).ConfigureAwait(false);

                    using (var command = testConnection.CreateCommand())
                    {
                        // I really don't care about user sanitization here, they want to f**k their own DB? so be it
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
                        command.CommandText = $"CREATE DATABASE {databaseName}";
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
                        await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
                    }

                    await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false);

                    await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);

                    using (var command = testConnection.CreateCommand())
                    {
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
                        command.CommandText = $"DROP DATABASE {databaseName}";
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
                        try
                        {
                            await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
                        }
                        catch (OperationCanceledException)
                        {
                            throw;
                        }
                        catch (Exception e)
                        {
                            await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);

                            await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);

                            await console.WriteAsync("This should be okay, but you may want to manually drop the database before continuing!", true, cancellationToken).ConfigureAwait(false);

                            await console.WriteAsync("Press any key to continue...", true, cancellationToken).ConfigureAwait(false);

                            await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
                        }
                    }
                }
            }

            if (isSqliteDB && !dbExists)
            {
                await Task.WhenAll(
                    console.WriteAsync("Deleting test database file...", true, cancellationToken),
                    ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(false);
            }
        }
Exemple #9
0
        /// <inheritdoc />
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            async Task <byte[]> GetActiveVersion()
            {
                var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);

                return(!activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false));
            }

            var activeVersionBytesTask = GetActiveVersion();

            await ioManager.CreateDirectory(".", cancellationToken).ConfigureAwait(false);

            var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false);

            async Task ReadVersion(string path)
            {
                var versionFile = ioManager.ConcatPath(path, VersionFileName);

                if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(false))
                {
                    logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
                    await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);                     // cleanup

                    return;
                }

                var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false);

                var text = Encoding.UTF8.GetString(bytes);

                if (Version.TryParse(text, out var version))
                {
                    var key = VersionKey(version);
                    lock (installedVersions)
                        if (!installedVersions.ContainsKey(key))
                        {
                            installedVersions.Add(key, Task.CompletedTask);
                            return;
                        }
                }

                await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);
            }

            await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false);

            var activeVersionBytes = await activeVersionBytesTask.ConfigureAwait(false);

            if (activeVersionBytes != null)
            {
                var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
                if (Version.TryParse(activeVersionString, out var activeVersion))
                {
                    ActiveVersion = activeVersion;
                }
                else
                {
                    await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
                }
            }
        }
Exemple #10
0
        public async Task <IActionResult> Create([FromBody] Api.Models.Instance model, CancellationToken cancellationToken)
        {
            if (model == null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (String.IsNullOrWhiteSpace(model.Name))
            {
                return(BadRequest(new ErrorMessage(ErrorCode.InstanceWhitespaceName)));
            }

            var targetInstancePath = NormalizePath(model.Path);

            model.Path = targetInstancePath;

            var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory);

            bool InstanceIsChildOf(string otherPath)
            {
                if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal))
                {
                    return(false);
                }

                bool sameLength       = targetInstancePath.Length == otherPath.Length;
                char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)];

                return(sameLength ||
                       dirSeparatorChar == Path.DirectorySeparatorChar ||
                       dirSeparatorChar == Path.AltDirectorySeparatorChar);
            }

            if (InstanceIsChildOf(installationDirectoryPath))
            {
                return(Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath)));
            }

            // Validate it's not a child of any other instance
            IActionResult earlyOut = null;
            ulong         countOfOtherInstances = 0;

            using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
            {
                var newCancellationToken = cts.Token;
                try
                {
                    await DatabaseContext
                    .Instances
                    .AsQueryable()
                    .Select(x => new Models.Instance
                    {
                        Path = x.Path
                    })
                    .ForEachAsync(
                        otherInstance =>
                    {
                        if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
                        {
                            earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
                        }
                        else if (InstanceIsChildOf(otherInstance.Path))
                        {
                            earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
                        }

                        if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
                        {
                            cts.Cancel();
                        }
                    },
                        newCancellationToken)
                    .ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }

            if (earlyOut != null)
            {
                return(earlyOut);
            }

            // Last test, ensure it's in the list of valid paths
            if (!(generalConfiguration.ValidInstancePaths?
                  .Select(path => NormalizePath(path))
                  .Any(path => InstanceIsChildOf(path)) ?? true))
            {
                return(BadRequest(new ErrorMessage(ErrorCode.InstanceNotAtWhitelistedPath)));
            }

            async Task <bool> DirExistsAndIsNotEmpty()
            {
                if (!await ioManager.DirectoryExists(model.Path, cancellationToken).ConfigureAwait(false))
                {
                    return(false);
                }

                var filesTask = ioManager.GetFiles(model.Path, cancellationToken);
                var dirsTask  = ioManager.GetDirectories(model.Path, cancellationToken);

                var files = await filesTask.ConfigureAwait(false);

                var dirs = await dirsTask.ConfigureAwait(false);

                return(files.Concat(dirs).Any());
            }

            var  dirExistsTask = DirExistsAndIsNotEmpty();
            bool attached      = false;

            if (await ioManager.FileExists(model.Path, cancellationToken).ConfigureAwait(false) || await dirExistsTask.ConfigureAwait(false))
            {
                if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken).ConfigureAwait(false))
                {
                    return(Conflict(new ErrorMessage(ErrorCode.InstanceAtExistingPath)));
                }
                else
                {
                    attached = true;
                }
            }

            var newInstance = new Models.Instance
            {
                ConfigurationType   = model.ConfigurationType ?? ConfigurationType.Disallowed,
                DreamDaemonSettings = new DreamDaemonSettings
                {
                    AllowWebClient   = false,
                    AutoStart        = false,
                    PrimaryPort      = 1337,
                    SecondaryPort    = 1338,
                    SecurityLevel    = DreamDaemonSecurity.Safe,
                    StartupTimeout   = 60,
                    HeartbeatSeconds = 60
                },
                DreamMakerSettings = new DreamMakerSettings
                {
                    ApiValidationPort          = 1339,
                    ApiValidationSecurityLevel = DreamDaemonSecurity.Safe
                },
                Name               = model.Name,
                Online             = false,
                Path               = model.Path,
                AutoUpdateInterval = model.AutoUpdateInterval ?? 0,
                ChatBotLimit       = model.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
                RepositorySettings = new RepositorySettings
                {
                    CommitterEmail            = "*****@*****.**",
                    CommitterName             = assemblyInformationProvider.VersionPrefix,
                    PushTestMergeCommits      = false,
                    ShowTestMergeCommitters   = false,
                    AutoUpdatesKeepTestMerges = false,
                    AutoUpdatesSynchronize    = false,
                    PostTestMergeComment      = false
                },
                InstanceUsers = new List <Models.InstanceUser>                // give this user full privileges on the instance
                {
                    InstanceAdminUser()
                }
            };

            DatabaseContext.Instances.Add(newInstance);
            try
            {
                await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);

                try
                {
                    // actually reserve it now
                    await ioManager.CreateDirectory(targetInstancePath, cancellationToken).ConfigureAwait(false);

                    await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken).ConfigureAwait(false);
                }
                catch
                {
                    // oh shit delete the model
                    DatabaseContext.Instances.Remove(newInstance);

                    await DatabaseContext.Save(default).ConfigureAwait(false);