diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index b5bc692f11..9edc4970e9 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -222,14 +222,13 @@ export_lock = FALSE /datum/tgs_api/v4/OnReboot() - var/json = Export(TGS4_COMM_WORLD_REBOOT) - var/list/result = json_decode(json) + var/list/result = Export(TGS4_COMM_WORLD_REBOOT) if(!result) return //okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter - var/port = json[TGS4_PARAMETER_DATA] + var/port = result[TGS4_PARAMETER_DATA] if(!isnum(port)) return //this is valid, server may just want use to reboot diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 0227e83be7..3e3ad9aad0 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -70,7 +70,7 @@ namespace Tgstation.Server.Host.Watchdog 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 @@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Watchdog using (var process = new Process()) { process.StartInfo.FileName = dotnetPath; - process.StartInfo.WorkingDirectory = Environment.CurrentDirectory; //for appsettings + process.StartInfo.WorkingDirectory = rootLocation; //for appsettings var arguments = new List { @@ -138,7 +138,7 @@ namespace Tgstation.Server.Host.Watchdog logger.LogInformation("Launching host..."); - var iShotTheSheriff = false; + var killedHostProcess = false; try { process.Start(); @@ -172,7 +172,7 @@ namespace Tgstation.Server.Host.Watchdog { if (!process.HasExited) { - iShotTheSheriff = true; + killedHostProcess = true; process.Kill(); process.WaitForExit(); } @@ -206,7 +206,7 @@ namespace Tgstation.Server.Host.Watchdog } throw new Exception(String.Format(CultureInfo.InvariantCulture, "Host propagated exception: {0}", data)); default: - if (iShotTheSheriff) + if (killedHostProcess) { logger.LogWarning("Watchdog forced to kill host process!"); cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index f11834a7cc..d1a33aac23 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -122,7 +122,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var channelsAvailable = new TaskCompletionSource(); client.Ready += () => { - channelsAvailable.SetResult(null); + channelsAvailable.TrySetResult(null); return Task.CompletedTask; }; using (cancellationToken.Register(() => channelsAvailable.SetCanceled())) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index ce081b13e7..12374f26fd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -291,16 +291,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers listenTask = Task.Factory.StartNew(() => { - while (!disconnecting && client.IsConnected) + while (!disconnecting && client.IsConnected && client.Nickname != nickname) { client.ListenOnce(true); if (disconnecting || !client.IsConnected) break; client.Listen(false); //ensure we have the correct nick - if (client.Nickname != nickname && client.GetIrcUser(nickname) == null) + if (client.GetIrcUser(nickname) == null) client.RfcNick(nickname); } + client.Listen(); }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } catch (Exception e) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index b676c9f94f..50077a86db 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -146,7 +147,7 @@ namespace Tgstation.Server.Host.Components throw new JobException("Missing Repository!"); var repoSha = repo.Head; - revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); + revInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id).Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).FirstOrDefaultAsync().ConfigureAwait(false); if (revInfo == default) { @@ -202,7 +203,7 @@ namespace Tgstation.Server.Host.Components StartedBy = user }; - var repositoryUpdateSuccess = false; + string deploySha = null; await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) => { var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken); @@ -230,47 +231,140 @@ namespace Tgstation.Server.Host.Components return; } + var startSha = repo.Head; + if (!repo.Tracking) + { + logger.LogTrace("Aborting repo update, not tracking origin!"); + deploySha = startSha; + return; + } + var repositorySettings = await repositorySettingsTask.ConfigureAwait(false); - + //the main point of auto update is to pull the remote await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); - var startSha = repo.Head; + RevisionInformation currentRevInfo = null; + bool hasDbChanges = false; + + Task LoadRevInfo() => databaseContext.RevisionInformations + .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id) + .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) + .FirstOrDefaultAsync(cancellationToken); + + async Task UpdateRevInfo(string currentHead, bool onOrigin) + { + if(currentRevInfo == null) + currentRevInfo = await LoadRevInfo().ConfigureAwait(false); + + if (currentRevInfo == default) + { + logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentHead); + onOrigin = true; + } + + var attachedInstance = new Models.Instance + { + Id = metadata.Id + }; + var oldRevInfo = currentRevInfo; + currentRevInfo = new RevisionInformation + { + CommitSha = currentHead, + OriginCommitSha = onOrigin ? currentHead : oldRevInfo.OriginCommitSha, + Instance = attachedInstance + }; + if (!onOrigin) + currentRevInfo.ActiveTestMerges = new List(oldRevInfo.ActiveTestMerges); + + databaseContext.Instances.Attach(attachedInstance); + databaseContext.RevisionInformations.Add(currentRevInfo); + hasDbChanges = true; + } //take appropriate auto update actions bool shouldSyncTracked; if (repositorySettings.AutoUpdatesKeepTestMerges.Value) { logger.LogTrace("Preserving test merges..."); + + var currentRevInfoTask = LoadRevInfo(); + var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); + if (!result.HasValue) throw new JobException("Merge conflict while preserving test merges!"); - shouldSyncTracked = result.Value; + + currentRevInfo = await currentRevInfoTask.ConfigureAwait(false); + + var lastRevInfoWasOriginCommit = currentRevInfo == default || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha; + var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit; + + var currentHead = repo.Head; + if (currentHead != startSha) + { + await UpdateRevInfo(currentHead, stillOnOrigin).ConfigureAwait(false); + shouldSyncTracked = stillOnOrigin; + } + else + shouldSyncTracked = false; } else { logger.LogTrace("Not preserving test merges..."); await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false); + + var currentHead = repo.Head; + + currentRevInfo = await databaseContext.RevisionInformations + .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id) + .FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false); + + if (currentHead != startSha && currentRevInfo != default) + await UpdateRevInfo(currentHead, true).ConfigureAwait(false); + shouldSyncTracked = true; } //synch if necessary if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head) - await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + { + var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false); + var currentHead = repo.Head; + if (currentHead != currentRevInfo.CommitSha) + await UpdateRevInfo(currentHead, pushedOrigin).ConfigureAwait(false); + } + + if(hasDbChanges) + try + { + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + catch + { + await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false); + throw; + } progressReporter(5 * ProgressStep); + deploySha = repo.Head; } - repositoryUpdateSuccess = true; }, cancellationToken).ConfigureAwait(false); await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false); - if (!repositoryUpdateSuccess) + if (deploySha == null) { logger.LogTrace("Aborting auto update, repository error!"); continue; } + if(deploySha == LatestCompileJob()?.RevisionInformation.CommitSha) + { + logger.LogTrace("Aborting auto update, same revision as latest CompileJob"); + continue; + } + //finally set up the job var compileProcessJob = new Job { diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 323ae7a5ad..e993508172 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -122,8 +122,8 @@ namespace Tgstation.Server.Host.Components.Repository /// If the synchronizations should be made to the tracked reference as opposed to a temporary branch /// to report 0-100 progress of the operation /// The for the operation - /// A representing the running operation - Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); + /// A resulting in if commits were pushed to the tracked origin reference, otherwise + Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken); /// /// Copies the current working directory to a given diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 0c7f7687ee..c7652fddfc 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -489,7 +489,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken) + public async Task Sychronize(string username, string password, string committerName, string committerEmail, Action progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken) { if (committerName == null) throw new ArgumentNullException(nameof(committerName)); @@ -501,7 +501,7 @@ namespace Tgstation.Server.Host.Components.Repository if (username == null && password == null) { logger.LogTrace("Not synchronizing due to lack of credentials!"); - return; + return false; } logger.LogTrace("Begin Synchronize..."); @@ -526,7 +526,7 @@ namespace Tgstation.Server.Host.Components.Repository if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false)) { logger.LogDebug("Aborted synchronize due to event handler response!"); - return; + return false; } } finally @@ -548,32 +548,35 @@ namespace Tgstation.Server.Host.Components.Repository if (!synchronizeTrackedBranch) { await PushHeadToTemporaryBranch(username, password, FinalReporter, cancellationToken).ConfigureAwait(false); - return; + return false; } var sameHead = Head == startHead; if (sameHead || !Tracking) { logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference"); - return; + return false; } logger.LogInformation("Synchronizing with origin..."); - await Task.Factory.StartNew(() => + return await Task.Factory.StartNew(() => { var remote = repository.Network.Remotes.First(); try { repository.Network.Push(repository.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken)); + return true; } catch (NonFastForwardException) { logger.LogInformation("Synchronize aborted, non-fast forward!"); + return false; } - catch (UserCancelledException) + catch (UserCancelledException e) { cancellationToken.ThrowIfCancellationRequested(); + throw new InvalidOperationException("Caught UserCancelledException without cancellationToken triggering", e); } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 1baed19018..f2c1f12673 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -713,9 +713,11 @@ namespace Tgstation.Server.Host.Components.Watchdog await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - //both servers are now running, alpha is the active server, huzzah + //both servers are now running, alpha is the active server(unless reattach), huzzah AlphaIsActive = doReattach ? reattachInfo?.AlphaIsActive ?? true : true; LastLaunchResult = alphaLrt.Result; + (AlphaIsActive ? alphaServer : bravoServer).ClosePortOnReboot = true; + logger.LogInformation("Launched servers successfully"); Running = true; diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 325f639731..2e6d3a9ebc 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -53,7 +53,8 @@ namespace Tgstation.Server.Host.Controllers IrcChannel = api.IrcChannel, IsAdminChannel = api.IsAdminChannel ?? false, IsWatchdogChannel = api.IsWatchdogChannel ?? false, - IsUpdatesChannel = api.IsUpdatesChannel ?? false + IsUpdatesChannel = api.IsUpdatesChannel ?? false, + Tag = api.Tag }; /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index f8efbfb785..40f8837f7d 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS if (revisionInfo == default) - revisionInfo = databaseContext.RevisionInformations.Local.Where(x => x.CommitSha == repoSha).FirstOrDefault(); + revisionInfo = databaseContext.RevisionInformations.Local.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id).FirstOrDefault(); var needsDbUpdate = revisionInfo == default; if (needsDbUpdate) diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 17e1d0c367..99bdedf7a6 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -30,7 +30,8 @@ namespace Tgstation.Server.Host.Models IsAdminChannel = IsAdminChannel, IsWatchdogChannel = IsWatchdogChannel, IsUpdatesChannel = IsUpdatesChannel, - IrcChannel = IrcChannel + IrcChannel = IrcChannel, + Tag = Tag }; } }