diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index d69ff7ee72..0227e83be7 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -35,77 +35,77 @@ namespace Tgstation.Server.Host.Watchdog { logger.LogInformation("Host watchdog starting..."); - var enviromentPath = Environment.GetEnvironmentVariable("PATH"); - var paths = enviromentPath.Split(';'); - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - var exeName = "dotnet"; - IEnumerable enumerator; - if (isWindows) - { - exeName += ".exe"; - enumerator = paths; - } - else - enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x); - - enumerator = enumerator.Select(x => Path.Combine(x, exeName)); - - var dotnetPath = enumerator - .Where(x => - { - logger.LogTrace("Checking for dotnet at {0}", x); - return File.Exists(x); - }) - .FirstOrDefault(); - - if (dotnetPath == default) - { - logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!"); - return; - } - logger.LogInformation("Detected dotnet executable at {0}", dotnetPath); - - var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog - var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); -#if DEBUG - //just copy the shit where it belongs - Directory.Delete(assemblyStoragePath, true); - Directory.CreateDirectory(defaultAssemblyPath); - - var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; - foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) - Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); - - foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) - File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); - - const string AppSettingsJson = "appsettings.json"; - var rootJson = Path.Combine(rootLocation, AppSettingsJson); - File.Delete(rootJson); - File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); -#endif - - var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); - var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); - - if (assemblyPath.Contains("\"")) - { - logger.LogCritical("Running from paths with \"'s in the name is not supported!"); - return; - } - - if (!File.Exists(assemblyPath)) - { - logger.LogCritical("Unable to locate host assembly!"); - return; - } - string updateDirectory = null; try { + var enviromentPath = Environment.GetEnvironmentVariable("PATH"); + var paths = enviromentPath.Split(';'); + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + var exeName = "dotnet"; + IEnumerable enumerator; + if (isWindows) + { + exeName += ".exe"; + enumerator = paths; + } + else + enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x); + + enumerator = enumerator.Select(x => Path.Combine(x, exeName)); + + var dotnetPath = enumerator + .Where(x => + { + logger.LogTrace("Checking for dotnet at {0}", x); + return File.Exists(x); + }) + .FirstOrDefault(); + + if (dotnetPath == default) + { + logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!"); + return; + } + logger.LogInformation("Detected dotnet executable at {0}", dotnetPath); + + var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog + var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default")); +#if DEBUG + //just copy the shit where it belongs + Directory.Delete(assemblyStoragePath, true); + Directory.CreateDirectory(defaultAssemblyPath); + + var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1"; + foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) + Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath)); + + foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) + File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); + + const string AppSettingsJson = "appsettings.json"; + var rootJson = Path.Combine(rootLocation, AppSettingsJson); + File.Delete(rootJson); + File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); +#endif + + var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll"); + var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName); + + if (assemblyPath.Contains("\"")) + { + logger.LogCritical("Running from paths with \"'s in the name is not supported!"); + return; + } + + if (!File.Exists(assemblyPath)) + { + logger.LogCritical("Unable to locate host assembly!"); + return; + } + while (!cancellationToken.IsCancellationRequested) using (logger.BeginScope("Host invocation")) { @@ -119,7 +119,7 @@ namespace Tgstation.Server.Host.Watchdog var arguments = new List { '"' + assemblyPath + '"', - updateDirectory + '"' + updateDirectory + '"' }; if (Debugger.IsAttached) arguments.Add("--attach-debugger"); diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 91302f8fdc..b676c9f94f 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -184,8 +184,8 @@ namespace Tgstation.Server.Host.Components while (true) try { - await Task.Delay(new TimeSpan(0, minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes, 0), cancellationToken).ConfigureAwait(false); - + await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false); + logger.LogDebug("Beginning auto update..."); try { Models.User user = null; @@ -198,10 +198,11 @@ namespace Tgstation.Server.Host.Components }, Description = "Scheduled repository update", CancelRightsType = RightsType.Repository, - CancelRight = (ulong)RepositoryRights.CancelPendingChanges + CancelRight = (ulong)RepositoryRights.CancelPendingChanges, + StartedBy = user }; - var noRepo = false; + var repositoryUpdateSuccess = false; await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) => { var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); @@ -225,8 +226,7 @@ namespace Tgstation.Server.Host.Components { if (repo == null) { - //no repo, no auto updates - noRepo = true; + logger.LogTrace("Aborting repo update, no repository!"); return; } @@ -241,13 +241,15 @@ namespace Tgstation.Server.Host.Components bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { + logger.LogTrace("Preserving test merges..."); var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); if (!result.HasValue) - return; + throw new JobException("Merge conflict while preserving test merges!"); shouldSyncTracked = result.Value; } else { + logger.LogTrace("Not preserving test merges..."); await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); shouldSyncTracked = true; } @@ -258,12 +260,16 @@ namespace Tgstation.Server.Host.Components progressReporter(5 * ProgressStep); } + repositoryUpdateSuccess = true; }, cancellationToken).ConfigureAwait(false); await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false); - if (noRepo) + if (!repositoryUpdateSuccess) + { + logger.LogTrace("Aborting auto update, repository error!"); continue; + } //finally set up the job var compileProcessJob = new Job diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 4113a66945..0c7f7687ee 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -442,6 +442,7 @@ namespace Tgstation.Server.Host.Components.Repository Branch trackedBranch = null; var oldHead = repository.Head; + var oldTip = oldHead.Tip; await Task.Factory.StartNew(() => { @@ -468,7 +469,10 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName); - RawCheckout(oldHead.CanonicalName, progressReporter, cancellationToken); + repository.Reset(ResetMode.Hard, oldTip, new CheckoutOptions + { + OnCheckoutProgress = CheckoutProgressHandler(progressReporter) + }); cancellationToken.ThrowIfCancellationRequested(); } @@ -477,7 +481,7 @@ namespace Tgstation.Server.Host.Components.Repository if (result.Status == MergeStatus.Conflicts) { - await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldHead.Tip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false); + await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false); return null; } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index 29b8b66da0..7c6ec4298b 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// The data to write. If , the file is deleted /// The hash any existing file must match in order for the write to succeed /// The for the operation. Usage may result in partial writes - /// A resulting in the updated + /// A resulting in the updated or if the write failed due to conflicts Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, string previousHash, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index acaf5206dd..327f289072 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Controllers if (newFile == null) return Conflict(new ErrorMessage { - Message = "" + Message = "This file has been updated since you last viewed it!" }); newFile.Content = null; @@ -171,6 +171,7 @@ namespace Tgstation.Server.Host.Controllers try { + model.IsDirectory = true; return await instanceManager.GetInstance(Instance).Configuration.CreateDirectory(model.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Json(model) : StatusCode((int)HttpStatusCode.Created, model); } catch (NotImplementedException) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 0c6801cbf2..28cc4d34e7 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -295,6 +295,7 @@ namespace Tgstation.Server.Host.Controllers } } + var oldAutoUpdateInterval = originalModel.AutoUpdateInterval.Value; var originalOnline = originalModel.Online.Value; var renamed = model.Name != null && originalModel.Name != model.Name; @@ -356,6 +357,9 @@ namespace Tgstation.Server.Host.Controllers api.MoveJob = job.ToApi(); } + if (originalModel.Online.Value && model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval) + await instanceManager.GetInstance(originalModel).SetAutoUpdateInterval(model.AutoUpdateInterval.Value).ConfigureAwait(false); + return Json(api); }