mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-21 12:06:59 +01:00
Auto updates working at last
This commit is contained in:
@@ -39,13 +39,21 @@ namespace Tgstation.Server.Host.Console
|
||||
}
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel();
|
||||
System.Console.CancelKeyPress += (a, b) =>
|
||||
void AppDomainHandler(object a, EventArgs b) => cts.Cancel();
|
||||
AppDomain.CurrentDomain.ProcessExit += AppDomainHandler;
|
||||
try
|
||||
{
|
||||
b.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false);
|
||||
System.Console.CancelKeyPress += (a, b) =>
|
||||
{
|
||||
b.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(arguments.ToArray(), cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit -= AppDomainHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,4 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="echo 'Publishing host to watchdog target path for configuration $(ConfigurationName)'
dotnet publish $(ProjectDir)../Tgstation.Server.Host/Tgstation.Server.Host.csproj -c $(ConfigurationName) -o $(TargetDir)lib/Default" />
|
||||
</Target>
|
||||
|
||||
<Target Name="AfterPublishDeploy" AfterTargets="Publish">
|
||||
<Exec Command="echo 'Publishing host to watchdog publish dir for configuration $(ConfigurationName)'
dotnet publish $(ProjectDir)../Tgstation.Server.Host/Tgstation.Server.Host.csproj -c $(ConfigurationName) -o $(PublishDir)lib/Default" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -39,9 +39,10 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
var enviromentPath = Environment.GetEnvironmentVariable("PATH");
|
||||
var paths = enviromentPath.Split(';');
|
||||
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
var exeName = "dotnet";
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (isWindows)
|
||||
exeName += ".exe";
|
||||
|
||||
var dotnetPath = paths.Select(x => Path.Combine(x, exeName))
|
||||
@@ -57,13 +58,26 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
var rootLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog
|
||||
var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default"));
|
||||
#if DEBUG
|
||||
rootLocation = Path.GetFullPath("../../../../Tgstation.Server.Host.Watchdog/bin/Debug/netstandard2.0");
|
||||
//just copy the shit where it belongs
|
||||
Directory.Delete(assemblyStoragePath, true);
|
||||
Directory.CreateDirectory(defaultAssemblyPath);
|
||||
|
||||
var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.0";
|
||||
foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
|
||||
Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath));
|
||||
|
||||
foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
|
||||
File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true);
|
||||
|
||||
const string AppSettingsJson = "appsettings.json";
|
||||
var rootJson = Path.Combine(rootLocation, AppSettingsJson);
|
||||
File.Delete(rootJson);
|
||||
File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson);
|
||||
#endif
|
||||
|
||||
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); //always always next to watchdog
|
||||
|
||||
var defaultAssemblyPath = Path.GetFullPath(Path.Combine(assemblyStoragePath, "Default"));
|
||||
var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll");
|
||||
var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName);
|
||||
|
||||
@@ -86,6 +100,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
using (logger.BeginScope("Host invocation"))
|
||||
{
|
||||
updateDirectory = Path.GetFullPath(Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString()));
|
||||
logger.LogInformation("Update path set to {0}", updateDirectory);
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo.FileName = dotnetPath;
|
||||
@@ -96,6 +111,8 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
'"' + assemblyPath + '"',
|
||||
updateDirectory
|
||||
};
|
||||
if (Debugger.IsAttached)
|
||||
arguments.Add("--attach-debugger");
|
||||
arguments.AddRange(args);
|
||||
|
||||
process.StartInfo.Arguments = String.Join(" ", arguments);
|
||||
@@ -108,13 +125,16 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
tcs.TrySetResult(null);
|
||||
};
|
||||
process.EnableRaisingEvents = true;
|
||||
|
||||
|
||||
logger.LogInformation("Launching host...");
|
||||
|
||||
var iShotTheSheriff = false;
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
using (var processCts = new CancellationTokenSource())
|
||||
using (processCts.Token.Register(() => tcs.TrySetResult(null)))
|
||||
using (cancellationToken.Register(() =>
|
||||
{
|
||||
if (!Directory.Exists(updateDirectory))
|
||||
@@ -127,8 +147,11 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
logger.LogInformation("Will force close host process if it doesn't exit in 10 seconds...");
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(10)); //things get weird if we use tasks or other stuff
|
||||
tcs.TrySetResult(null);
|
||||
try
|
||||
{
|
||||
processCts.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch (ObjectDisposedException) { } //race conditions
|
||||
}))
|
||||
await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -151,10 +174,15 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
switch (process.ExitCode)
|
||||
{
|
||||
case 0:
|
||||
//just a restart
|
||||
logger.LogInformation("Watchdog will restart host...");
|
||||
break;
|
||||
return;
|
||||
case 1:
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
//just a restart
|
||||
logger.LogInformation("Watchdog will restart host...");
|
||||
else
|
||||
logger.LogWarning("Host requested restart but watchdog shutdown is in progress!");
|
||||
break;
|
||||
case 2:
|
||||
//update path is now an exception document
|
||||
logger.LogCritical("Host crashed, propagating exception dump...");
|
||||
var data = File.ReadAllText(updateDirectory);
|
||||
@@ -177,29 +205,46 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
}
|
||||
}
|
||||
|
||||
//HEY YOU
|
||||
//BE WARNED THAT IF YOU DEBUGGED THE HOST PROCESS THAT JUST LAUNCHED THE DEBUGGER WILL HOLD A LOCK ON THE DIRECTORY
|
||||
//THIS MEANS THE FIRST DIRECTORY.MOVE WILL THROW
|
||||
if (Directory.Exists(updateDirectory))
|
||||
{
|
||||
logger.LogInformation("Applying server update...");
|
||||
if (isWindows)
|
||||
{
|
||||
//windows dick sucking resource unlocking
|
||||
GC.Collect();
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
var tempPath = Path.Combine(assemblyStoragePath, Guid.NewGuid().ToString());
|
||||
Directory.Move(defaultAssemblyPath, tempPath);
|
||||
try
|
||||
{
|
||||
Directory.Move(updateDirectory, defaultAssemblyPath);
|
||||
logger.LogInformation("Server update complete, deleting old server...");
|
||||
Directory.Move(defaultAssemblyPath, tempPath);
|
||||
try
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
Directory.Move(updateDirectory, defaultAssemblyPath);
|
||||
logger.LogInformation("Server update complete, deleting old server...");
|
||||
try
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogWarning("Error deleting old server at {0}! Exception: {1}", tempPath, e);
|
||||
logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e);
|
||||
Directory.Delete(defaultAssemblyPath, true);
|
||||
Directory.Move(tempPath, defaultAssemblyPath);
|
||||
logger.LogInformation("Revert successful!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
catch(Exception e)
|
||||
{
|
||||
logger.LogError("Error moving updated server directory, attempting revert! Exception: {0}", e);
|
||||
Directory.Delete(defaultAssemblyPath, true);
|
||||
Directory.Move(tempPath, defaultAssemblyPath);
|
||||
logger.LogInformation("Revert successful!");
|
||||
logger.LogWarning("Failed to move out active host assembly! Exception: {0}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,7 +261,10 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
{
|
||||
logger.LogCritical("Watchdog error! Exception: {0}", e);
|
||||
}
|
||||
logger.LogInformation("Host watchdog exiting...");
|
||||
finally
|
||||
{
|
||||
logger.LogInformation("Host watchdog exiting...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ namespace Tgstation.Server.Host.Components
|
||||
readonly IByondTopicSender byondTopicSender;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerUpdater"/> for the <see cref="InstanceFactory"/>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly IServerUpdater serverUpdater;
|
||||
readonly IServerControl serverUpdater;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="InstanceFactory"/>
|
||||
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="byondInstaller">The value of <see cref="byondInstaller"/></param>
|
||||
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
|
||||
/// <param name="scriptExecutor">The value of <see cref="scriptExecutor"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerUpdater serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor)
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="chat">The value of <see cref="chat"/></param>
|
||||
/// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param>
|
||||
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
|
||||
/// <param name="serverUpdater">The <see cref="IServerUpdater"/> for the <see cref="Watchdog"/></param>
|
||||
/// <param name="serverUpdater">The <see cref="IServerControl"/> for the <see cref="Watchdog"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
@@ -140,7 +140,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
/// <param name="autoStart">The value of <see cref="autoStart"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerUpdater serverUpdater, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
|
||||
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
|
||||
{
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
@@ -156,7 +156,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (serverUpdater == null)
|
||||
throw new ArgumentNullException(nameof(serverUpdater));
|
||||
|
||||
serverUpdater.RegisterForUpdate(() => releaseServers = true);
|
||||
serverUpdater.RegisterForRestart(() => releaseServers = true);
|
||||
|
||||
chat.RegisterCommandHandler(this);
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
readonly ISessionControllerFactory sessionControllerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerUpdater"/> for the <see cref="WatchdogFactory"/>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IServerUpdater serverUpdater;
|
||||
readonly IServerControl serverUpdater;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
@@ -69,7 +69,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerUpdater serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance)
|
||||
public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerControl serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance)
|
||||
{
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
|
||||
@@ -36,9 +36,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
readonly IGitHubClient gitHubClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerUpdater"/> for the <see cref="AdministrationController"/>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="AdministrationController"/>
|
||||
/// </summary>
|
||||
readonly IServerUpdater serverUpdater;
|
||||
readonly IServerControl serverUpdater;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApplication"/> for the <see cref="AdministrationController"/>
|
||||
@@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/></param>
|
||||
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false)
|
||||
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
|
||||
@@ -163,11 +163,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(AdministrationRights.RestartHost)]
|
||||
public override Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
serverUpdater.Restart();
|
||||
return Task.FromResult((IActionResult)Ok());
|
||||
}
|
||||
public Task<IActionResult> Delete() => Task.FromResult(serverUpdater.Restart() ? (IActionResult)Ok() : StatusCode((int)HttpStatusCode.NotImplemented));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(DreamDaemonRights.Shutdown)]
|
||||
public override async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken)
|
||||
{
|
||||
//alias for stopping DD
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
|
||||
@@ -232,10 +232,15 @@ namespace Tgstation.Server.Host.Core
|
||||
/// Configure the <see cref="Application"/>
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to configure</param>
|
||||
public void Configure(IApplicationBuilder applicationBuilder)
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="Application"/></param>
|
||||
public void Configure(IApplicationBuilder applicationBuilder, ILogger<Application> logger)
|
||||
{
|
||||
if (applicationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||
if (logger == null)
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
logger.LogInformation(VersionString);
|
||||
|
||||
serverAddresses = applicationBuilder.ServerFeatures.Get<IServerAddressesFeature>();
|
||||
|
||||
|
||||
+5
-4
@@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <summary>
|
||||
/// Represents a service that may take an updated <see cref="Host"/> assembly and run it, stopping the current assembly in the process
|
||||
/// </summary>
|
||||
public interface IServerUpdater
|
||||
public interface IServerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Run a new <see cref="Host"/> assembly and stop the current one. This will likely trigger all active <see cref="CancellationToken"/>s
|
||||
@@ -20,14 +20,15 @@ namespace Tgstation.Server.Host.Core
|
||||
Task<bool> ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Register a given <paramref name="action"/> to run before stopping the server for updates
|
||||
/// Register a given <paramref name="action"/> to run before stopping the server for a restart
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to run</param>
|
||||
void RegisterForUpdate(Action action);
|
||||
void RegisterForRestart(Action action);
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the <see cref="Host"/>
|
||||
/// </summary>
|
||||
void Restart();
|
||||
/// <returns><see langword="true"/> if live restarts are supported, <see langword="false"/> otherwise</returns>
|
||||
bool Restart();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,11 @@ namespace Tgstation.Server.Host
|
||||
/// </summary>
|
||||
public interface IServer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// If the <see cref="IServer"/> should restart
|
||||
/// </summary>
|
||||
bool RestartRequested { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the <see cref="IServer"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -7,7 +8,7 @@ using System.Threading.Tasks;
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
/// <summary>
|
||||
/// Entrypoint for the <see cref="System.Diagnostics.Process"/>
|
||||
/// Entrypoint for the <see cref="Process"/>
|
||||
/// </summary>
|
||||
static class Program
|
||||
{
|
||||
@@ -20,47 +21,59 @@ namespace Tgstation.Server.Host
|
||||
/// Entrypoint for the <see cref="Program"/>
|
||||
/// </summary>
|
||||
/// <param name="args">The command line arguments</param>
|
||||
/// <returns>The <see cref="System.Diagnostics.Process.ExitCode"/></returns>
|
||||
/// <returns>The <see cref="Process.ExitCode"/></returns>
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
var listArgs = new List<string>(args);
|
||||
//first arg is 100% always the update path
|
||||
//first arg is 100% always the update path, starting it otherwise is solely for debugging purposes
|
||||
string updatePath;
|
||||
if (listArgs.Count > 0)
|
||||
{
|
||||
updatePath = listArgs[0];
|
||||
listArgs.RemoveAt(0);
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debugger.Launch();
|
||||
#endif
|
||||
if (listArgs.Remove("--attach-debugger"))
|
||||
Debugger.Launch();
|
||||
}
|
||||
else
|
||||
updatePath = null;
|
||||
try
|
||||
{
|
||||
using (var cts = new CancellationTokenSource())
|
||||
using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath))
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (a, b) => cts.Cancel();
|
||||
Console.CancelKeyPress += (a, b) =>
|
||||
try
|
||||
{
|
||||
b.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
using (var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath))
|
||||
await server.RunAsync(cts.Token).ConfigureAwait(false);
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
void AppDomainHandler(object a, EventArgs b) => cts.Cancel();
|
||||
AppDomain.CurrentDomain.ProcessExit += AppDomainHandler;
|
||||
try
|
||||
{
|
||||
Console.CancelKeyPress += (a, b) =>
|
||||
{
|
||||
b.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
await server.RunAsync(cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit -= AppDomainHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
return server.RestartRequested ? 1 : 0;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception e)
|
||||
{
|
||||
if (updatePath != null)
|
||||
{
|
||||
File.WriteAllText(updatePath, e.ToString());
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ using Tgstation.Server.Host.IO;
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class Server : IServer, IServerUpdater
|
||||
sealed class Server : IServer, IServerControl
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool RestartRequested { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWebHostBuilder"/> for the <see cref="Server"/>
|
||||
/// </summary>
|
||||
@@ -32,7 +35,7 @@ namespace Tgstation.Server.Host
|
||||
/// If a server update has been applied
|
||||
/// </summary>
|
||||
bool updated;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="cancellationTokenSource"/> for the <see cref="Server"/>
|
||||
/// </summary>
|
||||
@@ -50,6 +53,7 @@ namespace Tgstation.Server.Host
|
||||
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
updated = false;
|
||||
RestartRequested = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -66,14 +70,14 @@ namespace Tgstation.Server.Host
|
||||
{
|
||||
fsWatcher.Created += (a, b) =>
|
||||
{
|
||||
if (b.Name == updatePath && File.Exists(b.FullPath))
|
||||
if (b.FullPath == updatePath && File.Exists(b.FullPath))
|
||||
cancellationTokenSource.Cancel();
|
||||
};
|
||||
fsWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
using (var webHost = webHostBuilder
|
||||
.UseStartup<Application>()
|
||||
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerUpdater>(this))
|
||||
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerControl>(this))
|
||||
.Build()
|
||||
)
|
||||
await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
@@ -111,19 +115,28 @@ namespace Tgstation.Server.Host
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterForUpdate(Action action)
|
||||
public void RegisterForRestart(Action action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
if (cancellationTokenSource == null)
|
||||
throw new InvalidOperationException("Tried to register an update action on a non-running Server!");
|
||||
cancellationTokenSource.Token.Register(action);
|
||||
cancellationTokenSource.Token.Register(() => {
|
||||
if (RestartRequested)
|
||||
action();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Restart()
|
||||
public bool Restart()
|
||||
{
|
||||
if (updatePath == null)
|
||||
return false;
|
||||
if (cancellationTokenSource == null)
|
||||
throw new InvalidOperationException("Tried to restart a non-running Server!");
|
||||
RestartRequested = true;
|
||||
cancellationTokenSource.Cancel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,4 +59,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,13 +11,13 @@ using Tgstation.Server.Host.IO;
|
||||
namespace Tgstation.Server.Host.Core.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestApplication : IServerUpdater
|
||||
public sealed class TestApplication : IServerControl
|
||||
{
|
||||
public Task<bool> ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
|
||||
public void RegisterForUpdate(Action action) => throw new NotImplementedException();
|
||||
public void RegisterForRestart(Action action) => throw new NotImplementedException();
|
||||
|
||||
public void Restart() => throw new NotImplementedException();
|
||||
public bool Restart() => throw new NotImplementedException();
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestSuccessfulStartup()
|
||||
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Core.Tests
|
||||
{
|
||||
using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite
|
||||
.UseStartup<Application>()
|
||||
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerUpdater>(this))
|
||||
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerControl>(this))
|
||||
.Build()
|
||||
)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user