mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 08:33:19 +01:00
Address all Host stylecop issues asside from complexity/maintainibilty
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <param name="path">The path to the BYOND installation</param>
|
||||
/// <param name="version">The <see cref="Version"/> of BYOND being installed</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task InstallByond(string path, Version version, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// </summary>
|
||||
/// <param name="version">The new <see cref="Version"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ChangeVersion(Version version, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
/// <summary>
|
||||
/// Construct a <see cref="VersionCommand"/>
|
||||
/// </summary>
|
||||
/// <param name="application"></param>
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
public VersionCommand(IApplication application)
|
||||
{
|
||||
this.application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
|
||||
@@ -38,6 +38,15 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
bool active;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="JsonTrackingContext"/>
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="customCommandHandler">The value of <see cref="customCommandHandler"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="onDispose">The value of <see cref="onDispose"/></param>
|
||||
/// <param name="commandsPath">The value of <see cref="commandsPath"/></param>
|
||||
/// <param name="channelsPath">The value of <see cref="channelsPath"/></param>
|
||||
public JsonTrackingContext(IIOManager ioManager, ICustomCommandHandler customCommandHandler, ILogger<JsonTrackingContext> logger, Action onDispose, string commandsPath, string channelsPath)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
|
||||
@@ -256,6 +256,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
client.Login(nickname, nickname, 0, nickname);
|
||||
}
|
||||
|
||||
@@ -317,6 +318,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (client.GetIrcUser(nickname) == null)
|
||||
client.RfcNick(nickname);
|
||||
}
|
||||
|
||||
client.Listen();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
@@ -329,6 +331,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
logger.LogWarning("Unable to connect to IRC: {0}", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
@@ -397,6 +400,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
id = channelIdCounter++;
|
||||
channelIdMap.Add(id.Value, x.IrcChannel);
|
||||
}
|
||||
|
||||
return new Channel
|
||||
{
|
||||
RealId = id.Value,
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token);
|
||||
Task otherTask;
|
||||
|
||||
// lock (this) //already locked below
|
||||
// lock (this) //already locked below
|
||||
otherTask = cleanupTask;
|
||||
await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <inheritdoc />
|
||||
public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName));
|
||||
|
||||
/// <inheritdoc />
|
||||
public RevisionInformation RevisionInformation => CompileJob.RevisionInformation;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/> for the <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -43,6 +43,5 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KeepAlive() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components
|
||||
readonly IConfiguration configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="EventConsumer"/>
|
||||
/// </summary>
|
||||
IWatchdog watchdog;
|
||||
|
||||
@@ -46,8 +46,10 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
public void SetWatchdog(IWatchdog watchdog)
|
||||
{
|
||||
#pragma warning disable IDE0016 // Use 'throw' expression
|
||||
if (watchdog == null)
|
||||
throw new ArgumentNullException(nameof(watchdog));
|
||||
#pragma warning restore IDE0016 // Use 'throw' expression
|
||||
if (this.watchdog != null)
|
||||
throw new InvalidOperationException("watchdog already set!");
|
||||
this.watchdog = watchdog;
|
||||
|
||||
@@ -9,18 +9,22 @@
|
||||
/// Parameters: Reference name, commit sha
|
||||
/// </summary>
|
||||
RepoResetOrigin = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Checkout target
|
||||
/// </summary>
|
||||
RepoCheckout = 1,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// </summary>
|
||||
RepoFetch = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Pull request number, pull request sha, merger message
|
||||
/// </summary>
|
||||
RepoMergePullRequest = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Absolute path to repository root
|
||||
/// </summary>
|
||||
@@ -30,26 +34,32 @@
|
||||
/// Parameters: Version being installed
|
||||
/// </summary>
|
||||
ByondInstallStart = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Error string
|
||||
/// </summary>
|
||||
ByondInstallFail = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Old active version, new active version
|
||||
/// </summary>
|
||||
ByondActiveVersionChange = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path, origin commit sha
|
||||
/// </summary>
|
||||
CompileStart = 8,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// </summary>
|
||||
CompileCancelled = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise
|
||||
/// </summary>
|
||||
CompileFailure = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path
|
||||
/// </summary>
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// The <see cref="IByondManager"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IByondManager ByondManager { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components
|
||||
IChat Chat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="StaticFiles.IConfiguration"/> for the <see cref="IInstance"/>
|
||||
/// The <see cref="IConfiguration"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IConfiguration Configuration { get; }
|
||||
|
||||
|
||||
@@ -27,9 +27,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public IByondManager ByondManager { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDreamMaker DreamMaker { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWatchdog Watchdog { get; }
|
||||
|
||||
@@ -39,8 +36,15 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public StaticFiles.IConfiguration Configuration { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICompileJobConsumer CompileJobConsumer { get; }
|
||||
/// <summary>
|
||||
/// The <see cref="IDreamMaker"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
readonly IDreamMaker dreamMaker;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICompileJobConsumer"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
readonly ICompileJobConsumer compileJobConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="Instance"/>
|
||||
@@ -81,6 +85,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// The auto update <see cref="Task"/>
|
||||
/// </summary>
|
||||
Task timerTask;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CancellationTokenSource"/> for <see cref="timerTask"/>
|
||||
/// </summary>
|
||||
@@ -92,11 +97,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="metadata">The value of <see cref="metadata"/></param>
|
||||
/// <param name="repositoryManager">The value of <see cref="RepositoryManager"/></param>
|
||||
/// <param name="byondManager">The value of <see cref="ByondManager"/></param>
|
||||
/// <param name="dreamMaker">The value of <see cref="DreamMaker"/></param>
|
||||
/// <param name="dreamMaker">The value of <see cref="dreamMaker"/></param>
|
||||
/// <param name="watchdog">The value of <see cref="Watchdog"/></param>
|
||||
/// <param name="chat">The value of <see cref="Chat"/></param>
|
||||
/// <param name="configuration">The value of <see cref="Configuration"/></param>
|
||||
/// <param name="compileJobConsumer">The value of <see cref="CompileJobConsumer"/></param>
|
||||
/// <param name="compileJobConsumer">The value of <see cref="compileJobConsumer"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
@@ -108,11 +113,11 @@ namespace Tgstation.Server.Host.Components
|
||||
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
|
||||
RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
|
||||
ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager));
|
||||
DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker));
|
||||
this.dreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker));
|
||||
Watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
CompileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
|
||||
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
@@ -125,7 +130,7 @@ namespace Tgstation.Server.Host.Components
|
||||
public void Dispose()
|
||||
{
|
||||
timerCts?.Dispose();
|
||||
CompileJobConsumer.Dispose();
|
||||
compileJobConsumer.Dispose();
|
||||
Configuration.Dispose();
|
||||
Chat.Dispose();
|
||||
Watchdog.Dispose();
|
||||
@@ -135,9 +140,10 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
//DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE
|
||||
#pragma warning disable IDE0016 // Use 'throw' expression
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
#pragma warning restore IDE0016 // Use 'throw' expression
|
||||
if (databaseContext == null)
|
||||
throw new ArgumentNullException(nameof(databaseContext));
|
||||
if (progressReporter == null)
|
||||
@@ -151,8 +157,6 @@ namespace Tgstation.Server.Host.Components
|
||||
var compileJobsTask = databaseContext.CompileJobs
|
||||
.Where(x => x.Job.Instance.Id == metadata.Id)
|
||||
.OrderByDescending(x => x.Job.StoppedAt)
|
||||
//TODO: Replace with this select when the issues linked in https://github.com/tgstation/tgstation-server/issues/737 are fixed
|
||||
//.Select(x => x.Job.StoppedAt.Value - x.Job.StartedAt.Value)
|
||||
.Select(x => new Job
|
||||
{
|
||||
StoppedAt = x.Job.StoppedAt,
|
||||
@@ -206,7 +210,7 @@ namespace Tgstation.Server.Host.Components
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
|
||||
databaseContext.Instances.Attach(revInfo.Instance);
|
||||
}
|
||||
|
||||
|
||||
TimeSpan? averageSpan = null;
|
||||
var previousCompileJobs = await compileJobsTask.ConfigureAwait(false);
|
||||
if(previousCompileJobs.Count != 0)
|
||||
@@ -217,14 +221,14 @@ namespace Tgstation.Server.Host.Components
|
||||
averageSpan = totalSpan / previousCompileJobs.Count;
|
||||
}
|
||||
|
||||
compileJob = await DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false);
|
||||
compileJob = await dreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.StartupTimeout.Value, repo, progressReporter, averageSpan, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
compileJob.Job = job;
|
||||
|
||||
databaseContext.CompileJobs.Add(compileJob); //will be saved by job context
|
||||
|
||||
job.PostComplete = ct => CompileJobConsumer.LoadCompileJob(compileJob, ct);
|
||||
databaseContext.CompileJobs.Add(compileJob); // will be saved by job context
|
||||
|
||||
job.PostComplete = ct => compileJobConsumer.LoadCompileJob(compileJob, ct);
|
||||
|
||||
if (repositorySettingsTask != null)
|
||||
{
|
||||
@@ -234,9 +238,9 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
if (repositorySettings.AccessToken != null)
|
||||
{
|
||||
//potential for commenting on a test merge change
|
||||
// potential for commenting on a test merge change
|
||||
var outgoingCompileJob = LatestCompileJob();
|
||||
|
||||
|
||||
if(outgoingCompileJob != null && outgoingCompileJob.RevisionInformation.CommitSha != compileJob.RevisionInformation.CommitSha)
|
||||
{
|
||||
var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken);
|
||||
@@ -263,10 +267,9 @@ namespace Tgstation.Server.Host.Components
|
||||
updated ? "Updated" : "Deployed",
|
||||
metadata.Name,
|
||||
compileJob.RevisionInformation.OriginCommitSha,
|
||||
compileJob.RevisionInformation.CommitSha
|
||||
);
|
||||
compileJob.RevisionInformation.CommitSha);
|
||||
|
||||
//added prs
|
||||
// added prs
|
||||
foreach (var I in compileJob
|
||||
.RevisionInformation
|
||||
.ActiveTestMerges
|
||||
@@ -277,7 +280,7 @@ namespace Tgstation.Server.Host.Components
|
||||
.Any(y => y.TestMerge.Number == x.Number)))
|
||||
tasks.Add(CommentOnPR(I.Number.Value, FormatTestMerge(I, false)));
|
||||
|
||||
//removed prs
|
||||
// removed prs
|
||||
foreach (var I in outgoingCompileJob
|
||||
.RevisionInformation
|
||||
.ActiveTestMerges
|
||||
@@ -288,7 +291,7 @@ namespace Tgstation.Server.Host.Components
|
||||
.Any(y => y.TestMerge.Number == x.Number)))
|
||||
tasks.Add(CommentOnPR(I.Number.Value, "#### Test Merge Removed"));
|
||||
|
||||
//updated prs
|
||||
// updated prs
|
||||
foreach(var I in compileJob
|
||||
.RevisionInformation
|
||||
.ActiveTestMerges
|
||||
@@ -341,11 +344,10 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
|
||||
|
||||
//assume 5 steps with synchronize
|
||||
// assume 5 steps with synchronize
|
||||
const int ProgressSections = 7;
|
||||
const int ProgressStep = 100 / ProgressSections;
|
||||
|
||||
|
||||
const int NumSteps = 3;
|
||||
var doneSteps = 0;
|
||||
|
||||
@@ -353,8 +355,8 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return progress => progressReporter((progress + 100 * tmpDoneSteps) / NumSteps);
|
||||
};
|
||||
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps);
|
||||
}
|
||||
|
||||
using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
@@ -374,7 +376,7 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);
|
||||
|
||||
//the main point of auto update is to pull the remote
|
||||
// the main point of auto update is to pull the remote
|
||||
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
RevisionInformation currentRevInfo = null;
|
||||
@@ -415,7 +417,7 @@ namespace Tgstation.Server.Host.Components
|
||||
hasDbChanges = true;
|
||||
}
|
||||
|
||||
//take appropriate auto update actions
|
||||
// take appropriate auto update actions
|
||||
bool shouldSyncTracked;
|
||||
if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
|
||||
{
|
||||
@@ -459,7 +461,7 @@ namespace Tgstation.Server.Host.Components
|
||||
shouldSyncTracked = true;
|
||||
}
|
||||
|
||||
//synch if necessary
|
||||
// synch if necessary
|
||||
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
|
||||
{
|
||||
var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
|
||||
@@ -498,7 +500,7 @@ namespace Tgstation.Server.Host.Components
|
||||
continue;
|
||||
}
|
||||
|
||||
//finally set up the job
|
||||
// finally set up the job
|
||||
var compileProcessJob = new Job
|
||||
{
|
||||
StartedBy = user,
|
||||
@@ -527,9 +529,10 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
logger.LogTrace("Leaving auto update loop...");
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Rename(string newName)
|
||||
{
|
||||
@@ -541,9 +544,9 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), CompileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false);
|
||||
await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false);
|
||||
|
||||
//dependent on so many things, its just safer this way
|
||||
// dependent on so many things, its just safer this way
|
||||
await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
CompileJob latestCompileJob = null;
|
||||
@@ -555,7 +558,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(0), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), CompileJobConsumer.StopAsync(cancellationToken));
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(0), Configuration.StopAsync(cancellationToken), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetAutoUpdateInterval(uint newInterval)
|
||||
@@ -571,12 +574,13 @@ namespace Tgstation.Server.Host.Components
|
||||
else
|
||||
toWait = Task.CompletedTask;
|
||||
}
|
||||
|
||||
await toWait.ConfigureAwait(false);
|
||||
if (newInterval == 0)
|
||||
return;
|
||||
lock (this)
|
||||
{
|
||||
//race condition, just quit
|
||||
// race condition, just quit
|
||||
if (timerTask != null)
|
||||
return;
|
||||
timerCts?.Dispose();
|
||||
|
||||
@@ -154,10 +154,10 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public IInstance CreateInstance(Models.Instance metadata)
|
||||
{
|
||||
//Create the ioManager for the instance
|
||||
// Create the ioManager for the instance
|
||||
var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path);
|
||||
|
||||
//various other ioManagers
|
||||
// various other ioManagers
|
||||
var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository");
|
||||
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
|
||||
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace Tgstation.Server.Host.Components
|
||||
return;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
foreach (var I in instances)
|
||||
I.Value.Dispose();
|
||||
}
|
||||
@@ -146,9 +147,10 @@ namespace Tgstation.Server.Host.Components
|
||||
throw new InvalidOperationException("Instance not online!");
|
||||
instances.Remove(metadata.Id);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//we are the one responsible for cancelling his jobs
|
||||
// we are the one responsible for cancelling his jobs
|
||||
var tasks = new List<Task>();
|
||||
await databaseContextFactory.UseContext(async db =>
|
||||
{
|
||||
@@ -194,6 +196,7 @@ namespace Tgstation.Server.Host.Components
|
||||
instance.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
await instance.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -236,7 +239,7 @@ namespace Tgstation.Server.Host.Components
|
||||
await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
|
||||
await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//downgrade the db if necessary
|
||||
// downgrade the db if necessary
|
||||
if (downgradeVersion != null)
|
||||
await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,24 @@
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a chat command to be handled by DD
|
||||
/// </summary>
|
||||
sealed class ChatCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// The command name
|
||||
/// </summary>
|
||||
public string Command { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The command params
|
||||
/// </summary>
|
||||
public string Params { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Chat.User"/> that sent the command
|
||||
/// </summary>
|
||||
public User User { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender of the event</param>
|
||||
/// <param name="e">The <see cref="FileSystemEventArgs"/></param>
|
||||
async void HandleWrite(object sender, FileSystemEventArgs e) //this is what async void was made for
|
||||
async void HandleWrite(object sender, FileSystemEventArgs e) // this is what async void was made for
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
//file not fully written yet
|
||||
// file not fully written yet
|
||||
logger.LogDebug("Suppressing json convert exception for command file write: {0}", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,31 +1,93 @@
|
||||
namespace Tgstation.Server.Host.Components.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants used for communication with the DMAPI
|
||||
/// </summary>
|
||||
static class Constants
|
||||
{
|
||||
//interop values, match them up with the appropriate api.dm
|
||||
|
||||
//api version 4.0.0.0
|
||||
/// <summary>
|
||||
/// Identifies a TGS execution. The server version
|
||||
/// </summary>
|
||||
public const string DMParamHostVersion = "server_service_version";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the <see cref="JsonFile"/>
|
||||
/// </summary>
|
||||
public const string DMParamInfoJson = "tgs_json";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="JsonFile.AccessIdentifier"/>
|
||||
/// </summary>
|
||||
public const string DMInteropAccessIdentifier = "tgs_tok";
|
||||
|
||||
/// <summary>
|
||||
/// Generic OK response
|
||||
/// </summary>
|
||||
public const string DMResponseSuccess = "tgs_succ";
|
||||
|
||||
/// <summary>
|
||||
/// Change port
|
||||
/// </summary>
|
||||
public const string DMTopicChangePort = "tgs_port";
|
||||
|
||||
/// <summary>
|
||||
/// Change reboot mode
|
||||
/// </summary>
|
||||
public const string DMTopicChangeReboot = "tgs_rmode";
|
||||
|
||||
/// <summary>
|
||||
/// Chat command
|
||||
/// </summary>
|
||||
public const string DMTopicChatCommand = "tgs_chat_comm";
|
||||
|
||||
/// <summary>
|
||||
/// Notify of an <see cref="EventType"/>
|
||||
/// </summary>
|
||||
public const string DMTopicEvent = "tgs_event";
|
||||
|
||||
/// <summary>
|
||||
/// Response to an interop export from DM
|
||||
/// </summary>
|
||||
public const string DMTopicInteropResponse = "tgs_interop";
|
||||
|
||||
/// <summary>
|
||||
/// Set port command
|
||||
/// </summary>
|
||||
public const string DMCommandNewPort = "tgs_new_port";
|
||||
|
||||
/// <summary>
|
||||
/// API validation command
|
||||
/// </summary>
|
||||
public const string DMCommandApiValidate = "tgs_validate";
|
||||
|
||||
/// <summary>
|
||||
/// Server primed command
|
||||
/// </summary>
|
||||
public const string DMCommandServerPrimed = "tgs_prime";
|
||||
|
||||
/// <summary>
|
||||
/// World reboot command
|
||||
/// </summary>
|
||||
public const string DMCommandWorldReboot = "tgs_reboot";
|
||||
|
||||
/// <summary>
|
||||
/// Terminate process command
|
||||
/// </summary>
|
||||
public const string DMCommandEndProcess = "tgs_kill";
|
||||
|
||||
/// <summary>
|
||||
/// Chat send command
|
||||
/// </summary>
|
||||
public const string DMCommandChat = "tgs_chat_send";
|
||||
|
||||
/// <summary>
|
||||
/// Topic command parameter
|
||||
/// </summary>
|
||||
public const string DMParameterCommand = "tgs_com";
|
||||
|
||||
/// <summary>
|
||||
/// Command data
|
||||
/// </summary>
|
||||
public const string DMParameterData = "tgs_data";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="committerEmail">The e-mail of the merge committer</param>
|
||||
/// <param name="username">The username to fetch from the origin repository</param>
|
||||
/// <param name="password">The password to fetch from the origin repository</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward or up to date, <see langword="false"/> on a non-fast-forward, <see langword="null"/> on a conflict</returns>
|
||||
Task<bool?> AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
|
||||
@@ -119,8 +119,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="password">The password to fetch from the origin repository</param>
|
||||
/// <param name="committerName">The name of the potential committer</param>
|
||||
/// <param name="committerEmail">The e-mail of the potential committer</param>
|
||||
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if commits were pushed to the tracked origin reference, <see langword="false"/> otherwise</returns>
|
||||
Task<bool> Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
|
||||
|
||||
@@ -84,27 +84,12 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly Action onDispose;
|
||||
|
||||
void GetRepositoryOwnerName(string remote, out string owner, out string name)
|
||||
{
|
||||
//Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
|
||||
//Yes use .git twice in case it was weird
|
||||
var toRemove = new string[] { ".git", "/", ".git" };
|
||||
foreach (string item in toRemove)
|
||||
if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
|
||||
remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
|
||||
var splits = remote.Split('/');
|
||||
name = splits[splits.Length - 1];
|
||||
owner = splits[splits.Length - 2].Split('.')[0];
|
||||
|
||||
logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a given <paramref name="progressReporter"/> to a <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/>
|
||||
/// </summary>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <returns>A <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/> based on <paramref name="progressReporter"/></returns>
|
||||
static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)((((float)completedSteps) / totalSteps) * 100));
|
||||
static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100));
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="Repository"/>
|
||||
@@ -140,6 +125,21 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
onDispose.Invoke();
|
||||
}
|
||||
|
||||
void GetRepositoryOwnerName(string remote, out string owner, out string name)
|
||||
{
|
||||
// Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
|
||||
// Yes use .git twice in case it was weird
|
||||
var toRemove = new string[] { ".git", "/", ".git" };
|
||||
foreach (string item in toRemove)
|
||||
if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
|
||||
remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
|
||||
var splits = remote.Split('/');
|
||||
name = splits[splits.Length - 1];
|
||||
owner = splits[splits.Length - 2].Split('.')[0];
|
||||
|
||||
logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a standard set of <see cref="PushOptions"/>
|
||||
/// </summary>
|
||||
@@ -264,7 +264,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
FailOnConflict = true,
|
||||
FastForwardStrategy = FastForwardStrategy.NoFastForward,
|
||||
SkipReuc = true,
|
||||
OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)((((float)completedSteps) / totalSteps) * 50)))
|
||||
OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
|
||||
});
|
||||
}
|
||||
finally
|
||||
@@ -373,7 +373,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
try
|
||||
{
|
||||
var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
|
||||
repository.Network.Push(remote,forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
|
||||
repository.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
|
||||
var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
|
||||
repository.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
|
||||
}
|
||||
@@ -510,6 +510,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
logger.LogTrace("Not synchronizing due to lack of credentials!");
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.LogTrace("Begin Synchronize...");
|
||||
|
||||
if (username == null)
|
||||
@@ -596,16 +597,18 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <inheritdoc />
|
||||
public Task<bool> IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
//check if it's a tag
|
||||
// check if it's a tag
|
||||
var gitObject = repository.Lookup(committish, ObjectType.Tag);
|
||||
if (gitObject != null)
|
||||
return false;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
//check if it's a branch
|
||||
|
||||
// check if it's a branch
|
||||
if (repository.Branches[committish] != null)
|
||||
return false;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
//err on the side of references, if we can't look it up, assume its a reference
|
||||
|
||||
// err on the side of references, if we can't look it up, assume its a reference
|
||||
if (repository.Lookup<Commit>(committish) != null)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
@@ -95,6 +95,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw new InvalidOperationException("The repository is already being cloned!");
|
||||
CloneInProgress = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
@@ -139,6 +140,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
else
|
||||
@@ -147,12 +149,14 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Clone complete!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloneInProgress = false;
|
||||
}
|
||||
|
||||
return await LoadRepository(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -188,6 +192,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
semaphore.Release();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
|
||||
{
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -131,12 +130,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// <inheritdoc />
|
||||
public async Task<ServerSideModifications> CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//just assume no other fs race conditions here
|
||||
// just assume no other fs race conditions here
|
||||
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
|
||||
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
|
||||
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
|
||||
@@ -154,7 +152,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
return null;
|
||||
|
||||
string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
|
||||
|
||||
|
||||
return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
|
||||
}
|
||||
}
|
||||
@@ -168,7 +166,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
configurationRelativePath = '.' + configurationRelativePath;
|
||||
var resolved = ioManager.ResolvePath(configurationRelativePath);
|
||||
var local = !nullOrEmptyCheck ? ioManager.ResolvePath(".") : null;
|
||||
if (!nullOrEmptyCheck && resolved.Length < local.Length) //.. fuccbois
|
||||
if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois
|
||||
throw new InvalidOperationException("Attempted to access file outside of configuration manager!");
|
||||
return resolved;
|
||||
}
|
||||
@@ -201,6 +199,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
enumerator = synchronousIOManager.GetFiles(path, cancellationToken);
|
||||
result.AddRange(enumerator.Select(x => new ConfigurationFile
|
||||
{
|
||||
@@ -236,7 +235,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1.
|
||||
using (var sha1 = new SHA1Managed())
|
||||
#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1.
|
||||
sha1String = String.Join("", sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
|
||||
sha1String = String.Join(String.Empty, sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
|
||||
result = new ConfigurationFile
|
||||
{
|
||||
Content = content,
|
||||
@@ -248,7 +247,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
//this happens on windows, dunno about linux
|
||||
// this happens on windows, dunno about linux
|
||||
bool isDirectory;
|
||||
try
|
||||
{
|
||||
@@ -289,7 +288,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
|
||||
var results = new List<string> { StaticIgnoreFile };
|
||||
|
||||
//we don't want to lose trailing whitespace on linux
|
||||
// we don't want to lose trailing whitespace on linux
|
||||
using (var reader = new StringReader(ignoreFileText))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -299,7 +298,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
}
|
||||
|
||||
IReadOnlyList<string> ignoreFiles;
|
||||
|
||||
@@ -316,9 +315,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
{
|
||||
var fileName = ioManager.GetFileName(x);
|
||||
|
||||
// need to normalize
|
||||
bool ignored;
|
||||
if (platformIdentifier.IsWindows)
|
||||
//need to normalize
|
||||
ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant());
|
||||
else
|
||||
ignored = ignoreFiles.Any(y => fileName == y);
|
||||
@@ -367,7 +366,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
if (!success)
|
||||
return;
|
||||
if (data != null)
|
||||
postWriteHandler.HandleWrite(path);
|
||||
postWriteHandler.HandleWrite(path);
|
||||
result = new ConfigurationFile
|
||||
{
|
||||
Content = data,
|
||||
@@ -379,7 +378,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
//this happens on windows, dunno about linux
|
||||
// this happens on windows, dunno about linux
|
||||
bool isDirectory;
|
||||
try
|
||||
{
|
||||
@@ -442,7 +441,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName))
|
||||
return true;
|
||||
|
||||
//always execute in serial
|
||||
// always execute in serial
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, cancellationToken).ConfigureAwait(false);
|
||||
@@ -458,6 +457,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -466,7 +466,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
{
|
||||
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
|
||||
var path = ValidateConfigRelativePath(configurationRelativePath);
|
||||
|
||||
|
||||
var result = false;
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
@@ -477,6 +477,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
else
|
||||
CheckDeleteImpl();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,27 @@
|
||||
/// The DMAPI never contacted the server for validation
|
||||
/// </summary>
|
||||
NeverValidated,
|
||||
|
||||
/// <summary>
|
||||
/// The server was contacted for validation but it was never requested
|
||||
/// </summary>
|
||||
UnaskedValidationRequest,
|
||||
|
||||
/// <summary>
|
||||
/// The validation request was malformed
|
||||
/// </summary>
|
||||
BadValidationRequest,
|
||||
|
||||
/// <summary>
|
||||
/// Valid API. The game must be run with a minimum security level of <see cref="Api.Models.DreamDaemonSecurity.Safe"/>
|
||||
/// </summary>
|
||||
RequiresSafe,
|
||||
|
||||
/// <summary>
|
||||
/// Valid API. The game must be run with a security level of <see cref="Api.Models.DreamDaemonSecurity.Trusted"/>
|
||||
/// </summary>
|
||||
RequiresTrusted,
|
||||
|
||||
/// <summary>
|
||||
/// Valid API. The game must be run with a minimum security level of <see cref="Api.Models.DreamDaemonSecurity.Ultrasafe"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -72,6 +72,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
Dmb.Dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,22 @@
|
||||
/// The monitor should continue as normal
|
||||
/// </summary>
|
||||
Continue,
|
||||
|
||||
/// <summary>
|
||||
/// Skips the next call to HandleMonitorWakeup action
|
||||
/// Skips the next call to HandleMonitorWakeup action
|
||||
/// </summary>
|
||||
Skip,
|
||||
|
||||
/// <summary>
|
||||
/// The monitor should kill and restart both servers
|
||||
/// </summary>
|
||||
Restart,
|
||||
|
||||
/// <summary>
|
||||
/// The monitor should stop checking actions for this iteration and continue its loop
|
||||
/// </summary>
|
||||
Break,
|
||||
|
||||
/// <summary>
|
||||
/// The monitor should exit. Does not kill servers
|
||||
/// </summary>
|
||||
|
||||
@@ -9,26 +9,32 @@
|
||||
/// The active server crashed or exited
|
||||
/// </summary>
|
||||
ActiveServerCrashed,
|
||||
|
||||
/// <summary>
|
||||
/// The inactive server crashed or exited
|
||||
/// </summary>
|
||||
InactiveServerCrashed,
|
||||
|
||||
/// <summary>
|
||||
/// The active server called /world/Reboot()
|
||||
/// </summary>
|
||||
ActiveServerRebooted,
|
||||
|
||||
/// <summary>
|
||||
/// The inactive server called /world/Reboot()
|
||||
/// </summary>
|
||||
InactiveServerRebooted,
|
||||
|
||||
/// <summary>
|
||||
/// The inactive server is past that point where DD hangs when you press "Go"
|
||||
/// </summary>
|
||||
InactiveServerStartupComplete,
|
||||
|
||||
/// <summary>
|
||||
/// A new .dmb was deployed
|
||||
/// </summary>
|
||||
NewDmbAvailable,
|
||||
|
||||
/// <summary>
|
||||
/// Server launch parameters were changed
|
||||
/// </summary>
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
//POSIX BYOND doesn't prompt you when you change the port
|
||||
/// <inheritdoc />
|
||||
sealed class PosixNetworkPromptReaper : INetworkPromptReaper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void RegisterProcess(IProcess process) { }
|
||||
public void RegisterProcess(IProcess process)
|
||||
{
|
||||
// POSIX BYOND doesn't prompt you when you change the port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
/// Run DreamDaemon's normal reboot process
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown DreamDaemon
|
||||
/// </summary>
|
||||
Shutdown = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Restart the DreamDaemon process
|
||||
/// </summary>
|
||||
|
||||
@@ -136,6 +136,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// The <see cref="TaskCompletionSource{TResult}"/> <see cref="SetPort(ushort, CancellationToken)"/> waits on when DreamDaemon currently has it's ports closed
|
||||
/// </summary>
|
||||
TaskCompletionSource<bool> portAssignmentTcs;
|
||||
|
||||
/// <summary>
|
||||
/// The port to assign DreamDaemon when it queries for it
|
||||
/// </summary>
|
||||
@@ -181,7 +182,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param>
|
||||
public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger<SessionController> logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout)
|
||||
{
|
||||
this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid
|
||||
this.chatJsonTrackingContext = chatJsonTrackingContext; // null valid
|
||||
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
this.process = process ?? throw new ArgumentNullException(nameof(process));
|
||||
@@ -219,17 +220,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
StartupTime = process.Startup.IsCompleted ? (TimeSpan?)(DateTimeOffset.Now - startTime) : null
|
||||
};
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
LaunchResult = GetLaunchResult();
|
||||
|
||||
logger.LogDebug("Created session controller. Primary: {0}, CommsKey: {1}, Port: {2}", IsPrimary, reattachInformation.AccessIdentifier, Port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalize the <see cref="SessionController"/>
|
||||
/// Finalizes an instance of the <see cref="SessionController"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>The finalizer dispose pattern is necessary so we don't accidentally leak the executable</remarks>
|
||||
#pragma warning disable CA1821 // Remove empty Finalizers //TODO remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed
|
||||
#pragma warning disable CA1821 // Remove empty Finalizers TODO: remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed
|
||||
~SessionController() => Dispose(false);
|
||||
#pragma warning restore CA1821 // Remove empty Finalizers
|
||||
|
||||
@@ -240,7 +242,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Implements the <see cref="IDisposable"/> pattern
|
||||
/// </summary>
|
||||
/// <param name="disposing">If this function was NOT called by the finalizer</param>
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
lock (this)
|
||||
@@ -254,9 +259,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
process.Terminate();
|
||||
byondLock.Dispose();
|
||||
}
|
||||
|
||||
process.Dispose();
|
||||
interopContext.Dispose();
|
||||
Dmb?.Dispose(); //will be null when released
|
||||
Dmb?.Dispose(); // will be null when released
|
||||
chatJsonTrackingContext.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
@@ -307,9 +313,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
logger.LogDebug("Exception while decoding chat message! Exception: {0}", e);
|
||||
goto default;
|
||||
}
|
||||
|
||||
break;
|
||||
case Constants.DMCommandServerPrimed:
|
||||
//currently unused, maybe in the future
|
||||
// currently unused, maybe in the future
|
||||
break;
|
||||
case Constants.DMCommandEndProcess:
|
||||
TerminationWasRequested = true;
|
||||
@@ -327,18 +334,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
if (!nextPort.HasValue)
|
||||
//not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
|
||||
reattachInformation.Port = currentPort;
|
||||
reattachInformation.Port = currentPort; // not ready yet, so what we'll do is accept the random port DD opened on for now and change it later when we decide to
|
||||
else
|
||||
{
|
||||
//nextPort is ready, tell DD to switch to that
|
||||
//if it fails it'll kill itself
|
||||
// nextPort is ready, tell DD to switch to that
|
||||
// if it fails it'll kill itself
|
||||
content = new Dictionary<string, ushort> { { Constants.DMParameterData, nextPort.Value } };
|
||||
reattachInformation.Port = nextPort.Value;
|
||||
overrideResponsePort = currentPort;
|
||||
nextPort = null;
|
||||
|
||||
//we'll also get here from SetPort so complete that task
|
||||
// we'll also get here from SetPort so complete that task
|
||||
var tmpTcs = portAssignmentTcs;
|
||||
portAssignmentTcs = null;
|
||||
if (tmpTcs != null)
|
||||
@@ -347,6 +353,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
portClosedForReboot = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case Constants.DMCommandApiValidate:
|
||||
if (!launchSecurityLevel.HasValue)
|
||||
@@ -356,6 +363,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
content = new ErrorMessage { Message = "Invalid API validation request!" };
|
||||
break;
|
||||
}
|
||||
|
||||
if (!query.TryGetValue(Constants.DMParameterData, out var stringMinimumSecurityLevelObject) || !Enum.TryParse<DreamDaemonSecurity>(stringMinimumSecurityLevelObject as string, out var minimumSecurityLevel))
|
||||
apiValidationStatus = ApiValidationStatus.BadValidationRequest;
|
||||
else
|
||||
@@ -373,6 +381,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
default:
|
||||
throw new InvalidOperationException("Enum.TryParse failed to validate the DreamDaemonSecurity range!");
|
||||
}
|
||||
|
||||
break;
|
||||
case Constants.DMCommandWorldReboot:
|
||||
if (ClosePortOnReboot)
|
||||
@@ -381,6 +390,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
content = new Dictionary<string, int> { { Constants.DMParameterData, 0 } };
|
||||
portClosedForReboot = true;
|
||||
}
|
||||
|
||||
var oldTcs = rebootTcs;
|
||||
rebootTcs = new TaskCompletionSource<object>();
|
||||
postRespond = () => oldTcs.SetResult(null);
|
||||
@@ -418,7 +428,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
public ReattachInformation Release()
|
||||
{
|
||||
CheckDisposed();
|
||||
//we still don't want to dispose the dmb yet, even though we're keeping it alive
|
||||
|
||||
// we still don't want to dispose the dmb yet, even though we're keeping it alive
|
||||
var tmpProvider = reattachInformation.Dmb;
|
||||
reattachInformation.Dmb = null;
|
||||
released = true;
|
||||
@@ -441,8 +452,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
byondTopicSender.SanitizeString(Constants.DMInteropAccessIdentifier),
|
||||
byondTopicSender.SanitizeString(reattachInformation.AccessIdentifier),
|
||||
byondTopicSender.SanitizeString(Constants.DMParameterCommand),
|
||||
//intentionally don't sanitize command, that's up to the caller
|
||||
command);
|
||||
command); // intentionally don't sanitize command, that's up to the caller
|
||||
|
||||
var targetPort = overridePort ?? reattachInformation.Port;
|
||||
logger.LogTrace("Export to :{0}. Query: {1}", targetPort, commandString);
|
||||
|
||||
@@ -5,7 +5,6 @@ using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -138,12 +137,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
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);
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
@@ -162,7 +161,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel));
|
||||
}
|
||||
|
||||
//setup interop files
|
||||
// setup interop files
|
||||
var interopInfo = new JsonFile
|
||||
{
|
||||
AccessIdentifier = accessIdentifier,
|
||||
@@ -197,19 +196,19 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
//get the byond lock
|
||||
// get the byond lock
|
||||
var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
//create interop context
|
||||
// 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
|
||||
// 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));
|
||||
|
||||
//important to run on all ports to allow port changing
|
||||
// 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} -verbose -public -params \"{4}\"",
|
||||
dmbProvider.DmbName,
|
||||
primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort,
|
||||
@@ -217,15 +216,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
SecurityWord(securityLevelToUse),
|
||||
parameters);
|
||||
|
||||
//See #719
|
||||
// See https://github.com/tgstation/tgstation-server/issues/719
|
||||
var noShellExecute = !platformIdentifier.IsWindows;
|
||||
//launch dd
|
||||
|
||||
// launch dd
|
||||
var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute);
|
||||
try
|
||||
{
|
||||
networkPromptReaper.RegisterProcess(process);
|
||||
|
||||
//return the session controller for it
|
||||
// return the session controller for it
|
||||
var result = new SessionController(new ReattachInformation
|
||||
{
|
||||
AccessIdentifier = accessIdentifier,
|
||||
@@ -238,7 +238,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ServerCommandsJson = interopInfo.ServerCommandsJson,
|
||||
}, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger<SessionController>(), launchParameters.SecurityLevel, launchParameters.StartupTimeout);
|
||||
|
||||
//writeback launch parameter's fixed security level
|
||||
// writeback launch parameter's fixed security level
|
||||
launchParameters.SecurityLevel = securityLevelToUse;
|
||||
|
||||
return result;
|
||||
@@ -317,6 +317,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (result == null)
|
||||
chatJsonTrackingContext.Dispose();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -134,6 +135,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// Server designation alpha
|
||||
/// </summary>
|
||||
ISessionController alphaServer;
|
||||
|
||||
/// <summary>
|
||||
/// Server designation bravo
|
||||
/// </summary>
|
||||
@@ -179,7 +181,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
if (serverControl == null)
|
||||
throw new ArgumentNullException(nameof(serverControl));
|
||||
|
||||
|
||||
chat.RegisterCommandHandler(this);
|
||||
|
||||
AlphaIsActive = true;
|
||||
@@ -239,7 +241,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return;
|
||||
}
|
||||
|
||||
//merely set the reboot state
|
||||
// merely set the reboot state
|
||||
var toKill = AlphaIsActive ? alphaServer : bravoServer;
|
||||
var other = AlphaIsActive ? bravoServer : alphaServer;
|
||||
if (toKill != null)
|
||||
@@ -257,26 +259,27 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
logger.LogDebug("Monitor activation. Reason: {0}", activationReason);
|
||||
|
||||
//this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry
|
||||
//I'll do my best to walk you through it
|
||||
// this is where the bulk of the watchdog handling code lives and is fraught with lambdas, sorry not sorry
|
||||
// I'll do my best to walk you through it
|
||||
|
||||
//returns true if the inactive server can't be used immediately
|
||||
//also sets monitor to restart if the above holds
|
||||
// returns true if the inactive server can't be used immediately
|
||||
// also sets monitor to restart if the above holds
|
||||
bool FullRestartDeadInactive()
|
||||
{
|
||||
if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail)
|
||||
{
|
||||
logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting");
|
||||
monitorState.NextAction = MonitorAction.Restart; //will dispose server
|
||||
monitorState.NextAction = MonitorAction.Restart; // will dispose server
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
//trys to set inactive server's port to the public game port
|
||||
//doesn't handle closing active server's port
|
||||
//returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters
|
||||
//on failure, sets monitor to restart
|
||||
return false;
|
||||
}
|
||||
|
||||
// trys to set inactive server's port to the public game port
|
||||
// doesn't handle closing active server's port
|
||||
// returns true on success and swaps inactiveserver and activeserver also sets LastLaunchParameters to ActiveLaunchParameters
|
||||
// on failure, sets monitor to restart
|
||||
async Task<bool> MakeInactiveActive()
|
||||
{
|
||||
logger.LogDebug("Setting inactive server to port {0}...", ActiveLaunchParameters.PrimaryPort.Value);
|
||||
@@ -285,11 +288,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (!result)
|
||||
{
|
||||
logger.LogWarning("Failed to activate inactive server! Restarting monitor...");
|
||||
monitorState.NextAction = MonitorAction.Restart; //will dispose server
|
||||
monitorState.NextAction = MonitorAction.Restart; // will dispose server
|
||||
return false;
|
||||
}
|
||||
|
||||
//inactive server should always be using active launch parameters
|
||||
// inactive server should always be using active launch parameters
|
||||
LastLaunchParameters = ActiveLaunchParameters;
|
||||
|
||||
var tmp = monitorState.ActiveServer;
|
||||
@@ -307,7 +310,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
async Task UpdateAndRestartInactiveServer(bool breakAfter)
|
||||
{
|
||||
activeParametersUpdated = new TaskCompletionSource<object>();
|
||||
monitorState.InactiveServer.Dispose(); //kill or recycle it
|
||||
monitorState.InactiveServer.Dispose(); // kill or recycle it
|
||||
var desiredNextAction = breakAfter ? MonitorAction.Break : MonitorAction.Continue;
|
||||
monitorState.NextAction = desiredNextAction;
|
||||
|
||||
@@ -325,17 +328,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error occurred while recreating server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString());
|
||||
//ahh jeez, what do we do here?
|
||||
//this is our fault, so it should never happen but
|
||||
//idk maybe a database error while handling the newest dmb?
|
||||
//either way try to start it using the active server's dmb as a backup
|
||||
|
||||
// ahh jeez, what do we do here?
|
||||
// this is our fault, so it should never happen but
|
||||
// idk maybe a database error while handling the newest dmb?
|
||||
// either way try to start it using the active server's dmb as a backup
|
||||
try
|
||||
{
|
||||
var dmbBackup = await dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (dmbBackup == null) //NANI!?
|
||||
//just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has
|
||||
throw new JobException("Creating backup DMB provider failed!");
|
||||
if (dmbBackup == null) // NANI!?
|
||||
throw new JobException("Creating backup DMB provider failed!"); // just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has
|
||||
|
||||
monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false);
|
||||
monitorState.InactiveServer.SetHighPriority();
|
||||
@@ -347,7 +350,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
//fuuuuucckkk
|
||||
// fuuuuucckkk
|
||||
logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! Exception: {0}", e2.ToString());
|
||||
monitorState.InactiveServerCritFail = true;
|
||||
await chat.SendWatchdogMessage("Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits", cancellationToken).ConfigureAwait(false);
|
||||
@@ -361,13 +364,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
string ExitWord(ISessionController controller) => controller.TerminationWasRequested ? "exited" : "crashed";
|
||||
|
||||
//reason handling
|
||||
// reason handling
|
||||
switch (activationReason)
|
||||
{
|
||||
case MonitorActivationReason.ActiveServerCrashed:
|
||||
if (monitorState.ActiveServer.RebootState == Components.Watchdog.RebootState.Shutdown)
|
||||
{
|
||||
//the time for graceful shutdown is now
|
||||
// the time for graceful shutdown is now
|
||||
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Exiting due to graceful termination request...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false);
|
||||
DisposeAndNullControllers();
|
||||
monitorState.NextAction = MonitorAction.Exit;
|
||||
@@ -376,118 +379,122 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
if (FullRestartDeadInactive())
|
||||
{
|
||||
//tell chat about it and go ahead
|
||||
// tell chat about it and go ahead
|
||||
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Inactive server unable to online!", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false);
|
||||
//we've already been set to restart
|
||||
|
||||
// we've already been set to restart
|
||||
break;
|
||||
}
|
||||
|
||||
//tell chat about it
|
||||
// tell chat about it
|
||||
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Onlining inactive server...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//try to active the inactive server
|
||||
// try to activate the inactive server
|
||||
if (!await MakeInactiveActive().ConfigureAwait(false))
|
||||
//failing that, we've already been set to restart
|
||||
break;
|
||||
break; // failing that, we've already been set to restart
|
||||
|
||||
//bring up another inactive server
|
||||
// bring up another inactive server
|
||||
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
|
||||
break;
|
||||
case MonitorActivationReason.InactiveServerCrashed:
|
||||
//just announce and try to bring it back
|
||||
// just announce and try to bring it back
|
||||
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Inactive server {0}! Rebooting...", ExitWord(monitorState.InactiveServer)), cancellationToken).ConfigureAwait(false);
|
||||
await UpdateAndRestartInactiveServer(false).ConfigureAwait(false);
|
||||
break;
|
||||
case MonitorActivationReason.ActiveServerRebooted:
|
||||
//ideal goal: active server just closed its port
|
||||
//tell inactive server to open it's port and that's now the active server
|
||||
// ideal goal: active server just closed its port
|
||||
// tell inactive server to open it's port and that's now the active server
|
||||
var rebootState = monitorState.ActiveServer.RebootState;
|
||||
monitorState.ActiveServer.ResetRebootState(); //the DMAPI has already done this internally
|
||||
monitorState.ActiveServer.ResetRebootState(); // the DMAPI has already done this internally
|
||||
|
||||
if (FullRestartDeadInactive() && rebootState != Components.Watchdog.RebootState.Shutdown)
|
||||
//full restart if the inactive server is being fucky
|
||||
break;
|
||||
break; // full restart if the inactive server is being fucky
|
||||
|
||||
//what matters here is the RebootState
|
||||
// what matters here is the RebootState
|
||||
var restartOnceSwapped = false;
|
||||
|
||||
switch (rebootState)
|
||||
{
|
||||
case Components.Watchdog.RebootState.Normal:
|
||||
//life as normal
|
||||
// life as normal
|
||||
break;
|
||||
case Components.Watchdog.RebootState.Restart:
|
||||
//reboot the current active server once the inactive one activates
|
||||
// reboot the current active server once the inactive one activates
|
||||
restartOnceSwapped = true;
|
||||
break;
|
||||
case Components.Watchdog.RebootState.Shutdown:
|
||||
//graceful shutdown time
|
||||
// graceful shutdown time
|
||||
await chat.SendWatchdogMessage("Active server rebooted! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false);
|
||||
DisposeAndNullControllers();
|
||||
monitorState.NextAction = MonitorAction.Exit;
|
||||
return;
|
||||
default:
|
||||
Trace.Assert(false, String.Format(CultureInfo.InvariantCulture, "Invalid RebootState: {0}!", rebootState));
|
||||
break;
|
||||
}
|
||||
|
||||
//are both servers now running the same CompileJob?
|
||||
// are both servers now running the same CompileJob?
|
||||
var sameCompileJob = monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id;
|
||||
|
||||
|
||||
if (!sameCompileJob || ActiveLaunchParameters != LastLaunchParameters)
|
||||
//need a new launch to update either settings or compile job
|
||||
restartOnceSwapped = true;
|
||||
restartOnceSwapped = true; // need a new launch to update either settings or compile job
|
||||
|
||||
if (restartOnceSwapped)
|
||||
//we need to manually restart active server
|
||||
//just kill it here, easier that way
|
||||
/*
|
||||
* we need to manually restart active server
|
||||
* just kill it here, easier that way
|
||||
*/
|
||||
monitorState.ActiveServer.Dispose();
|
||||
|
||||
var activeServerStillHasPortOpen = !restartOnceSwapped && !monitorState.ActiveServer.ClosePortOnReboot;
|
||||
|
||||
if (activeServerStillHasPortOpen)
|
||||
//we didn't want active server to swap for some reason and it still has it's port open
|
||||
//just continue as normal
|
||||
/* we didn't want active server to swap for some reason and it still has it's port open
|
||||
* just continue as normal
|
||||
*/
|
||||
break;
|
||||
|
||||
|
||||
if (!await MakeInactiveActive().ConfigureAwait(false))
|
||||
//monitor will restart
|
||||
break;
|
||||
break; // monitor will restart
|
||||
|
||||
//servers now swapped
|
||||
|
||||
//enable this now if inactive server is not still valid
|
||||
// servers now swapped
|
||||
// enable this now if inactive server is not still valid
|
||||
monitorState.ActiveServer.ClosePortOnReboot = restartOnceSwapped;
|
||||
|
||||
if (!restartOnceSwapped)
|
||||
//now try to reopen it on the private port
|
||||
//failing that, just reboot it
|
||||
/*
|
||||
* now try to reopen it on the private port
|
||||
* failing that, just reboot it
|
||||
*/
|
||||
restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//break either way because any issues past this point would be solved by the reboot
|
||||
if (restartOnceSwapped)
|
||||
//for one reason or another
|
||||
//update and reboot
|
||||
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
|
||||
// break either way because any issues past this point would be solved by the reboot
|
||||
if (restartOnceSwapped) // for one reason or another
|
||||
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); // update and reboot
|
||||
else
|
||||
//only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration
|
||||
monitorState.NextAction = MonitorAction.Skip;
|
||||
monitorState.NextAction = MonitorAction.Skip; // only skip checking inactive server rebooted, it's guaranteed InactiveServerStartup complete wouldn't fire this iteration
|
||||
break;
|
||||
case MonitorActivationReason.InactiveServerRebooted:
|
||||
//just don't let the active server close it's port if the inactive server isn't ready
|
||||
// just don't let the active server close it's port if the inactive server isn't ready
|
||||
monitorState.RebootingInactiveServer = true;
|
||||
monitorState.InactiveServer.ResetRebootState();
|
||||
monitorState.ActiveServer.ClosePortOnReboot = false;
|
||||
monitorState.NextAction = MonitorAction.Continue;
|
||||
break;
|
||||
case MonitorActivationReason.InactiveServerStartupComplete:
|
||||
//opposite of above case
|
||||
// opposite of above case
|
||||
monitorState.RebootingInactiveServer = false;
|
||||
monitorState.ActiveServer.ClosePortOnReboot = true;
|
||||
monitorState.NextAction = MonitorAction.Continue;
|
||||
break;
|
||||
case MonitorActivationReason.NewDmbAvailable:
|
||||
case MonitorActivationReason.ActiveLaunchParametersUpdated:
|
||||
//just reload the inactive server and wait for a swap to apply the changes
|
||||
// just reload the inactive server and wait for a swap to apply the changes
|
||||
await UpdateAndRestartInactiveServer(true).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
Trace.Assert(false, String.Format(CultureInfo.InvariantCulture, "Invalid monitor activation reason: {0}!", activationReason));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,15 +507,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
logger.LogTrace("Entered MonitorLifetimes");
|
||||
|
||||
//this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState
|
||||
|
||||
// this function is responsible for calling HandlerMonitorWakeup when necessary and manitaining the MonitorState
|
||||
var iteration = 1;
|
||||
for (var monitorState = new MonitorState(); monitorState.NextAction != MonitorAction.Exit; ++iteration)
|
||||
{
|
||||
//always start out with continue
|
||||
// always start out with continue
|
||||
monitorState.NextAction = MonitorAction.Continue;
|
||||
|
||||
//dump some info to the logs
|
||||
// dump some info to the logs
|
||||
logger.LogDebug("Iteration {0} of monitor loop", iteration);
|
||||
try
|
||||
{
|
||||
@@ -516,12 +522,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
logger.LogDebug("Alpha is the active server");
|
||||
else
|
||||
logger.LogDebug("Bravo is the active server");
|
||||
|
||||
|
||||
|
||||
if (monitorState.RebootingInactiveServer)
|
||||
logger.LogDebug("Inactive server is rebooting");
|
||||
|
||||
//update the monitor state with the inactive/active servers
|
||||
// update the monitor state with the inactive/active servers
|
||||
monitorState.ActiveServer = AlphaIsActive ? alphaServer : bravoServer;
|
||||
monitorState.InactiveServer = AlphaIsActive ? bravoServer : alphaServer;
|
||||
|
||||
@@ -533,7 +538,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
logger.LogDebug("Active server Compile Job ID: {0}", monitorState.ActiveServer.Dmb.CompileJob.Id);
|
||||
logger.LogDebug("Inactive server Compile Job ID: {0}", monitorState.InactiveServer.Dmb.CompileJob.Id);
|
||||
|
||||
//load the activation tasks into local variables
|
||||
// load the activation tasks into local variables
|
||||
Task activeServerLifetime = monitorState.ActiveServer.Lifetime;
|
||||
Task inactiveServerLifetime = monitorState.InactiveServer.Lifetime;
|
||||
var activeServerReboot = monitorState.ActiveServer.OnReboot;
|
||||
@@ -542,14 +547,15 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
Task activeLaunchParametersChanged = activeParametersUpdated.Task;
|
||||
var newDmbAvailable = dmbFactory.OnNewerDmb;
|
||||
|
||||
//cancel waiting if requested
|
||||
// cancel waiting if requested
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
{
|
||||
var toWaitOn = Task.WhenAny(activeServerLifetime, inactiveServerLifetime, activeServerReboot, inactiveServerReboot, newDmbAvailable, cancelTcs.Task, activeLaunchParametersChanged);
|
||||
if (monitorState.RebootingInactiveServer)
|
||||
toWaitOn = Task.WhenAny(toWaitOn, inactiveServerStartup);
|
||||
//wait for something to happen
|
||||
|
||||
// wait for something to happen
|
||||
await toWaitOn.ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
@@ -557,14 +563,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var chatTask = Task.CompletedTask;
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
//always run HandleMonitorWakeup from the context of the semaphore lock
|
||||
//multiple things may have happened, handle them one at a time
|
||||
// always run HandleMonitorWakeup from the context of the semaphore lock
|
||||
// multiple things may have happened, handle them one at a time
|
||||
for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);)
|
||||
{
|
||||
MonitorActivationReason activationReason = default; //this will always be assigned before being used
|
||||
|
||||
//process the tasks in this order and call HandlerMonitorWakup for each
|
||||
MonitorActivationReason activationReason = default; // this will always be assigned before being used
|
||||
|
||||
// process the tasks in this order and call HandlerMonitorWakup for each
|
||||
bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason)
|
||||
{
|
||||
var taskCompleted = task?.IsCompleted == true;
|
||||
@@ -576,8 +581,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
activationReason = testActivationReason;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed)
|
||||
|| CheckActivationReason(ref inactiveServerLifetime, MonitorActivationReason.InactiveServerCrashed)
|
||||
@@ -591,12 +597,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
moreActivationsToProcess = false;
|
||||
}
|
||||
|
||||
//writeback alphaServer and bravoServer from monitor state in case they changesd
|
||||
// writeback alphaServer and bravoServer from monitor state in case they changesd
|
||||
alphaServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer;
|
||||
bravoServer = !AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer;
|
||||
}
|
||||
|
||||
//full reboot required
|
||||
// full reboot required
|
||||
if (monitorState.NextAction == MonitorAction.Restart)
|
||||
{
|
||||
logger.LogDebug("Next state action is to restart");
|
||||
@@ -609,12 +615,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
//use LaunchImplNoLock without announcements or restarting the monitor
|
||||
// use LaunchImplNoLock without announcements or restarting the monitor
|
||||
await LaunchImplNoLock(false, false, null, cancellationToken).ConfigureAwait(false);
|
||||
if (Running)
|
||||
{
|
||||
logger.LogDebug("Relaunch successful, resetting monitor state...");
|
||||
monitorState = new MonitorState(); //clean the slate and continue
|
||||
monitorState = new MonitorState(); // clean the slate and continue
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -633,7 +639,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}", retryAttempts);
|
||||
else
|
||||
logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}, Exception: {1}", retryAttempts, launchException);
|
||||
var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour, increasing by a power of 2 each time
|
||||
var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); // max of one hour, increasing by a power of 2 each time
|
||||
chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken);
|
||||
await Task.WhenAll(asyncDelayer.Delay(TimeSpan.FromSeconds(retryDelay), cancellationToken), chatTask).ConfigureAwait(false);
|
||||
}
|
||||
@@ -647,11 +653,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//really, this should NEVER happen
|
||||
// really, this should NEVER happen
|
||||
logger.LogError("Monitor crashed! Iteration: {0}, State: {1}, Exception: {2}", iteration, JsonConvert.SerializeObject(monitorState), e);
|
||||
await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Monitor crashed, this should NEVER happen! Please report this, full details in logs! Restarting monitor... Error: {0}", e.Message), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogTrace("Monitor exiting...");
|
||||
}
|
||||
|
||||
@@ -671,7 +678,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -681,8 +687,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return;
|
||||
ActiveLaunchParameters = launchParameters;
|
||||
if (Running)
|
||||
//queue an update
|
||||
activeParametersUpdated.TrySetResult(null);
|
||||
activeParametersUpdated.TrySetResult(null); // queue an update
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,67 +706,68 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (Running)
|
||||
throw new JobException("Watchdog already running!");
|
||||
|
||||
// this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers
|
||||
Task chatTask;
|
||||
//this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers
|
||||
if (startMonitor && await StopMonitor().ConfigureAwait(false))
|
||||
chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken);
|
||||
else if (announce)
|
||||
//simple announce
|
||||
chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken);
|
||||
chatTask = chat.SendWatchdogMessage(reattachInfo == null ? "Starting..." : "Reattaching...", cancellationToken); // simple announce
|
||||
else
|
||||
//no announce
|
||||
chatTask = Task.CompletedTask;
|
||||
|
||||
//since neither server is running, this is safe to do
|
||||
chatTask = Task.CompletedTask; // no announce
|
||||
|
||||
// since neither server is running, this is safe to do
|
||||
LastLaunchParameters = ActiveLaunchParameters;
|
||||
|
||||
//for when we call ourself and want to not catch thrown exceptions
|
||||
// for when we call ourself and want to not catch thrown exceptions
|
||||
var ignoreNestedException = false;
|
||||
try
|
||||
{
|
||||
//good ole sanity, should never fucking trigger but i don't trust myself even though I should
|
||||
// good ole sanity, should never fucking trigger but i don't trust myself even though I should
|
||||
// TODO: Unit test this instead?
|
||||
if (alphaServer != null || bravoServer != null)
|
||||
throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!");
|
||||
|
||||
//don't need a new dmb if reattaching
|
||||
|
||||
// don't need a new dmb if reattaching
|
||||
var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
|
||||
var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2);
|
||||
|
||||
//if this try catches something, both servers are killed
|
||||
// if this try catches something, both servers are killed
|
||||
try
|
||||
{
|
||||
//start the alpha server task, either by launch a new process or attaching to an existing one
|
||||
//The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
|
||||
//The tasks pertaining to server startup times are in the ISessionControllers
|
||||
// start the alpha server task, either by launch a new process or attaching to an existing one
|
||||
// The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
|
||||
// The tasks pertaining to server startup times are in the ISessionControllers
|
||||
Task<ISessionController> alphaServerTask;
|
||||
if (!doesntNeedNewDmb)
|
||||
alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, cancellationToken);
|
||||
else
|
||||
alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken);
|
||||
|
||||
//retrieve the session controller
|
||||
// retrieve the session controller
|
||||
var startTime = DateTimeOffset.Now;
|
||||
alphaServer = await alphaServerTask.ConfigureAwait(false);
|
||||
//failed reattaches will return null
|
||||
|
||||
// failed reattaches will return null
|
||||
alphaServer?.SetHighPriority();
|
||||
|
||||
//extra delay for total ordering
|
||||
// extra delay for total ordering
|
||||
var now = DateTimeOffset.Now;
|
||||
var delay = now - startTime;
|
||||
|
||||
//definitely not if reattaching though
|
||||
// definitely not if reattaching though
|
||||
if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval)
|
||||
await asyncDelayer.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//now bring bravo up
|
||||
// now bring bravo up
|
||||
if (!doesntNeedNewDmb)
|
||||
bravoServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken).ConfigureAwait(false);
|
||||
else
|
||||
bravoServer = await sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken).ConfigureAwait(false);
|
||||
//failed reattaches will return null
|
||||
|
||||
// failed reattaches will return null
|
||||
bravoServer?.SetHighPriority();
|
||||
|
||||
//possiblity of null servers due to failed reattaches
|
||||
// possiblity of null servers due to failed reattaches
|
||||
if (alphaServer == null || bravoServer == null)
|
||||
{
|
||||
await chatTask.ConfigureAwait(false);
|
||||
@@ -770,7 +776,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|| (alphaServer == null && reattachInfo.AlphaIsActive)
|
||||
|| (bravoServer == null && !reattachInfo.AlphaIsActive))
|
||||
{
|
||||
//we lost the active server, just restart entirely
|
||||
// we lost the active server, just restart entirely
|
||||
DisposeAndNullControllers();
|
||||
const string FailReattachMessage = "Unable to properly reattach to active server! Restarting...";
|
||||
logger.LogWarning(FailReattachMessage);
|
||||
@@ -781,7 +787,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return;
|
||||
}
|
||||
|
||||
//we still have the active server but the other one is dead to us, hand it off to the monitor to restart
|
||||
// we still have the active server but the other one is dead to us, hand it off to the monitor to restart
|
||||
const string InactiveReattachFailureMessage = "Unable to reattach to inactive server. Leaving for monitor to reboot...";
|
||||
chatTask = chat.SendWatchdogMessage(InactiveReattachFailureMessage, cancellationToken);
|
||||
logger.LogWarning(InactiveReattachFailureMessage);
|
||||
@@ -792,13 +798,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
alphaServer = sessionControllerFactory.CreateDeadSession(reattachInfo.Alpha.Dmb);
|
||||
}
|
||||
|
||||
//throws a JobException if something went wrong with a launch
|
||||
//Dead sessions won't trigger this
|
||||
// throws a JobException if something went wrong with a launch
|
||||
// Dead sessions won't trigger this
|
||||
async Task<LaunchResult> CheckLaunch(ISessionController controller, string serverName)
|
||||
{
|
||||
var launch = await controller.LaunchResult.ConfigureAwait(false);
|
||||
if (launch.ExitCode.HasValue)
|
||||
//you killed us ray...
|
||||
if (launch.ExitCode.HasValue) // you killed us ray...
|
||||
throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName));
|
||||
if (!launch.StartupTime.HasValue)
|
||||
throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value));
|
||||
@@ -807,10 +812,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
var alphaLrt = CheckLaunch(alphaServer, "Alpha");
|
||||
var bravoLrt = CheckLaunch(bravoServer, "Bravo");
|
||||
//this task completes when both serers have finished booting
|
||||
|
||||
// this task completes when both serers have finished booting
|
||||
var allTask = Task.WhenAll(alphaLrt, bravoLrt);
|
||||
|
||||
//don't forget about the cancellationToken
|
||||
// don't forget about the cancellationToken
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false);
|
||||
@@ -818,7 +824,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
await allTask.ConfigureAwait(false);
|
||||
|
||||
//both servers are now running, alpha is the active server(unless reattach), huzzah
|
||||
// both servers are now running, alpha is the active server(unless reattach), huzzah
|
||||
AlphaIsActive = reattachInfo?.AlphaIsActive ?? true;
|
||||
|
||||
var activeServer = AlphaIsActive ? alphaServer : bravoServer;
|
||||
@@ -838,34 +844,32 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
if (dmbToUse != null)
|
||||
{
|
||||
//we locked 2 dmbs
|
||||
// we locked 2 dmbs
|
||||
if (bravoServer == null)
|
||||
{
|
||||
//bravo didn't get control of his
|
||||
// bravo didn't get control of his
|
||||
dmbToUse.Dispose();
|
||||
if (alphaServer == null)
|
||||
//alpha didn't get control of his
|
||||
dmbToUse.Dispose();
|
||||
dmbToUse.Dispose(); // alpha didn't get control of his
|
||||
}
|
||||
}
|
||||
else if (doesntNeedNewDmb)
|
||||
//we have reattachInfo
|
||||
else if (doesntNeedNewDmb) // we have reattachInfo
|
||||
if (bravoServer == null)
|
||||
{
|
||||
//bravo didn't get control of his
|
||||
// bravo didn't get control of his
|
||||
reattachInfo.Bravo?.Dmb.Dispose();
|
||||
if (alphaServer == null)
|
||||
//alpha didn't get control of his
|
||||
reattachInfo.Alpha?.Dmb.Dispose();
|
||||
reattachInfo.Alpha?.Dmb.Dispose(); // alpha didn't get control of his
|
||||
}
|
||||
//kill the controllers
|
||||
|
||||
// kill the controllers
|
||||
DisposeAndNullControllers();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled
|
||||
// don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled
|
||||
if (!ignoreNestedException && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var originalChatTask = chatTask;
|
||||
@@ -874,14 +878,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
await originalChatTask.ConfigureAwait(false);
|
||||
await chat.SendWatchdogMessage("Startup failed!", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
chatTask = ChainChatTaskWithErrorMessage();
|
||||
logger.LogWarning("Failed to start watchdog: {0}", e.ToString());
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
//finish the chat task that's in flight
|
||||
// finish the chat task that's in flight
|
||||
try
|
||||
{
|
||||
await chatTask.ConfigureAwait(false);
|
||||
@@ -929,6 +935,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
await LaunchImplNoLock(true, !Running, null, cancellationToken).ConfigureAwait(false);
|
||||
await chatTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var toReboot = AlphaIsActive ? alphaServer : bravoServer;
|
||||
if (toReboot != null)
|
||||
{
|
||||
@@ -994,6 +1001,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
reattachInformation.Bravo = bravoServer?.Release();
|
||||
await reattachInfoHandler.Save(reattachInformation, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Terminate(false, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// Number of times to send the button click message. Should be at least 2 or it may fail to focus the window
|
||||
/// </summary>
|
||||
const int SendMessageCount = 5;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check for prompts each time this amount of milliseconds pass
|
||||
/// </summary>
|
||||
@@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
readonly CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// The list of <see cref="IProcess"/>s registered
|
||||
/// The list of <see cref="IProcess"/>s registered
|
||||
/// </summary>
|
||||
readonly List<IProcess> registeredProcesses;
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
gcChildhandlesList.Free();
|
||||
}
|
||||
|
||||
return childHandles;
|
||||
}
|
||||
|
||||
@@ -117,12 +118,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (window == IntPtr.Zero)
|
||||
continue;
|
||||
|
||||
//found a bitch
|
||||
// found a bitch
|
||||
var threadId = NativeMethods.GetWindowThreadProcessId(window, out processId);
|
||||
if (!registeredProcesses.Any(x => x.Id == processId))
|
||||
//not our bitch
|
||||
continue;
|
||||
continue; // not our bitch
|
||||
}
|
||||
|
||||
logger.LogTrace("Identified \"Network Accessibility\" window in owned process {0}", processId);
|
||||
|
||||
var found = false;
|
||||
@@ -140,13 +141,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var windowText = stringBuilder.ToString();
|
||||
if (windowText == "Yes")
|
||||
{
|
||||
//smash_button_meme.jpg
|
||||
// smash_button_meme.jpg
|
||||
logger.LogTrace("Sending \"Yes\" button clicks...");
|
||||
for (var J = 0; J < SendMessageCount; ++J)
|
||||
{
|
||||
const int BM_CLICK = 0x00F5;
|
||||
var result = NativeMethods.SendMessage(I, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
/// Use Microsoft SQL Server
|
||||
/// </summary>
|
||||
SqlServer,
|
||||
|
||||
/// <summary>
|
||||
/// Use MySQL
|
||||
/// </summary>
|
||||
MySql,
|
||||
|
||||
/// <summary>
|
||||
/// Use MariaDB
|
||||
/// </summary>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// Where log files are stored
|
||||
/// </summary>
|
||||
public string Directory { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If file logging is disabled
|
||||
/// </summary>
|
||||
@@ -40,7 +40,6 @@ namespace Tgstation.Server.Host.Configuration
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LogLevel LogLevel { get; set; } = DefaultLogLevel;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The minimum <see cref="Microsoft.Extensions.Logging.LogLevel"/> to display in logs for Microsoft library sources
|
||||
/// </summary>
|
||||
|
||||
@@ -9,14 +9,17 @@
|
||||
/// Run the wizard if the appsettings.{Environment}.json is not present or empty
|
||||
/// </summary>
|
||||
Autodetect,
|
||||
|
||||
/// <summary>
|
||||
/// Force run the wizard
|
||||
/// </summary>
|
||||
Force,
|
||||
|
||||
/// <summary>
|
||||
/// Only run the wizard and exit
|
||||
/// </summary>
|
||||
Only,
|
||||
|
||||
/// <summary>
|
||||
/// Never run the wizard
|
||||
/// </summary>
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
@@ -126,6 +125,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
Logger.LogWarning("Not found exception while retrieving upstream repository info: {0}", e);
|
||||
}
|
||||
|
||||
return Json(new Administration
|
||||
{
|
||||
LatestVersion = greatestVersion,
|
||||
@@ -188,19 +188,22 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault();
|
||||
if (asset == default)
|
||||
continue;
|
||||
|
||||
|
||||
if (!serverUpdater.ApplyUpdate(version, new Uri(asset.BrowserDownloadUrl), ioManager))
|
||||
return Conflict(new ErrorMessage
|
||||
{
|
||||
Message = "An update operation is already in progress!"
|
||||
});
|
||||
return Accepted(); //gtfo of here before all the cancellation tokens fire
|
||||
return Accepted(); // gtfo of here before all the cancellation tokens fire
|
||||
}
|
||||
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Attempts to restart the server
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request</returns>
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(AdministrationRights.RestartHost)]
|
||||
public async Task<IActionResult> Delete()
|
||||
@@ -215,6 +218,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Message = RestartNotSupportedException
|
||||
});
|
||||
}
|
||||
|
||||
await serverUpdater.Restart().ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ByondRights.ReadActive)]
|
||||
public override Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)
|
||||
public override Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult<IActionResult>(
|
||||
Json(new Api.Models.Byond
|
||||
{
|
||||
Version = instanceManager.GetInstance(Instance).ByondManager.ActiveVersion
|
||||
@@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ByondRights.ListInstalled)]
|
||||
public override Task<IActionResult> List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)
|
||||
public override Task<IActionResult> List(CancellationToken cancellationToken) => Task.FromResult<IActionResult>(
|
||||
Json(instanceManager.GetInstance(Instance).ByondManager.InstalledVersions.Select(x => new Api.Models.Byond
|
||||
{
|
||||
Version = x
|
||||
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var byondManager = instanceManager.GetInstance(Instance).ByondManager;
|
||||
|
||||
//remove cruff fields
|
||||
// remove cruff fields
|
||||
var installingVersion = new Version(model.Version.Major, model.Version.Minor);
|
||||
|
||||
var result = new Api.Models.Byond();
|
||||
@@ -86,7 +86,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("User ID {0} installing BYOND version to {2} on instance ID {1}", AuthenticationContext.User.Id, Instance.Id, installingVersion);
|
||||
//run the install through the job manager
|
||||
|
||||
// run the install through the job manager
|
||||
var job = new Models.Job
|
||||
{
|
||||
Description = String.Format(CultureInfo.InvariantCulture, "Install BYOND version {0}", installingVersion),
|
||||
@@ -98,6 +99,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false);
|
||||
result.InstallJob = job.ToApi();
|
||||
}
|
||||
|
||||
if ((AuthenticationContext.GetRight(RightsType.Byond) & (ulong)ByondRights.ReadActive) != 0)
|
||||
result.Version = byondManager.ActiveVersion;
|
||||
return result.InstallJob != null ? (IActionResult)Accepted(result) : Json(result);
|
||||
|
||||
@@ -87,13 +87,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
model.Enabled = model.Enabled ?? false;
|
||||
|
||||
//try to update das db first
|
||||
// try to update das db first
|
||||
var dbModel = new Models.ChatBot
|
||||
{
|
||||
Name = model.Name,
|
||||
ConnectionString = model.ConnectionString,
|
||||
Enabled = model.Enabled,
|
||||
Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List<Models.ChatChannel>(), //important that this isn't null
|
||||
Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List<Models.ChatChannel>(), // important that this isn't null
|
||||
InstanceId = Instance.Id,
|
||||
Provider = model.Provider,
|
||||
};
|
||||
@@ -106,7 +106,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
//try to create it
|
||||
// try to create it
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
await instance.Chat.ChangeSettings(dbModel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
catch
|
||||
{
|
||||
//undo the add
|
||||
// undo the add
|
||||
DatabaseContext.ChatBots.Remove(dbModel);
|
||||
await DatabaseContext.Save(default).ConfigureAwait(false);
|
||||
throw;
|
||||
@@ -125,6 +125,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
return BadRequest(new ErrorMessage { Message = e.Message });
|
||||
}
|
||||
|
||||
return StatusCode((int)HttpStatusCode.Created, dbModel.ToApi());
|
||||
}
|
||||
|
||||
@@ -208,7 +209,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
property.SetValue(current, newVal);
|
||||
anySettingsModified = true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
var oldProvider = current.Provider;
|
||||
|
||||
@@ -238,8 +239,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var chat = instanceManager.GetInstance(Instance).Chat;
|
||||
|
||||
if (anySettingsModified)
|
||||
//have to rebuild the thing first
|
||||
await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
|
||||
await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); // have to rebuild the thing first
|
||||
|
||||
if (model.Channels != null || anySettingsModified)
|
||||
await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false);
|
||||
@@ -250,6 +250,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
current.ConnectionString = null;
|
||||
return Json(current.ToApi());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
systemIdentityToUse = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
systemIdentityToUse = Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite ? AuthenticationContext.SystemIdentity : null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// The <see cref="IJobManager"/> for the <see cref="DreamMakerController"/>
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceManager"/> for the <see cref="DreamMakerController"/>
|
||||
/// </summary>
|
||||
@@ -52,7 +53,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize(DreamDaemonRights.Start)]
|
||||
public override async Task<IActionResult> Create([FromBody] DreamDaemon model, CancellationToken cancellationToken)
|
||||
{
|
||||
//alias for launching DD
|
||||
// alias for launching DD
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
|
||||
if (instance.Watchdog.Running)
|
||||
@@ -94,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (settings == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
}
|
||||
|
||||
|
||||
var result = new DreamDaemon();
|
||||
if (metadata)
|
||||
{
|
||||
@@ -113,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
result.SoftRestart = rstate == RebootState.Restart;
|
||||
result.SoftShutdown = rstate == RebootState.Shutdown;
|
||||
result.StartupTimeout = settings.StartupTimeout;
|
||||
};
|
||||
}
|
||||
|
||||
if (revision)
|
||||
{
|
||||
@@ -126,12 +127,15 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Stops DreamDaemon if it's running
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(DreamDaemonRights.Shutdown)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken)
|
||||
{
|
||||
//alias for stopping DD
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
await instance.Watchdog.Terminate(false, cancellationToken).ConfigureAwait(false);
|
||||
return Ok();
|
||||
@@ -150,7 +154,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model.SecurityLevel == DreamDaemonSecurity.Ultrasafe)
|
||||
return BadRequest(new ErrorMessage { Message = "This version of TGS does not support the ultrasafe DreamDaemon configuration!" });
|
||||
|
||||
//alias for changing DD settings
|
||||
// alias for changing DD settings
|
||||
var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (current == default)
|
||||
@@ -171,7 +175,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
property.SetValue(current, newVal);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
var oldSoftRestart = current.SoftRestart;
|
||||
var oldSoftShutdown = current.SoftShutdown;
|
||||
@@ -190,9 +194,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return BadRequest(new ErrorMessage { Message = "Primary port and secondary port cannot be the same!" });
|
||||
|
||||
var wd = instanceManager.GetInstance(Instance).Watchdog;
|
||||
|
||||
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
//run this second because current may be modified by it
|
||||
|
||||
// run this second because current may be modified by it
|
||||
await wd.ChangeSettings(current, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!oldSoftRestart.Value && current.SoftRestart.Value)
|
||||
|
||||
@@ -20,12 +20,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// Controller for managing the compiler
|
||||
/// </summary>
|
||||
[Route(Routes.DreamMaker)]
|
||||
public sealed class DreamMakerController : ModelController<Api.Models.DreamMaker>
|
||||
public sealed class DreamMakerController : ModelController<DreamMaker>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="DreamMakerController"/>
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceManager"/> for the <see cref="DreamMakerController"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
@@ -23,18 +22,22 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// The <see cref="ITokenFactory"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly ITokenFactory tokenFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly ISystemIdentityFactory systemIdentityFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly ICryptographySuite cryptographySuite;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApplication"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly IApplication application;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIdentityCache"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
@@ -86,13 +89,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
ISystemIdentity identity;
|
||||
try
|
||||
{
|
||||
//trust the system over the database because a user's name can change while still having the same SID
|
||||
// trust the system over the database because a user's name can change while still having the same SID
|
||||
identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
identity = null;
|
||||
}
|
||||
|
||||
using (identity)
|
||||
{
|
||||
IQueryable<User> query;
|
||||
@@ -127,7 +131,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
//check if the name changed and updoot accordingly
|
||||
|
||||
// check if the name changed and updoot accordingly
|
||||
else if (identity.Username != user.Name)
|
||||
{
|
||||
DatabaseContext.Users.Attach(user);
|
||||
@@ -142,7 +147,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false);
|
||||
if (identity != null)
|
||||
{
|
||||
//expire the identity slightly after the auth token in case of lag
|
||||
// expire the identity slightly after the auth token in case of lag
|
||||
var identExpiry = token.ExpiresAt.Value;
|
||||
identExpiry += tokenFactory.ValidationParameters.ClockSkew;
|
||||
identExpiry += TimeSpan.FromSeconds(15);
|
||||
|
||||
@@ -87,6 +87,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
absolutePath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
absolutePath = ioManager.ResolvePath(model.Path);
|
||||
if (platformIdentifier.IsWindows)
|
||||
model.Path = absolutePath.ToUpperInvariant();
|
||||
@@ -130,7 +131,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (rawPath.StartsWith(normalizedLocalPath, StringComparison.Ordinal))
|
||||
return Conflict("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))
|
||||
@@ -171,8 +171,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
AutoUpdatesKeepTestMerges = false,
|
||||
AutoUpdatesSynchronize = false
|
||||
},
|
||||
//give this user full privileges on the instance
|
||||
InstanceUsers = new List<Models.InstanceUser>
|
||||
InstanceUsers = new List<Models.InstanceUser> // give this user full privileges on the instance
|
||||
{
|
||||
InstanceAdminUser()
|
||||
}
|
||||
@@ -185,13 +184,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
//actually reserve it now
|
||||
// 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
|
||||
// oh shit delete the model
|
||||
DatabaseContext.Instances.Remove(newInstance);
|
||||
|
||||
await DatabaseContext.Save(default).ConfigureAwait(false);
|
||||
@@ -243,7 +242,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var attachFileName = ioManager.ConcatPath(originalModel.Path, InstanceAttachFileName);
|
||||
await ioManager.WriteAllBytes(attachFileName, Array.Empty<byte>(), default).ConfigureAwait(false);
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); //cascades everything
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); // cascades everything
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -264,8 +263,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (moveJob != default)
|
||||
//cancel it now
|
||||
await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false); // cancel it now
|
||||
|
||||
var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
@@ -273,7 +271,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
.Include(x => x.RepositorySettings)
|
||||
.Include(x => x.ChatSettings)
|
||||
.ThenInclude(x => x.Channels)
|
||||
.Include(x => x.DreamDaemonSettings) //need these for onlining
|
||||
.Include(x => x.DreamDaemonSettings) // need these for onlining
|
||||
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (originalModel == default(Models.Instance))
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
@@ -292,7 +290,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
property.SetValue(originalModel, newVal);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
string originalModelPath = null;
|
||||
string rawPath = null;
|
||||
@@ -326,7 +324,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|| CheckModified(x => x.Online, InstanceManagerRights.SetOnline))
|
||||
return Forbid();
|
||||
|
||||
//ensure the current user has write privilege on the instance
|
||||
// ensure the current user has write privilege on the instance
|
||||
var usersInstanceUser = await usersInstanceUserTask.ConfigureAwait(false);
|
||||
if (usersInstanceUser == default)
|
||||
{
|
||||
@@ -349,8 +347,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await instanceManager.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken).ConfigureAwait(false);
|
||||
else if (!originalOnline && model.Online == true)
|
||||
{
|
||||
//force autostart false here because we don't want any long running jobs right now
|
||||
//remember to document this
|
||||
// force autostart false here because we don't want any long running jobs right now
|
||||
// remember to document this
|
||||
originalModel.DreamDaemonSettings.AutoStart = false;
|
||||
await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -412,7 +410,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var apis = instances.Select(x => x.ToApi());
|
||||
var moveJobs = await moveJobTasks.ConfigureAwait(false);
|
||||
foreach(var I in moveJobs)
|
||||
apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); //if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible
|
||||
apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); // if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible
|
||||
return Json(apis);
|
||||
}
|
||||
|
||||
@@ -440,7 +438,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (cantList && !instance.InstanceUsers.Any(x => x.UserId == AuthenticationContext.User.Id && x.AnyRights))
|
||||
return Forbid();
|
||||
|
||||
|
||||
var api = instance.ToApi();
|
||||
api.MoveJob = (await moveJobTask.ConfigureAwait(false))?.ToApi();
|
||||
return Json(api);
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
|
||||
public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger<InstanceUserController> logger) : base(databaseContext, authenticationContextFactory, logger, true) //false instance requirement, we handle this ourself
|
||||
public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger<InstanceUserController> logger) : base(databaseContext, authenticationContextFactory, logger, true) // false instance requirement, we handle this ourself
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -116,7 +116,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize(InstanceUserRights.ReadUsers)]
|
||||
public override async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
//this functions as userId
|
||||
// this functions as userId
|
||||
var user = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (user == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize]
|
||||
public override async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
//you KNOW this will need pagination eventually right?
|
||||
// you KNOW this will need pagination eventually right?
|
||||
var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.Job
|
||||
{
|
||||
Id = x.Id
|
||||
@@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize]
|
||||
public override async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
//don't care if an instance post or not at this point
|
||||
// don't care if an instance post or not at this point
|
||||
var job = await DatabaseContext.Jobs.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (job == default(Job))
|
||||
return NotFound();
|
||||
|
||||
@@ -83,24 +83,26 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var needsDbUpdate = revisionInfo == default;
|
||||
if (needsDbUpdate)
|
||||
{
|
||||
//needs insertion
|
||||
// needs insertion
|
||||
revisionInfo = new Models.RevisionInformation
|
||||
{
|
||||
Instance = instance,
|
||||
CommitSha = repoSha,
|
||||
CompileJobs = new List<Models.CompileJob>(),
|
||||
ActiveTestMerges = new List<RevInfoTestMerge>() //non null vals for api returns
|
||||
ActiveTestMerges = new List<RevInfoTestMerge>() // non null vals for api returns
|
||||
};
|
||||
|
||||
lock (databaseContext) //cleaner this way
|
||||
lock (databaseContext) // cleaner this way
|
||||
databaseContext.RevisionInformations.Add(revisionInfo);
|
||||
}
|
||||
|
||||
revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha;
|
||||
if (revisionInfo.OriginCommitSha == null)
|
||||
{
|
||||
revisionInfo.OriginCommitSha = repoSha;
|
||||
Logger.LogWarning(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha);
|
||||
}
|
||||
|
||||
revInfoSink?.Invoke(revisionInfo);
|
||||
return needsDbUpdate;
|
||||
}
|
||||
@@ -112,10 +114,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
model.GitHubOwner = repository.GitHubOwner;
|
||||
model.GitHubName = repository.GitHubRepoName;
|
||||
}
|
||||
|
||||
model.Origin = repository.Origin;
|
||||
model.Reference = repository.Reference;
|
||||
|
||||
//rev info stuff
|
||||
// rev info stuff
|
||||
Models.RevisionInformation revisionInfo = null;
|
||||
var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
|
||||
model.RevisionInformation = revisionInfo.ToApi();
|
||||
@@ -140,7 +143,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (currentModel == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
//normalize github urls
|
||||
// normalize github urls
|
||||
const string BadGitHubUrl = "://www.github.com/";
|
||||
var uiOrigin = model.Origin.ToUpperInvariant();
|
||||
var uiBad = BadGitHubUrl.ToUpperInvariant();
|
||||
@@ -149,7 +152,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal);
|
||||
|
||||
currentModel.AccessToken = model.AccessToken;
|
||||
currentModel.AccessUser = model.AccessUser; //intentionally only these fields, user not allowed to change anything else atm
|
||||
currentModel.AccessUser = model.AccessUser; // intentionally only these fields, user not allowed to change anything else atm
|
||||
var cloneBranch = model.Reference;
|
||||
var origin = model.Origin;
|
||||
|
||||
@@ -169,8 +172,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// clone conflict
|
||||
if (repo != null)
|
||||
//clone conflict
|
||||
return Conflict(new ErrorMessage
|
||||
{
|
||||
Message = "The repository already exists!"
|
||||
@@ -269,10 +272,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
//user may have fucked with the repo without telling us, do what we can
|
||||
// user may have fucked with the repo without telling us, do what we can
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
return StatusCode((int)HttpStatusCode.Created, api);
|
||||
}
|
||||
|
||||
return Json(api);
|
||||
}
|
||||
}
|
||||
@@ -331,7 +335,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
property.SetValue(currentModel, newVal);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
|
||||
|| CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
|
||||
@@ -346,7 +350,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
|
||||
{
|
||||
//setting an empty string clears everything
|
||||
// setting an empty string clears everything
|
||||
currentModel.AccessUser = null;
|
||||
currentModel.AccessToken = null;
|
||||
}
|
||||
@@ -380,11 +384,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
//this is just db stuf so stow it away
|
||||
|
||||
// this is just db stuf so stow it away
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//format the job description
|
||||
// format the job description
|
||||
string description = null;
|
||||
if (model.UpdateFromOrigin == true)
|
||||
if (model.Reference != null)
|
||||
@@ -397,16 +401,15 @@ namespace Tgstation.Server.Host.Controllers
|
||||
description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha);
|
||||
|
||||
if (newTestMerges)
|
||||
description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
|
||||
description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
|
||||
description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T",
|
||||
String.Join(", ", model.NewTestMerges.Select(x =>
|
||||
String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
|
||||
String.Join(", ", model.NewTestMerges.Select(x =>
|
||||
String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
|
||||
x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))),
|
||||
description != null ? String.Empty : " in repository");
|
||||
|
||||
if (description == null)
|
||||
//no git changes
|
||||
return Json(api);
|
||||
return Json(api); // no git changes
|
||||
|
||||
var job = new Models.Job
|
||||
{
|
||||
@@ -444,14 +447,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return progress => progressReporter((progress + 100 * tmpDoneSteps) / numSteps);
|
||||
};
|
||||
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
|
||||
}
|
||||
|
||||
progressReporter(0);
|
||||
|
||||
//get a base line for where we are
|
||||
// get a base line for where we are
|
||||
Models.RevisionInformation lastRevisionInfo = null;
|
||||
|
||||
|
||||
var attachedInstance = new Models.Instance
|
||||
{
|
||||
Id = Instance.Id
|
||||
@@ -460,17 +463,17 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
|
||||
//apply new rev info, tracking applied test merges
|
||||
// apply new rev info, tracking applied test merges
|
||||
async Task UpdateRevInfo()
|
||||
{
|
||||
var last = lastRevisionInfo;
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, last.OriginCommitSha, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//fetch/pull
|
||||
// fetch/pull
|
||||
if (model.UpdateFromOrigin == true)
|
||||
{
|
||||
if (!repo.Tracking)
|
||||
@@ -494,7 +497,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
//checkout/hard reset
|
||||
// checkout/hard reset
|
||||
if (modelHasShaOrReference)
|
||||
{
|
||||
if ((model.CheckoutSha != null && repo.Head.ToUpperInvariant().StartsWith(model.CheckoutSha.ToUpperInvariant(), StringComparison.Ordinal))
|
||||
@@ -507,7 +510,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
throw new JobException("Attempted to checkout a SHA or reference that was actually the opposite!");
|
||||
|
||||
await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
|
||||
}
|
||||
else
|
||||
NextProgressReporter()(100);
|
||||
@@ -519,35 +522,36 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
//repo head is on origin so force this
|
||||
//will update the db if necessary
|
||||
|
||||
// repo head is on origin so force this
|
||||
// will update the db if necessary
|
||||
lastRevisionInfo.OriginCommitSha = repo.Head;
|
||||
}
|
||||
}
|
||||
|
||||
// test merging
|
||||
Dictionary<int, Octokit.PullRequest> prMap = null;
|
||||
//test merging
|
||||
if (newTestMerges)
|
||||
{
|
||||
//bit of sanitization
|
||||
// bit of sanitization
|
||||
foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
|
||||
I.PullRequestRevision = null;
|
||||
|
||||
var gitHubClient = currentModel.AccessToken != null
|
||||
? gitHubClientFactory.CreateClient(currentModel.AccessToken)
|
||||
var gitHubClient = currentModel.AccessToken != null
|
||||
? gitHubClientFactory.CreateClient(currentModel.AccessToken)
|
||||
: (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
|
||||
? gitHubClientFactory.CreateClient()
|
||||
? gitHubClientFactory.CreateClient()
|
||||
: gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken));
|
||||
|
||||
var repoOwner = repo.GitHubOwner;
|
||||
var repoName = repo.GitHubRepoName;
|
||||
|
||||
// optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
|
||||
Models.RevisionInformation revInfoWereLookingFor = null;
|
||||
bool needToApplyRemainingPrs = true;
|
||||
//optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
|
||||
if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
|
||||
{
|
||||
//In order for this to work though we need the shas of all the commits
|
||||
// In order for this to work though we need the shas of all the commits
|
||||
if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
|
||||
prMap = new Dictionary<int, Octokit.PullRequest>();
|
||||
|
||||
@@ -555,14 +559,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
foreach (var I in model.NewTestMerges)
|
||||
{
|
||||
if (I.PullRequestRevision != null)
|
||||
//normalize the shas to lowercase ala libgit2
|
||||
#pragma warning disable CA1308 // Normalize strings to uppercase
|
||||
I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant();
|
||||
I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
else
|
||||
//retrieve the latest sha
|
||||
try
|
||||
{
|
||||
// retrieve the latest sha
|
||||
var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
|
||||
prMap.Add(I.Number.Value, pr);
|
||||
I.PullRequestRevision = pr.Head.Sha;
|
||||
@@ -585,7 +588,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//split here cause this bit has to be done locally
|
||||
// split here cause this bit has to be done locally
|
||||
revInfoWereLookingFor = dbPull
|
||||
.Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
|
||||
&& x.ActiveTestMerges.Select(y => y.TestMerge)
|
||||
@@ -597,7 +600,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (revInfoWereLookingFor == null && model.NewTestMerges.Count > 1)
|
||||
{
|
||||
//okay try to add at least SOME prs we've seen before
|
||||
// okay try to add at least SOME prs we've seen before
|
||||
var search = model.NewTestMerges.ToList();
|
||||
|
||||
var appliedTestMergeIds = new List<long>();
|
||||
@@ -623,7 +626,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (revInfoWereLookingFor != null && search.Count > 0);
|
||||
}
|
||||
while (revInfoWereLookingFor != null && search.Count > 0);
|
||||
|
||||
revInfoWereLookingFor = lastGoodRevInfo;
|
||||
needToApplyRemainingPrs = search.Count != 0;
|
||||
@@ -637,14 +641,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (revInfoWereLookingFor != null)
|
||||
{
|
||||
//goteem
|
||||
// goteem
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
|
||||
lastRevisionInfo = revInfoWereLookingFor;
|
||||
}
|
||||
|
||||
if (needToApplyRemainingPrs)
|
||||
{
|
||||
//an invocation of LoadRevisionInformation could have already loaded this user
|
||||
// an invocation of LoadRevisionInformation could have already loaded this user
|
||||
var contextUser = databaseContext.Users.Local.Where(x => x.Id == AuthenticationContext.User.Id).FirstOrDefault();
|
||||
if (contextUser == default)
|
||||
{
|
||||
@@ -667,13 +671,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
//load from cache if possible
|
||||
// load from cache if possible
|
||||
if (prMap == null || !prMap.TryGetValue(I.Number.Value, out pr))
|
||||
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
|
||||
}
|
||||
catch (Octokit.RateLimitExceededException)
|
||||
{
|
||||
//you look at your anonymous access and sigh
|
||||
// you look at your anonymous access and sigh
|
||||
errorMessage = "P.R.E. RATE LIMITED";
|
||||
}
|
||||
catch (Octokit.AuthorizationException)
|
||||
@@ -682,11 +686,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
catch (Octokit.NotFoundException)
|
||||
{
|
||||
//you look at your shithub and sigh
|
||||
// you look at your shithub and sigh
|
||||
errorMessage = "P.R.E. NOT FOUND";
|
||||
}
|
||||
|
||||
//we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
|
||||
// we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
|
||||
if (I.PullRequestRevision == null && pr != null)
|
||||
I.PullRequestRevision = pr.Head.Sha;
|
||||
|
||||
@@ -729,13 +733,15 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
|
||||
await UpdateRevInfo().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await databaseContext.Save(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
doneSteps = 0;
|
||||
numSteps = 2;
|
||||
//the stuff didn't make it into the db, forget what we've done and abort
|
||||
|
||||
// the stuff didn't make it into the db, forget what we've done and abort
|
||||
await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
|
||||
if (startReference != null && repo.Head != startSha)
|
||||
await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
|
||||
|
||||
@@ -33,6 +33,7 @@ using Tgstation.Server.Host.Security;
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506
|
||||
sealed class Application : IApplication
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -12,8 +12,10 @@ namespace Tgstation.Server.Host.Core
|
||||
public interface IJobManager : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="Api.Models.Job.Progress"/> for a job
|
||||
/// Get the <see cref="Api.Models.Job.Progress"/> for a <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to get <see cref="Api.Models.Job.Progress"/> for</param>
|
||||
/// <returns>The <see cref="Api.Models.Job.Progress"/> of <paramref name="job"/></returns>
|
||||
int? JobProgress(Job job);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
/// Launch a <see cref="IProcess"/>
|
||||
/// </summary>
|
||||
/// <param name="fileName">The full path to the executable file</param>
|
||||
/// <param name="arguments">The arguments for the <see cref="IProcess"/></param>
|
||||
/// <param name="workingDirectory">The working directory for the <see cref="IProcess"/></param>
|
||||
/// <param name="arguments">The arguments for the <see cref="IProcess"/></param>
|
||||
/// <param name="readOutput">If standard output should be read</param>
|
||||
/// <param name="readError">If standard error should be read</param>
|
||||
/// <param name="noShellExecute">If shell execute should not be used. Ignored if <paramref name="readError"/> or <paramref name="readOutput"/> are set</param>
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <summary>
|
||||
/// Run the setup wizard if necessary
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the wizard ran, <see langword="false"/> otherwise</returns>
|
||||
Task<bool> CheckRunWizard(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ namespace Tgstation.Server.Host.Core
|
||||
job.Cancelled = true;
|
||||
job.StoppedAt = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
@@ -28,6 +28,16 @@ namespace Tgstation.Server.Host.Core
|
||||
/// </summary>
|
||||
readonly ILogger<Process> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="Process"/>
|
||||
/// </summary>
|
||||
/// <param name="handle">The value of <see cref="handle"/></param>
|
||||
/// <param name="lifetime">The value of <see cref="Lifetime"/></param>
|
||||
/// <param name="outputStringBuilder">The value of <see cref="outputStringBuilder"/></param>
|
||||
/// <param name="errorStringBuilder">The value of <see cref="errorStringBuilder"/></param>
|
||||
/// <param name="combinedStringBuilder">The value of <see cref="combinedStringBuilder"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="preExisting">If <paramref name="handle"/> was NOT just created</param>
|
||||
public Process(System.Diagnostics.Process handle, Task<int> lifetime, StringBuilder outputStringBuilder, StringBuilder errorStringBuilder, StringBuilder combinedStringBuilder, ILogger<Process> logger, bool preExisting)
|
||||
{
|
||||
this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
@@ -103,6 +113,7 @@ namespace Tgstation.Server.Host.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetHighPriority()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -180,6 +180,7 @@ namespace Tgstation.Server.Host.IO
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
results.Add(I);
|
||||
}
|
||||
|
||||
return results;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
@@ -238,6 +239,7 @@ namespace Tgstation.Server.Host.IO
|
||||
results.Add(I);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
return (IReadOnlyList<string>)results;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
@@ -252,6 +254,7 @@ namespace Tgstation.Server.Host.IO
|
||||
results.Add(I);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
return (IReadOnlyList<string>)results;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles changing file modes/permissions after writing
|
||||
/// </summary>
|
||||
interface IPostWriteHandler
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Models.Migrations
|
||||
/// <summary>
|
||||
/// The initial database migration for MSSQL
|
||||
/// </summary>
|
||||
#pragma warning disable CA1506
|
||||
public partial class MSInitialCreate : Migration
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Models.Migrations
|
||||
/// <summary>
|
||||
/// The initial database migration for MySQL/MariaDB
|
||||
/// </summary>
|
||||
#pragma warning disable CA1506
|
||||
public partial class MYInitialCreate : Migration
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <returns>A new <see cref="Repository"/></returns>
|
||||
public Repository ToApi() => new Repository
|
||||
{
|
||||
// AccessToken = AccessToken, //never show this
|
||||
// AccessToken = AccessToken, // never show this
|
||||
AccessUser = AccessUser,
|
||||
AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges,
|
||||
AutoUpdatesSynchronize = AutoUpdatesSynchronize,
|
||||
|
||||
@@ -7,6 +7,10 @@ namespace Tgstation.Server.Host
|
||||
/// <summary>
|
||||
/// Native methods used by the code
|
||||
/// </summary>
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1602
|
||||
#pragma warning disable SA1611
|
||||
#pragma warning disable SA1615
|
||||
static class NativeMethods
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -63,8 +63,8 @@ namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
if (identity != null)
|
||||
{
|
||||
// var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841
|
||||
var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally
|
||||
// var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841
|
||||
var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally
|
||||
|
||||
return new WindowsSystemIdentity(newIdentity);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace Tgstation.Server.Host.Security
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
return principal != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702;SA1652</NoWarn>
|
||||
<DocumentationFile>D:\tgstation-server\src\Tgstation.Server.Host\.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user